[dev.typeparams] go/types: backport lazy loading changes from CL 336252

When CL 336252 was created (itself a port of CL 335929), types2
tests revealed that lazy expansion of instances was not behaving
correctly with respect to lazy loading of Named types.

This CL ports the fixes from CL 336252 back to go/types.

Change-Id: Iffc6c84a708449633153b800dfb98ff57402893c
Reviewed-on: https://go-review.googlesource.com/c/go/+/338369
Trust: Robert Findley <rfindley@google.com>
Trust: Robert Griesemer <gri@golang.org>
Run-TryBot: Robert Findley <rfindley@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Robert Griesemer <gri@golang.org>
This commit is contained in:
Rob Findley 2021-07-29 16:39:49 -04:00 committed by Robert Findley
parent 27283d208f
commit 4480e3b11a
9 changed files with 61 additions and 43 deletions

View File

@ -317,7 +317,7 @@ func (check *Checker) validType(typ Type, path []Object) typeInfo {
} }
case *Named: case *Named:
t.complete() t.expand()
// don't touch the type if it is from a different package or the Universe scope // don't touch the type if it is from a different package or the Universe scope
// (doing so would lead to a race condition - was issue #35049) // (doing so would lead to a race condition - was issue #35049)
if t.obj.pkg != check.pkg { if t.obj.pkg != check.pkg {
@ -747,7 +747,7 @@ func (check *Checker) collectMethods(obj *TypeName) {
} }
if base != nil { if base != nil {
base.expand() // TODO(mdempsky): Probably unnecessary. base.load() // TODO(mdempsky): Probably unnecessary.
base.methods = append(base.methods, m) base.methods = append(base.methods, m)
} }
} }

View File

@ -8,34 +8,37 @@ package types
import "go/token" import "go/token"
// instance holds a Checker along with syntactic information // instance holds position information for use in lazy instantiation.
// information, for use in lazy instantiation. //
// TODO(rfindley): come up with a better name for this type, now that its usage
// has changed.
type instance struct { type instance struct {
check *Checker
pos token.Pos // position of type instantiation; for error reporting only pos token.Pos // position of type instantiation; for error reporting only
posList []token.Pos // position of each targ; for error reporting only posList []token.Pos // position of each targ; for error reporting only
} }
// complete ensures that the underlying type of n is instantiated. // expand ensures that the underlying type of n is instantiated.
// The underlying type will be Typ[Invalid] if there was an error. // The underlying type will be Typ[Invalid] if there was an error.
// TODO(rfindley): expand would be a better name for this method, but conflicts // TODO(rfindley): expand would be a better name for this method, but conflicts
// with the existing concept of lazy expansion. Need to reconcile this. // with the existing concept of lazy expansion. Need to reconcile this.
func (n *Named) complete() { func (n *Named) expand() {
if n.instance != nil && len(n.targs) > 0 && n.underlying == nil { if n.instance != nil {
check := n.instance.check // n must be loaded before instantiation, in order to have accurate
inst := check.instantiate(n.instance.pos, n.orig.underlying, n.TParams().list(), n.targs, n.instance.posList) // tparams. This is done implicitly by the call to n.TParams, but making it
// explicit is harmless: load is idempotent.
n.load()
inst := n.check.instantiate(n.instance.pos, n.orig.underlying, n.TParams().list(), n.targs, n.instance.posList)
n.underlying = inst n.underlying = inst
n.fromRHS = inst n.fromRHS = inst
n.methods = n.orig.methods n.instance = nil
} }
} }
// expand expands a type instance into its instantiated // expand expands uninstantiated named types and leaves all other types alone.
// type and leaves all other types alone. expand does // expand does not recurse.
// not recurse.
func expand(typ Type) Type { func expand(typ Type) Type {
if t, _ := typ.(*Named); t != nil { if t, _ := typ.(*Named); t != nil {
t.complete() t.expand()
} }
return typ return typ
} }

View File

@ -105,7 +105,9 @@ func (check *Checker) instantiate(pos token.Pos, typ Type, tparams []*TypeName,
// instantiating the type until needed. typ must be a *Named // instantiating the type until needed. typ must be a *Named
// type. // type.
func (check *Checker) InstantiateLazy(pos token.Pos, typ Type, targs []Type, posList []token.Pos, verify bool) Type { func (check *Checker) InstantiateLazy(pos token.Pos, typ Type, targs []Type, posList []token.Pos, verify bool) Type {
base := asNamed(typ) // Don't use asNamed here: we don't want to expand the base during lazy
// instantiation.
base := typ.(*Named)
if base == nil { if base == nil {
panic(fmt.Sprintf("%v: cannot instantiate %v", pos, typ)) panic(fmt.Sprintf("%v: cannot instantiate %v", pos, typ))
} }
@ -116,15 +118,18 @@ func (check *Checker) InstantiateLazy(pos token.Pos, typ Type, targs []Type, pos
} }
h := instantiatedHash(base, targs) h := instantiatedHash(base, targs)
if check != nil { if check != nil {
// typ may already have been instantiated with identical type arguments. In
// that case, re-use the existing instance.
if named := check.typMap[h]; named != nil { if named := check.typMap[h]; named != nil {
return named return named
} }
} }
tname := NewTypeName(pos, base.obj.pkg, base.obj.name, nil) tname := NewTypeName(pos, base.obj.pkg, base.obj.name, nil)
named := check.newNamed(tname, base, nil, base.TParams(), base.methods) // methods are instantiated lazily named := check.newNamed(tname, base, nil, nil, nil) // methods and tparams are set when named is loaded.
named.targs = targs named.targs = targs
named.instance = &instance{check, pos, posList} named.instance = &instance{pos, posList}
if check != nil { if check != nil {
check.typMap[h] = named check.typMap[h] = named
} }

View File

@ -121,7 +121,7 @@ func lookupFieldOrMethod(T Type, addressable bool, pkg *Package, name string) (o
seen[named] = true seen[named] = true
// look for a matching attached method // look for a matching attached method
named.expand() named.load()
if i, m := lookupMethod(named.methods, pkg, name); m != nil { if i, m := lookupMethod(named.methods, pkg, name); m != nil {
// potential match // potential match
// caution: method may not have a proper signature yet // caution: method may not have a proper signature yet

View File

@ -10,12 +10,13 @@ import "sync"
// A Named represents a named (defined) type. // A Named represents a named (defined) type.
type Named struct { type Named struct {
instance *instance // syntactic information for lazy instantiation check *Checker
info typeInfo // for cycle detection info typeInfo // for cycle detection
obj *TypeName // corresponding declared object obj *TypeName // corresponding declared object
orig *Named // original, uninstantiated type orig *Named // original, uninstantiated type
fromRHS Type // type (on RHS of declaration) this *Named type is derived of (for cycle reporting) fromRHS Type // type (on RHS of declaration) this *Named type is derived of (for cycle reporting)
underlying Type // possibly a *Named during setup; never a *Named once set up completely underlying Type // possibly a *Named during setup; never a *Named once set up completely
instance *instance // syntactic information for lazy instantiation
tparams *TypeParams // type parameters, or nil tparams *TypeParams // type parameters, or nil
targs []Type // type arguments (after instantiation), or nil targs []Type // type arguments (after instantiation), or nil
methods []*Func // methods declared for this type (not the method set of this type); signatures are type-checked lazily methods []*Func // methods declared for this type (not the method set of this type); signatures are type-checked lazily
@ -34,7 +35,19 @@ func NewNamed(obj *TypeName, underlying Type, methods []*Func) *Named {
return (*Checker)(nil).newNamed(obj, nil, underlying, nil, methods) return (*Checker)(nil).newNamed(obj, nil, underlying, nil, methods)
} }
func (t *Named) expand() *Named { func (t *Named) load() *Named {
// If t is an instantiated type, it derives its methods and tparams from its
// base type. Since we expect type parameters and methods to be set after a
// call to load, we must load the base and copy here.
//
// underlying is set when t is expanded.
//
// By convention, a type instance is loaded iff its tparams are set.
if len(t.targs) > 0 && t.tparams == nil {
t.orig.load()
t.tparams = t.orig.tparams
t.methods = t.orig.methods
}
if t.resolve == nil { if t.resolve == nil {
return t return t
} }
@ -65,13 +78,7 @@ func (t *Named) expand() *Named {
// newNamed is like NewNamed but with a *Checker receiver and additional orig argument. // newNamed is like NewNamed but with a *Checker receiver and additional orig argument.
func (check *Checker) newNamed(obj *TypeName, orig *Named, underlying Type, tparams *TypeParams, methods []*Func) *Named { func (check *Checker) newNamed(obj *TypeName, orig *Named, underlying Type, tparams *TypeParams, methods []*Func) *Named {
var inst *instance typ := &Named{check: check, obj: obj, orig: orig, fromRHS: underlying, underlying: underlying, tparams: tparams, methods: methods}
if check != nil {
inst = &instance{
check: check,
}
}
typ := &Named{instance: inst, obj: obj, orig: orig, fromRHS: underlying, underlying: underlying, tparams: tparams, methods: methods}
if typ.orig == nil { if typ.orig == nil {
typ.orig = typ typ.orig = typ
} }
@ -92,7 +99,7 @@ func (check *Checker) newNamed(obj *TypeName, orig *Named, underlying Type, tpar
case *Named: case *Named:
panic("internal error: unexpanded underlying type") panic("internal error: unexpanded underlying type")
} }
typ.instance = nil typ.check = nil
}) })
} }
return typ return typ
@ -110,10 +117,10 @@ func (t *Named) _Orig() *Named { return t.orig }
// TParams returns the type parameters of the named type t, or nil. // TParams returns the type parameters of the named type t, or nil.
// The result is non-nil for an (originally) parameterized type even if it is instantiated. // The result is non-nil for an (originally) parameterized type even if it is instantiated.
func (t *Named) TParams() *TypeParams { return t.expand().tparams } func (t *Named) TParams() *TypeParams { return t.load().tparams }
// SetTParams sets the type parameters of the named type t. // SetTParams sets the type parameters of the named type t.
func (t *Named) SetTParams(tparams []*TypeName) { t.expand().tparams = bindTParams(tparams) } func (t *Named) SetTParams(tparams []*TypeName) { t.load().tparams = bindTParams(tparams) }
// TArgs returns the type arguments after instantiation of the named type t, or nil if not instantiated. // TArgs returns the type arguments after instantiation of the named type t, or nil if not instantiated.
func (t *Named) TArgs() []Type { return t.targs } func (t *Named) TArgs() []Type { return t.targs }
@ -122,10 +129,10 @@ func (t *Named) TArgs() []Type { return t.targs }
func (t *Named) SetTArgs(args []Type) { t.targs = args } func (t *Named) SetTArgs(args []Type) { t.targs = args }
// NumMethods returns the number of explicit methods whose receiver is named type t. // NumMethods returns the number of explicit methods whose receiver is named type t.
func (t *Named) NumMethods() int { return len(t.expand().methods) } func (t *Named) NumMethods() int { return len(t.load().methods) }
// Method returns the i'th method of named type t for 0 <= i < t.NumMethods(). // Method returns the i'th method of named type t for 0 <= i < t.NumMethods().
func (t *Named) Method(i int) *Func { return t.expand().methods[i] } func (t *Named) Method(i int) *Func { return t.load().methods[i] }
// SetUnderlying sets the underlying type and marks t as complete. // SetUnderlying sets the underlying type and marks t as complete.
func (t *Named) SetUnderlying(underlying Type) { func (t *Named) SetUnderlying(underlying Type) {
@ -135,18 +142,18 @@ func (t *Named) SetUnderlying(underlying Type) {
if _, ok := underlying.(*Named); ok { if _, ok := underlying.(*Named); ok {
panic("types.Named.SetUnderlying: underlying type must not be *Named") panic("types.Named.SetUnderlying: underlying type must not be *Named")
} }
t.expand().underlying = underlying t.load().underlying = underlying
} }
// AddMethod adds method m unless it is already in the method list. // AddMethod adds method m unless it is already in the method list.
func (t *Named) AddMethod(m *Func) { func (t *Named) AddMethod(m *Func) {
t.expand() t.load()
if i, _ := lookupMethod(t.methods, m.pkg, m.name); i < 0 { if i, _ := lookupMethod(t.methods, m.pkg, m.name); i < 0 {
t.methods = append(t.methods, m) t.methods = append(t.methods, m)
} }
} }
func (t *Named) Underlying() Type { return t.expand().underlying } func (t *Named) Underlying() Type { return t.load().underlying }
func (t *Named) String() string { return TypeString(t, nil) } func (t *Named) String() string { return TypeString(t, nil) }
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
@ -159,7 +166,7 @@ func (t *Named) String() string { return TypeString(t, nil) }
// is detected, the result is Typ[Invalid]. If a cycle is detected and // is detected, the result is Typ[Invalid]. If a cycle is detected and
// n0.check != nil, the cycle is reported. // n0.check != nil, the cycle is reported.
func (n0 *Named) under() Type { func (n0 *Named) under() Type {
n0.complete() n0.expand()
u := n0.Underlying() u := n0.Underlying()
@ -180,13 +187,13 @@ func (n0 *Named) under() Type {
// handled below // handled below
} }
if n0.instance == nil || n0.instance.check == nil { if n0.check == nil {
panic("internal error: Named.check == nil but type is incomplete") panic("internal error: Named.check == nil but type is incomplete")
} }
// Invariant: after this point n0 as well as any named types in its // Invariant: after this point n0 as well as any named types in its
// underlying chain should be set up when this function exits. // underlying chain should be set up when this function exits.
check := n0.instance.check check := n0.check
// If we can't expand u at this point, it is invalid. // If we can't expand u at this point, it is invalid.
n := asNamed(u) n := asNamed(u)
@ -207,7 +214,7 @@ func (n0 *Named) under() Type {
var n1 *Named var n1 *Named
switch u1 := u.(type) { switch u1 := u.(type) {
case *Named: case *Named:
u1.complete() u1.expand()
n1 = u1 n1 = u1
} }
if n1 == nil { if n1 == nil {

View File

@ -21,7 +21,7 @@ func isNamed(typ Type) bool {
func isGeneric(typ Type) bool { func isGeneric(typ Type) bool {
// A parameterized type is only instantiated if it doesn't have an instantiation already. // A parameterized type is only instantiated if it doesn't have an instantiation already.
named, _ := typ.(*Named) named, _ := typ.(*Named)
return named != nil && named.obj != nil && named.TParams() != nil && named.targs == nil return named != nil && named.obj != nil && named.targs == nil && named.TParams() != nil
} }
func is(typ Type, what BasicInfo) bool { func is(typ Type, what BasicInfo) bool {

View File

@ -30,7 +30,7 @@ func TestSizeof(t *testing.T) {
{Interface{}, 40, 80}, {Interface{}, 40, 80},
{Map{}, 16, 32}, {Map{}, 16, 32},
{Chan{}, 12, 24}, {Chan{}, 12, 24},
{Named{}, 76, 144}, {Named{}, 80, 152},
{TypeParam{}, 28, 48}, {TypeParam{}, 28, 48},
{top{}, 0, 0}, {top{}, 0, 0},

View File

@ -244,7 +244,7 @@ func (subst *subster) typ(typ Type) Type {
named := subst.check.newNamed(tname, t, t.Underlying(), t.TParams(), t.methods) // method signatures are updated lazily named := subst.check.newNamed(tname, t, t.Underlying(), t.TParams(), t.methods) // method signatures are updated lazily
named.targs = newTargs named.targs = newTargs
subst.typMap[h] = named subst.typMap[h] = named
t.complete() // must happen after typMap update to avoid infinite recursion t.expand() // must happen after typMap update to avoid infinite recursion
// do the substitution // do the substitution
dump(">>> subst %s with %s (new: %s)", t.underlying, subst.smap, newTargs) dump(">>> subst %s with %s (new: %s)", t.underlying, subst.smap, newTargs)

View File

@ -270,6 +270,9 @@ func writeType(buf *bytes.Buffer, typ Type, qf Qualifier, visited []Type) {
} }
case *Named: case *Named:
if t.instance != nil {
buf.WriteByte(instanceMarker)
}
writeTypeName(buf, t.obj, qf) writeTypeName(buf, t.obj, qf)
if t.targs != nil { if t.targs != nil {
// instantiated type // instantiated type