Skip to content

Commit 323e009

Browse files
committed
go/types: ensure named types are expanded after type-checking
Rather than using Checker.later in newNamed, add a Checker.defTypes field to track named types that have been created during type-checking, and use this to expand named types as a final phase in type checking. We have encountered several bugs related to infinite recursion while expanding named types, because (I would argue) we have two conflicting requirements in the type checker: ensuring that we eventually collapse underlying chains, and yet allowing lazy substitution of the underlying type in instances. The former is necessary for correctness, and to ensure that we detect cycles during the type-checking pass. The latter is necessary to allow infinitely expanding patterns of instances through underlying or method definitions. I believe this CL reconciles these conflicting requirements, by creating a boundary between types that are encountered in the source during type checking, and instances that are created by recursive evaluation. At the end of the type checking pass, Checker.defTypes should contain all possible origin types for instantiation. Once we compute the true underlying for these origin types, any remaining instances that are unresolved are guaranteed to have an origin with a valid underlying. Therefore, we can return from the type-checking pass without calling under() for these remaining instances. Fixes #48703 Fixes #48974 Change-Id: I1474f514e2ab71c1ad4c3704fe32bfba11d59394 Reviewed-on: https://go-review.googlesource.com/c/go/+/356490 Trust: Robert Findley <[email protected]> Run-TryBot: Robert Findley <[email protected]> TryBot-Result: Go Bot <[email protected]> Reviewed-by: Robert Griesemer <[email protected]>
1 parent 7397178 commit 323e009

File tree

4 files changed

+84
-15
lines changed

4 files changed

+84
-15
lines changed

src/go/types/check.go

+27
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ type Checker struct {
113113
untyped map[ast.Expr]exprInfo // map of expressions without final type
114114
delayed []func() // stack of delayed action segments; segments are processed in FIFO order
115115
objPath []Object // path of object dependencies during type inference (for cycle reporting)
116+
defTypes []*Named // defined types created during type checking, for final validation.
116117

117118
// context within which the current object is type-checked
118119
// (valid only for the duration of type-checking a specific object)
@@ -269,6 +270,8 @@ func (check *Checker) checkFiles(files []*ast.File) (err error) {
269270

270271
check.processDelayed(0) // incl. all functions
271272

273+
check.expandDefTypes()
274+
272275
check.initOrder()
273276

274277
if !check.conf.DisableUnusedImportCheck {
@@ -285,6 +288,7 @@ func (check *Checker) checkFiles(files []*ast.File) (err error) {
285288
check.pkgPathMap = nil
286289
check.seenPkgMap = nil
287290
check.recvTParamMap = nil
291+
check.defTypes = nil
288292

289293
// TODO(rFindley) There's more memory we should release at this point.
290294

@@ -306,6 +310,29 @@ func (check *Checker) processDelayed(top int) {
306310
check.delayed = check.delayed[:top]
307311
}
308312

313+
func (check *Checker) expandDefTypes() {
314+
// Ensure that every defined type created in the course of type-checking has
315+
// either non-*Named underlying, or is unresolved.
316+
//
317+
// This guarantees that we don't leak any types whose underlying is *Named,
318+
// because any unresolved instances will lazily compute their underlying by
319+
// substituting in the underlying of their origin. The origin must have
320+
// either been imported or type-checked and expanded here, and in either case
321+
// its underlying will be fully expanded.
322+
for i := 0; i < len(check.defTypes); i++ {
323+
n := check.defTypes[i]
324+
switch n.underlying.(type) {
325+
case nil:
326+
if n.resolver == nil {
327+
panic("nil underlying")
328+
}
329+
case *Named:
330+
n.under() // n.under may add entries to check.defTypes
331+
}
332+
n.check = nil
333+
}
334+
}
335+
309336
func (check *Checker) record(x *operand) {
310337
// convert x into a user-friendly set of values
311338
// TODO(gri) this code can be simplified

src/go/types/named.go

+8-15
Original file line numberDiff line numberDiff line change
@@ -65,22 +65,9 @@ func (check *Checker) newNamed(obj *TypeName, orig *Named, underlying Type, tpar
6565
if obj.typ == nil {
6666
obj.typ = typ
6767
}
68-
// Ensure that typ is always expanded, at which point the check field can be
69-
// nilled out.
70-
//
71-
// Note that currently we cannot nil out check inside typ.under(), because
72-
// it's possible that typ is expanded multiple times.
73-
//
74-
// TODO(rFindley): clean this up so that under is the only function mutating
75-
// named types.
68+
// Ensure that typ is always expanded and sanity-checked.
7669
if check != nil {
77-
check.later(func() {
78-
switch typ.under().(type) {
79-
case *Named:
80-
panic("unexpanded underlying type")
81-
}
82-
typ.check = nil
83-
})
70+
check.defTypes = append(check.defTypes, typ)
8471
}
8572
return typ
8673
}
@@ -241,6 +228,12 @@ func expandNamed(ctxt *Context, n *Named, instPos token.Pos) (tparams *TypeParam
241228

242229
check := n.check
243230

231+
if _, unexpanded := n.orig.underlying.(*Named); unexpanded {
232+
// We should only get an unexpanded underlying here during type checking
233+
// (for example, in recursive type declarations).
234+
assert(check != nil)
235+
}
236+
244237
// Mismatching arg and tparam length may be checked elsewhere.
245238
if n.orig.tparams.Len() == n.targs.Len() {
246239
// We must always have a context, to avoid infinite recursion.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
// Copyright 2021 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 p
6+
7+
import "unsafe"
8+
9+
// The actual example from the issue.
10+
type List[P any] struct{}
11+
12+
func (_ List[P]) m() (_ List[List[P]]) { return }
13+
14+
// Other types of recursion through methods.
15+
type R[P any] int
16+
17+
func (*R[R /* ERROR must be an identifier */ [int]]) m0() {}
18+
func (R[P]) m1(R[R[P]]) {}
19+
func (R[P]) m2(R[*P]) {}
20+
func (R[P]) m3([unsafe.Sizeof(new(R[P]))]int) {}
21+
func (R[P]) m4([unsafe.Sizeof(new(R[R[P]]))]int) {}
22+
23+
// Mutual recursion
24+
type M[P any] int
25+
26+
func (R[P]) m5(M[M[P]]) {}
27+
func (M[P]) m(R[R[P]]) {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// Copyright 2021 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 p
6+
7+
type Fooer interface {
8+
Foo()
9+
}
10+
11+
type Fooable[F Fooer] struct {
12+
ptr F
13+
}
14+
15+
func (f *Fooable[F]) Adapter() *Fooable[*FooerImpl[F]] {
16+
return &Fooable[*FooerImpl[F]]{&FooerImpl[F]{}}
17+
}
18+
19+
type FooerImpl[F Fooer] struct {
20+
}
21+
22+
func (fi *FooerImpl[F]) Foo() {}

0 commit comments

Comments
 (0)