5
5
package types2
6
6
7
7
// validType verifies that the given type does not "expand" indefinitely
8
- // producing a cycle in the type graph. Cycles are detected by marking
9
- // defined types.
8
+ // producing a cycle in the type graph.
10
9
// (Cycles involving alias types, as in "type A = [10]A" are detected
11
10
// earlier, via the objDecl cycle detection mechanism.)
12
11
func (check * Checker ) validType (typ * Named ) {
13
- check .validType0 (typ , nil , nil )
12
+ check .validType0 (typ , nil , nil , nil )
14
13
}
15
14
16
- type typeInfo uint
17
-
18
15
// validType0 checks if the given type is valid. If typ is a type parameter
19
16
// its value is looked up in the provided environment. The environment is
20
17
// nil if typ is not part of (the RHS of) an instantiated type, in that case
21
18
// any type parameter encountered must be from an enclosing function and can
22
- // be ignored. The path is the list of type names that lead to the current typ.
23
- func (check * Checker ) validType0 (typ Type , env * tparamEnv , path []Object ) typeInfo {
24
- const (
25
- unknown typeInfo = iota
26
- marked
27
- valid
28
- invalid
29
- )
30
-
19
+ // be ignored. The nest list describes the stack (the "nest in memory") of
20
+ // types which contain (or embed in the case of interfaces) other types. For
21
+ // instance, a struct named S which contains a field of named type F contains
22
+ // (the memory of) F in S, leading to the nest S->F. If a type appears in its
23
+ // own nest (say S->F->S) we have an invalid recursive type. The path list is
24
+ // the full path of named types in a cycle, it is only needed for error reporting.
25
+ func (check * Checker ) validType0 (typ Type , env * tparamEnv , nest , path []* Named ) bool {
31
26
switch t := typ .(type ) {
32
27
case nil :
33
28
// We should never see a nil type but be conservative and panic
@@ -37,74 +32,109 @@ func (check *Checker) validType0(typ Type, env *tparamEnv, path []Object) typeIn
37
32
}
38
33
39
34
case * Array :
40
- return check .validType0 (t .elem , env , path )
35
+ return check .validType0 (t .elem , env , nest , path )
41
36
42
37
case * Struct :
43
38
for _ , f := range t .fields {
44
- if check .validType0 (f .typ , env , path ) == invalid {
45
- return invalid
39
+ if ! check .validType0 (f .typ , env , nest , path ) {
40
+ return false
46
41
}
47
42
}
48
43
49
44
case * Union :
50
45
for _ , t := range t .terms {
51
- if check .validType0 (t .typ , env , path ) == invalid {
52
- return invalid
46
+ if ! check .validType0 (t .typ , env , nest , path ) {
47
+ return false
53
48
}
54
49
}
55
50
56
51
case * Interface :
57
52
for _ , etyp := range t .embeddeds {
58
- if check .validType0 (etyp , env , path ) == invalid {
59
- return invalid
53
+ if ! check .validType0 (etyp , env , nest , path ) {
54
+ return false
60
55
}
61
56
}
62
57
63
58
case * Named :
59
+ // Exit early if we already know t is valid.
60
+ // This is purely an optimization but it prevents excessive computation
61
+ // times in pathological cases such as testdata/fixedbugs/issue6977.go.
62
+ // (Note: The valids map could also be allocated locally, once for each
63
+ // validType call.)
64
+ if check .valids .lookup (t ) != nil {
65
+ break
66
+ }
67
+
64
68
// Don't report a 2nd error if we already know the type is invalid
65
69
// (e.g., if a cycle was detected earlier, via under).
66
70
// Note: ensure that t.orig is fully resolved by calling Underlying().
67
71
if t .Underlying () == Typ [Invalid ] {
68
- check .infoMap [t ] = invalid
69
- return invalid
72
+ return false
70
73
}
71
74
72
- switch check .infoMap [t ] {
73
- case unknown :
74
- check .infoMap [t ] = marked
75
- check .infoMap [t ] = check .validType0 (t .Origin ().fromRHS , env .push (t ), append (path , t .obj ))
76
- case marked :
77
- // We have seen type t before and thus must have a cycle.
78
- check .infoMap [t ] = invalid
79
- // t cannot be in an imported package otherwise that package
80
- // would have reported a type cycle and couldn't have been
81
- // imported in the first place.
82
- assert (t .obj .pkg == check .pkg )
83
- t .underlying = Typ [Invalid ] // t is in the current package (no race possibility)
84
- // Find the starting point of the cycle and report it.
85
- for i , tn := range path {
86
- if tn == t .obj {
87
- check .cycleError (path [i :])
88
- return invalid
75
+ // If the current type t is also found in nest, (the memory of) t is
76
+ // embedded in itself, indicating an invalid recursive type.
77
+ for _ , e := range nest {
78
+ if Identical (e , t ) {
79
+ // t cannot be in an imported package otherwise that package
80
+ // would have reported a type cycle and couldn't have been
81
+ // imported in the first place.
82
+ assert (t .obj .pkg == check .pkg )
83
+ t .underlying = Typ [Invalid ] // t is in the current package (no race possibility)
84
+ // Find the starting point of the cycle and report it.
85
+ // Because each type in nest must also appear in path (see invariant below),
86
+ // type t must be in path since it was found in nest. But not every type in path
87
+ // is in nest. Specifically t may appear in path with an earlier index than the
88
+ // index of t in nest. Search again.
89
+ for start , p := range path {
90
+ if Identical (p , t ) {
91
+ check .cycleError (makeObjList (path [start :]))
92
+ return false
93
+ }
89
94
}
95
+ panic ("cycle start not found" )
90
96
}
91
- panic ("cycle start not found" )
92
97
}
93
- return check .infoMap [t ]
98
+
99
+ // No cycle was found. Check the RHS of t.
100
+ // Every type added to nest is also added to path; thus every type that is in nest
101
+ // must also be in path (invariant). But not every type in path is in nest, since
102
+ // nest may be pruned (see below, *TypeParam case).
103
+ if ! check .validType0 (t .Origin ().fromRHS , env .push (t ), append (nest , t ), append (path , t )) {
104
+ return false
105
+ }
106
+
107
+ check .valids .add (t ) // t is valid
94
108
95
109
case * TypeParam :
96
110
// A type parameter stands for the type (argument) it was instantiated with.
97
111
// Check the corresponding type argument for validity if we have one.
98
112
if env != nil {
99
113
if targ := env .tmap [t ]; targ != nil {
100
114
// Type arguments found in targ must be looked
101
- // up in the enclosing environment env.link.
102
- return check .validType0 (targ , env .link , path )
115
+ // up in the enclosing environment env.link. The
116
+ // type argument must be valid in the enclosing
117
+ // type (where the current type was instantiated),
118
+ // hence we must check targ's validity in the type
119
+ // nest excluding the current (instantiated) type
120
+ // (see the example at the end of this file).
121
+ // For error reporting we keep the full path.
122
+ return check .validType0 (targ , env .link , nest [:len (nest )- 1 ], path )
103
123
}
104
124
}
105
125
}
106
126
107
- return valid
127
+ return true
128
+ }
129
+
130
+ // makeObjList returns the list of type name objects for the given
131
+ // list of named types.
132
+ func makeObjList (tlist []* Named ) []Object {
133
+ olist := make ([]Object , len (tlist ))
134
+ for i , t := range tlist {
135
+ olist [i ] = t .obj
136
+ }
137
+ return olist
108
138
}
109
139
110
140
// A tparamEnv provides the environment for looking up the type arguments
@@ -146,3 +176,93 @@ func (env *tparamEnv) push(typ *Named) *tparamEnv {
146
176
// same information should be available via the path:
147
177
// We should be able to just walk the path backwards
148
178
// and find the type arguments in the instance objects.
179
+
180
+ // Here is an example illustrating why we need to exclude the
181
+ // instantiated type from nest when evaluating the validity of
182
+ // a type parameter. Given the declarations
183
+ //
184
+ // var _ A[A[string]]
185
+ //
186
+ // type A[P any] struct { _ B[P] }
187
+ // type B[P any] struct { _ P }
188
+ //
189
+ // we want to determine if the type A[A[string]] is valid.
190
+ // We start evaluating A[A[string]] outside any type nest:
191
+ //
192
+ // A[A[string]]
193
+ // nest =
194
+ // path =
195
+ //
196
+ // The RHS of A is now evaluated in the A[A[string]] nest:
197
+ //
198
+ // struct{_ B[P₁]}
199
+ // nest = A[A[string]]
200
+ // path = A[A[string]]
201
+ //
202
+ // The struct has a single field of type B[P₁] with which
203
+ // we continue:
204
+ //
205
+ // B[P₁]
206
+ // nest = A[A[string]]
207
+ // path = A[A[string]]
208
+ //
209
+ // struct{_ P₂}
210
+ // nest = A[A[string]]->B[P]
211
+ // path = A[A[string]]->B[P]
212
+ //
213
+ // Eventutally we reach the type parameter P of type B (P₂):
214
+ //
215
+ // P₂
216
+ // nest = A[A[string]]->B[P]
217
+ // path = A[A[string]]->B[P]
218
+ //
219
+ // The type argument for P of B is the type parameter P of A (P₁).
220
+ // It must be evaluated in the type nest that existed when B was
221
+ // instantiated:
222
+ //
223
+ // P₁
224
+ // nest = A[A[string]] <== type nest at B's instantiation time
225
+ // path = A[A[string]]->B[P]
226
+ //
227
+ // If we'd use the current nest it would correspond to the path
228
+ // which will be wrong as we will see shortly. P's type argument
229
+ // is A[string], which again must be evaluated in the type nest
230
+ // that existed when A was instantiated with A[string]. That type
231
+ // nest is empty:
232
+ //
233
+ // A[string]
234
+ // nest = <== type nest at A's instantiation time
235
+ // path = A[A[string]]->B[P]
236
+ //
237
+ // Evaluation then proceeds as before for A[string]:
238
+ //
239
+ // struct{_ B[P₁]}
240
+ // nest = A[string]
241
+ // path = A[A[string]]->B[P]->A[string]
242
+ //
243
+ // Now we reach B[P] again. If we had not adjusted nest, it would
244
+ // correspond to path, and we would find B[P] in nest, indicating
245
+ // a cycle, which would clearly be wrong since there's no cycle in
246
+ // A[string]:
247
+ //
248
+ // B[P₁]
249
+ // nest = A[string]
250
+ // path = A[A[string]]->B[P]->A[string] <== path contains B[P]!
251
+ //
252
+ // But because we use the correct type nest, evaluation proceeds without
253
+ // errors and we get the evaluation sequence:
254
+ //
255
+ // struct{_ P₂}
256
+ // nest = A[string]->B[P]
257
+ // path = A[A[string]]->B[P]->A[string]->B[P]
258
+ // P₂
259
+ // nest = A[string]->B[P]
260
+ // path = A[A[string]]->B[P]->A[string]->B[P]
261
+ // P₁
262
+ // nest = A[string]
263
+ // path = A[A[string]]->B[P]->A[string]->B[P]
264
+ // string
265
+ // nest =
266
+ // path = A[A[string]]->B[P]->A[string]->B[P]
267
+ //
268
+ // At this point we're done and A[A[string]] and is valid.
0 commit comments