mirror of
https://github.com/golang/go
synced 2024-11-18 19:14:40 -07: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:
parent
677d2ff680
commit
02eab57fcf
35
go/types/typeutil/callee.go
Normal file
35
go/types/typeutil/callee.go
Normal 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())
|
||||
}
|
89
go/types/typeutil/callee_test.go
Normal file
89
go/types/typeutil/callee_test.go
Normal 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
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user