From 02eab57fcf07c5dffe3a7f31258e1da0bddc02b5 Mon Sep 17 00:00:00 2001 From: Alan Donovan Date: Mon, 17 Sep 2018 10:16:20 -0400 Subject: [PATCH] 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 --- go/types/typeutil/callee.go | 35 +++++++++++++ go/types/typeutil/callee_test.go | 89 ++++++++++++++++++++++++++++++++ 2 files changed, 124 insertions(+) create mode 100644 go/types/typeutil/callee.go create mode 100644 go/types/typeutil/callee_test.go diff --git a/go/types/typeutil/callee.go b/go/types/typeutil/callee.go new file mode 100644 index 00000000000..c079a499823 --- /dev/null +++ b/go/types/typeutil/callee.go @@ -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()) +} diff --git a/go/types/typeutil/callee_test.go b/go/types/typeutil/callee_test.go new file mode 100644 index 00000000000..a0d107dc98f --- /dev/null +++ b/go/types/typeutil/callee_test.go @@ -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 + }) + } + } +}