@@ -17,6 +17,7 @@ import (
17
17
"golang.org/x/tools/go/analysis/internal/facts"
18
18
"golang.org/x/tools/go/packages"
19
19
"golang.org/x/tools/internal/testenv"
20
+ "golang.org/x/tools/internal/typeparams"
20
21
)
21
22
22
23
type myFact struct {
@@ -26,23 +27,209 @@ type myFact struct {
26
27
func (f * myFact ) String () string { return fmt .Sprintf ("myFact(%s)" , f .S ) }
27
28
func (f * myFact ) AFact () {}
28
29
29
- func TestEncodeDecode ( t * testing. T ) {
30
+ func init ( ) {
30
31
gob .Register (new (myFact ))
32
+ }
31
33
32
- // c -> b -> a, a2
33
- // c does not directly depend on a, but it indirectly uses a.T.
34
- //
35
- // Package a2 is never loaded directly so it is incomplete.
36
- //
37
- // We use only types in this example because we rely on
38
- // types.Eval to resolve the lookup expressions, and it only
39
- // works for types. This is a definite gap in the typechecker API.
40
- files := map [string ]string {
41
- "a/a.go" : `package a; type A int; type T int` ,
42
- "a2/a.go" : `package a2; type A2 int; type Unneeded int` ,
43
- "b/b.go" : `package b; import ("a"; "a2"); type B chan a2.A2; type F func() a.T` ,
44
- "c/c.go" : `package c; import "b"; type C []b.B` ,
34
+ func TestEncodeDecode (t * testing.T ) {
35
+ tests := []struct {
36
+ name string
37
+ typeparams bool // requires typeparams to be enabled
38
+ files map [string ]string
39
+ plookups []pkgLookups // see testEncodeDecode for details
40
+ }{
41
+ {
42
+ name : "loading-order" ,
43
+ // c -> b -> a, a2
44
+ // c does not directly depend on a, but it indirectly uses a.T.
45
+ //
46
+ // Package a2 is never loaded directly so it is incomplete.
47
+ //
48
+ // We use only types in this example because we rely on
49
+ // types.Eval to resolve the lookup expressions, and it only
50
+ // works for types. This is a definite gap in the typechecker API.
51
+ files : map [string ]string {
52
+ "a/a.go" : `package a; type A int; type T int` ,
53
+ "a2/a.go" : `package a2; type A2 int; type Unneeded int` ,
54
+ "b/b.go" : `package b; import ("a"; "a2"); type B chan a2.A2; type F func() a.T` ,
55
+ "c/c.go" : `package c; import "b"; type C []b.B` ,
56
+ },
57
+ // In the following table, we analyze packages (a, b, c) in order,
58
+ // look up various objects accessible within each package,
59
+ // and see if they have a fact. The "analysis" exports a fact
60
+ // for every object at package level.
61
+ //
62
+ // Note: Loop iterations are not independent test cases;
63
+ // order matters, as we populate factmap.
64
+ plookups : []pkgLookups {
65
+ {"a" , []lookup {
66
+ {"A" , "myFact(a.A)" },
67
+ }},
68
+ {"b" , []lookup {
69
+ {"a.A" , "myFact(a.A)" },
70
+ {"a.T" , "myFact(a.T)" },
71
+ {"B" , "myFact(b.B)" },
72
+ {"F" , "myFact(b.F)" },
73
+ {"F(nil)()" , "myFact(a.T)" }, // (result type of b.F)
74
+ }},
75
+ {"c" , []lookup {
76
+ {"b.B" , "myFact(b.B)" },
77
+ {"b.F" , "myFact(b.F)" },
78
+ //{"b.F(nil)()", "myFact(a.T)"}, // no fact; TODO(adonovan): investigate
79
+ {"C" , "myFact(c.C)" },
80
+ {"C{}[0]" , "myFact(b.B)" },
81
+ {"<-(C{}[0])" , "no fact" }, // object but no fact (we never "analyze" a2)
82
+ }},
83
+ },
84
+ },
85
+ {
86
+ name : "globals" ,
87
+ files : map [string ]string {
88
+ "a/a.go" : `package a;
89
+ type T1 int
90
+ type T2 int
91
+ type T3 int
92
+ type T4 int
93
+ type T5 int
94
+ type K int; type V string
95
+ ` ,
96
+ "b/b.go" : `package b
97
+ import "a"
98
+ var (
99
+ G1 []a.T1
100
+ G2 [7]a.T2
101
+ G3 chan a.T3
102
+ G4 *a.T4
103
+ G5 struct{ F a.T5 }
104
+ G6 map[a.K]a.V
105
+ )
106
+ ` ,
107
+ "c/c.go" : `package c; import "b";
108
+ var (
109
+ v1 = b.G1
110
+ v2 = b.G2
111
+ v3 = b.G3
112
+ v4 = b.G4
113
+ v5 = b.G5
114
+ v6 = b.G6
115
+ )
116
+ ` ,
117
+ },
118
+ plookups : []pkgLookups {
119
+ {"a" , []lookup {}},
120
+ {"b" , []lookup {}},
121
+ {"c" , []lookup {
122
+ {"v1[0]" , "myFact(a.T1)" },
123
+ {"v2[0]" , "myFact(a.T2)" },
124
+ {"<-v3" , "myFact(a.T3)" },
125
+ {"*v4" , "myFact(a.T4)" },
126
+ {"v5.F" , "myFact(a.T5)" },
127
+ {"v6[0]" , "myFact(a.V)" },
128
+ }},
129
+ },
130
+ },
131
+ {
132
+ name : "typeparams" ,
133
+ typeparams : true ,
134
+ files : map [string ]string {
135
+ "a/a.go" : `package a
136
+ type T1 int
137
+ type T2 int
138
+ type T3 interface{Foo()}
139
+ type T4 int
140
+ type T5 int
141
+ type T6 interface{Foo()}
142
+ ` ,
143
+ "b/b.go" : `package b
144
+ import "a"
145
+ type N1[T a.T1|int8] func() T
146
+ type N2[T any] struct{ F T }
147
+ type N3[T a.T3] func() T
148
+ type N4[T a.T4|int8] func() T
149
+ type N5[T interface{Bar() a.T5} ] func() T
150
+
151
+ type t5 struct{}; func (t5) Bar() a.T5
152
+
153
+ var G1 N1[a.T1]
154
+ var G2 func() N2[a.T2]
155
+ var G3 N3[a.T3]
156
+ var G4 N4[a.T4]
157
+ var G5 N5[t5]
158
+
159
+ func F6[T a.T6]() T { var x T; return x }
160
+ ` ,
161
+ "c/c.go" : `package c; import "b";
162
+ var (
163
+ v1 = b.G1
164
+ v2 = b.G2
165
+ v3 = b.G3
166
+ v4 = b.G4
167
+ v5 = b.G5
168
+ v6 = b.F6[t6]
169
+ )
170
+
171
+ type t6 struct{}; func (t6) Foo() {}
172
+ ` ,
173
+ },
174
+ plookups : []pkgLookups {
175
+ {"a" , []lookup {}},
176
+ {"b" , []lookup {}},
177
+ {"c" , []lookup {
178
+ {"v1" , "myFact(b.N1)" },
179
+ {"v1()" , "myFact(a.T1)" },
180
+ {"v2()" , "myFact(b.N2)" },
181
+ {"v2().F" , "myFact(a.T2)" },
182
+ {"v3" , "myFact(b.N3)" },
183
+ {"v4" , "myFact(b.N4)" },
184
+ {"v4()" , "myFact(a.T4)" },
185
+ {"v5" , "myFact(b.N5)" },
186
+ {"v5()" , "myFact(b.t5)" },
187
+ {"v6()" , "myFact(c.t6)" },
188
+ }},
189
+ },
190
+ },
191
+ }
192
+
193
+ for i := range tests {
194
+ test := tests [i ]
195
+ t .Run (test .name , func (t * testing.T ) {
196
+ t .Parallel ()
197
+ if test .typeparams && ! typeparams .Enabled {
198
+ t .Skip ("type parameters are not enabled" )
199
+ }
200
+ testEncodeDecode (t , test .files , test .plookups )
201
+ })
45
202
}
203
+ }
204
+
205
+ type lookup struct {
206
+ objexpr string
207
+ want string
208
+ }
209
+
210
+ type pkgLookups struct {
211
+ path string
212
+ lookups []lookup
213
+ }
214
+
215
+ // testEncodeDecode tests fact encoding and decoding and simulates how package facts
216
+ // are passed during analysis. It operates on a group of Go file contents. Then
217
+ // for each <package, []lookup> in tests it does the following:
218
+ // 1) loads and type checks the package,
219
+ // 2) calls facts.Decode to loads the facts exported by its imports,
220
+ // 3) exports a myFact Fact for all of package level objects,
221
+ // 4) For each lookup for the current package:
222
+ // 4.a) lookup the types.Object for an Go source expression in the curent package
223
+ // (or confirms one is not expected want=="no object"),
224
+ // 4.b) finds a Fact for the object (or confirms one is not expected want=="no fact"),
225
+ // 4.c) compares the content of the Fact to want.
226
+ // 5) encodes the Facts of the package.
227
+ //
228
+ // Note: tests are not independent test cases; order matters (as does a package being
229
+ // skipped). It changes what Facts can be imported.
230
+ //
231
+ // Failures are reported on t.
232
+ func testEncodeDecode (t * testing.T , files map [string ]string , tests []pkgLookups ) {
46
233
dir , cleanup , err := analysistest .WriteFiles (files )
47
234
if err != nil {
48
235
t .Fatal (err )
@@ -54,40 +241,13 @@ func TestEncodeDecode(t *testing.T) {
54
241
factmap := make (map [string ][]byte )
55
242
read := func (path string ) ([]byte , error ) { return factmap [path ], nil }
56
243
57
- // In the following table, we analyze packages (a, b, c) in order,
58
- // look up various objects accessible within each package,
59
- // and see if they have a fact. The "analysis" exports a fact
60
- // for every object at package level.
244
+ // Analyze packages in order, look up various objects accessible within
245
+ // each package, and see if they have a fact. The "analysis" exports a
246
+ // fact for every object at package level.
61
247
//
62
248
// Note: Loop iterations are not independent test cases;
63
249
// order matters, as we populate factmap.
64
- type lookups []struct {
65
- objexpr string
66
- want string
67
- }
68
- for _ , test := range []struct {
69
- path string
70
- lookups lookups
71
- }{
72
- {"a" , lookups {
73
- {"A" , "myFact(a.A)" },
74
- }},
75
- {"b" , lookups {
76
- {"a.A" , "myFact(a.A)" },
77
- {"a.T" , "myFact(a.T)" },
78
- {"B" , "myFact(b.B)" },
79
- {"F" , "myFact(b.F)" },
80
- {"F(nil)()" , "myFact(a.T)" }, // (result type of b.F)
81
- }},
82
- {"c" , lookups {
83
- {"b.B" , "myFact(b.B)" },
84
- {"b.F" , "myFact(b.F)" },
85
- //{"b.F(nil)()", "myFact(a.T)"}, // no fact; TODO(adonovan): investigate
86
- {"C" , "myFact(c.C)" },
87
- {"C{}[0]" , "myFact(b.B)" },
88
- {"<-(C{}[0])" , "no fact" }, // object but no fact (we never "analyze" a2)
89
- }},
90
- } {
250
+ for _ , test := range tests {
91
251
// load package
92
252
pkg , err := load (t , dir , test .path )
93
253
if err != nil {
@@ -99,18 +259,16 @@ func TestEncodeDecode(t *testing.T) {
99
259
if err != nil {
100
260
t .Fatalf ("Decode failed: %v" , err )
101
261
}
102
- if true {
103
- t .Logf ("decode %s facts = %v" , pkg .Path (), facts ) // show all facts
104
- }
262
+ t .Logf ("decode %s facts = %v" , pkg .Path (), facts ) // show all facts
105
263
106
264
// export
107
265
// (one fact for each package-level object)
108
- scope := pkg .Scope ()
109
- for _ , name := range scope .Names () {
110
- obj := scope .Lookup (name )
266
+ for _ , name := range pkg .Scope ().Names () {
267
+ obj := pkg .Scope ().Lookup (name )
111
268
fact := & myFact {obj .Pkg ().Name () + "." + obj .Name ()}
112
269
facts .ExportObjectFact (obj , fact )
113
270
}
271
+ t .Logf ("exported %s facts = %v" , pkg .Path (), facts ) // show all facts
114
272
115
273
// import
116
274
// (after export, because an analyzer may import its own facts)
0 commit comments