1
0
mirror of https://github.com/golang/go synced 2024-10-01 01:38:33 -06:00

go/types/typeutil: add StaticCallee helper function

StaticCallee reports the destination of a CallExpr, if it is a static
call to a Go function or method.

StaticCallee is not a complicated function, but I wrote it four times
during the course of prototyping the analysis API in
go-review.googlesource.com/c/tools/+/134935.

Change-Id: Icd26fc1e5f6ed9edebd4d0a00fdf18aa0acb074c
Reviewed-on: https://go-review.googlesource.com/135676
Reviewed-by: Robert Griesemer <gri@golang.org>
This commit is contained in:
Alan Donovan 2018-09-17 10:16:20 -04:00
parent 677d2ff680
commit 02eab57fcf
2 changed files with 124 additions and 0 deletions

View File

@ -0,0 +1,35 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package typeutil
import (
"go/ast"
"go/types"
)
// StaticCallee returns the target (function or method) of a static
// function call, if any. It returns nil for calls to builtin.
func StaticCallee(info *types.Info, call *ast.CallExpr) *types.Func {
var obj types.Object
switch fun := call.Fun.(type) {
case *ast.Ident:
obj = info.Uses[fun] // type, var, builtin, or declared func
case *ast.SelectorExpr:
if sel, ok := info.Selections[fun]; ok {
obj = sel.Obj() // method or field
} else {
obj = info.Uses[fun.Sel] // qualified identifier?
}
}
if f, ok := obj.(*types.Func); ok && !interfaceMethod(f) {
return f
}
return nil
}
func interfaceMethod(f *types.Func) bool {
recv := f.Type().(*types.Signature).Recv()
return recv != nil && types.IsInterface(recv.Type())
}

View File

@ -0,0 +1,89 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package typeutil_test
import (
"go/ast"
"go/importer"
"go/parser"
"go/token"
"go/types"
"strings"
"testing"
"golang.org/x/tools/go/types/typeutil"
)
func TestStaticCallee(t *testing.T) {
const src = `package p
import "fmt"
type T int
func g(int)
var f = g
var x int
type s struct{ f func(int) }
func (s) g(int)
type I interface{ f(int) }
var a struct{b struct{c s}}
func calls() {
g(x) // a declared func
s{}.g(x) // a concrete method
a.b.c.g(x) // same
fmt.Println(x) // declared func, qualified identifier
}
func noncalls() {
_ = T(x) // a type
f(x) // a var
panic(x) // a built-in
s{}.f(x) // a field
I(nil).f(x) // interface method
}
`
// parse
fset := token.NewFileSet()
f, err := parser.ParseFile(fset, "p.go", src, 0)
if err != nil {
t.Fatal(err)
}
// type-check
info := &types.Info{
Uses: make(map[*ast.Ident]types.Object),
Selections: make(map[*ast.SelectorExpr]*types.Selection),
}
cfg := &types.Config{Importer: importer.For("source", nil)}
if _, err := cfg.Check("p", fset, []*ast.File{f}, info); err != nil {
t.Fatal(err)
}
for _, decl := range f.Decls {
if decl, ok := decl.(*ast.FuncDecl); ok && strings.HasSuffix(decl.Name.Name, "calls") {
wantCallee := decl.Name.Name == "calls" // false within func noncalls()
ast.Inspect(decl.Body, func(n ast.Node) bool {
if call, ok := n.(*ast.CallExpr); ok {
fn := typeutil.StaticCallee(info, call)
if fn == nil && wantCallee {
t.Errorf("%s: StaticCallee returned nil",
fset.Position(call.Lparen))
} else if fn != nil && !wantCallee {
t.Errorf("%s: StaticCallee returned %s, want nil",
fset.Position(call.Lparen), fn)
}
}
return true
})
}
}
}