1 | // Copyright 2018 The Go Authors. All rights reserved. |
---|---|
2 | // Use of this source code is governed by a BSD-style |
3 | // license that can be found in the LICENSE file. |
4 | |
5 | package typeutil_test |
6 | |
7 | import ( |
8 | "fmt" |
9 | "go/ast" |
10 | "go/parser" |
11 | "go/token" |
12 | "go/types" |
13 | "strings" |
14 | "testing" |
15 | |
16 | "golang.org/x/tools/go/types/typeutil" |
17 | "golang.org/x/tools/internal/typeparams" |
18 | ) |
19 | |
20 | func TestStaticCallee(t *testing.T) { |
21 | testStaticCallee(t, []string{ |
22 | `package q; |
23 | func Abs(x int) int { |
24 | if x < 0 { |
25 | return -x |
26 | } |
27 | return x |
28 | }`, |
29 | `package p |
30 | import "q" |
31 | |
32 | type T int |
33 | |
34 | func g(int) |
35 | |
36 | var f = g |
37 | |
38 | var x int |
39 | |
40 | type s struct{ f func(int) } |
41 | func (s) g(int) |
42 | |
43 | type I interface{ f(int) } |
44 | |
45 | var a struct{b struct{c s}} |
46 | |
47 | var n map[int]func() |
48 | var m []func() |
49 | |
50 | func calls() { |
51 | g(x) // a declared func |
52 | s{}.g(x) // a concrete method |
53 | a.b.c.g(x) // same |
54 | _ = q.Abs(x) // declared func, qualified identifier |
55 | } |
56 | |
57 | func noncalls() { |
58 | _ = T(x) // a type |
59 | f(x) // a var |
60 | panic(x) // a built-in |
61 | s{}.f(x) // a field |
62 | I(nil).f(x) // interface method |
63 | m[0]() // a map |
64 | n[0]() // a slice |
65 | } |
66 | `}) |
67 | } |
68 | |
69 | func TestTypeParamStaticCallee(t *testing.T) { |
70 | if !typeparams.Enabled { |
71 | t.Skip("type parameters are not enabled") |
72 | } |
73 | testStaticCallee(t, []string{ |
74 | `package q |
75 | func R[T any]() {} |
76 | `, |
77 | `package p |
78 | import "q" |
79 | type I interface{ |
80 | i() |
81 | } |
82 | |
83 | type G[T any] func() T |
84 | func F[T any]() T { var x T; return x } |
85 | |
86 | type M[T I] struct{ t T } |
87 | func (m M[T]) noncalls() { |
88 | m.t.i() // method on a type parameter |
89 | } |
90 | |
91 | func (m M[T]) calls() { |
92 | m.calls() // method on a generic type |
93 | } |
94 | |
95 | type Chain[T I] struct{ r struct { s M[T] } } |
96 | |
97 | type S int |
98 | func (S) i() {} |
99 | |
100 | func Multi[TP0, TP1 any](){} |
101 | |
102 | func calls() { |
103 | _ = F[int]() // instantiated function |
104 | _ = (F[int])() // go through parens |
105 | M[S]{}.calls() // instantiated method |
106 | Chain[S]{}.r.s.calls() // same as above |
107 | Multi[int,string]() // multiple type parameters |
108 | q.R[int]() // different package |
109 | } |
110 | |
111 | func noncalls() { |
112 | _ = G[int](nil)() // instantiated function |
113 | } |
114 | `}) |
115 | } |
116 | |
117 | // testStaticCallee parses and type checks each file content in contents |
118 | // as a single file package in order. Within functions that have the suffix |
119 | // "calls" it checks that the CallExprs within have a static callee. |
120 | // If the function's name == "calls" all calls must have static callees, |
121 | // and if the name != "calls", the calls must not have static callees. |
122 | // Failures are reported on t. |
123 | func testStaticCallee(t *testing.T, contents []string) { |
124 | fset := token.NewFileSet() |
125 | packages := make(map[string]*types.Package) |
126 | cfg := &types.Config{Importer: closure(packages)} |
127 | info := &types.Info{ |
128 | Uses: make(map[*ast.Ident]types.Object), |
129 | Selections: make(map[*ast.SelectorExpr]*types.Selection), |
130 | } |
131 | typeparams.InitInstanceInfo(info) |
132 | |
133 | var files []*ast.File |
134 | for i, content := range contents { |
135 | // parse |
136 | f, err := parser.ParseFile(fset, fmt.Sprintf("%d.go", i), content, 0) |
137 | if err != nil { |
138 | t.Fatal(err) |
139 | } |
140 | files = append(files, f) |
141 | |
142 | // type-check |
143 | pkg, err := cfg.Check(f.Name.Name, fset, []*ast.File{f}, info) |
144 | if err != nil { |
145 | t.Fatal(err) |
146 | } |
147 | packages[pkg.Path()] = pkg |
148 | } |
149 | |
150 | // check |
151 | for _, f := range files { |
152 | for _, decl := range f.Decls { |
153 | if decl, ok := decl.(*ast.FuncDecl); ok && strings.HasSuffix(decl.Name.Name, "calls") { |
154 | wantCallee := decl.Name.Name == "calls" // false within func noncalls() |
155 | ast.Inspect(decl.Body, func(n ast.Node) bool { |
156 | if call, ok := n.(*ast.CallExpr); ok { |
157 | fn := typeutil.StaticCallee(info, call) |
158 | if fn == nil && wantCallee { |
159 | t.Errorf("%s: StaticCallee returned nil", |
160 | fset.Position(call.Lparen)) |
161 | } else if fn != nil && !wantCallee { |
162 | t.Errorf("%s: StaticCallee returned %s, want nil", |
163 | fset.Position(call.Lparen), fn) |
164 | } |
165 | } |
166 | return true |
167 | }) |
168 | } |
169 | } |
170 | } |
171 | } |
172 |
Members