From 02e69cfa9695f17902ff1806205c26a0d02a684f Mon Sep 17 00:00:00 2001 From: Robert Findley Date: Sat, 7 May 2022 19:26:35 -0400 Subject: [PATCH] go/types, types2: store Named instance information separately Separate instance information into an instance struct, to reduce memory footprint for non-instance Named types. This may induce a sense of deja-vu: we had a similar construct in the past that was removed as unnecessary. With additional new fields being added that only apply to instances, having a separate struct makes sense again. Updates #52728 Change-Id: I0bb5982d71c27e6b574bfb4f7b886a6aeb9c5390 Reviewed-on: https://go-review.googlesource.com/c/go/+/404884 Reviewed-by: Robert Griesemer Run-TryBot: Robert Findley TryBot-Result: Gopher Robot --- src/cmd/compile/internal/types2/decl.go | 4 +- src/cmd/compile/internal/types2/infer.go | 2 +- .../compile/internal/types2/instantiate.go | 5 +- src/cmd/compile/internal/types2/named.go | 131 +++++++++++------- src/cmd/compile/internal/types2/predicates.go | 4 +- .../compile/internal/types2/sizeof_test.go | 2 +- src/cmd/compile/internal/types2/subst.go | 25 ++-- src/cmd/compile/internal/types2/typestring.go | 4 +- src/cmd/compile/internal/types2/typexpr.go | 6 +- src/cmd/compile/internal/types2/unify.go | 4 +- src/cmd/compile/internal/types2/validtype.go | 2 +- src/go/types/decl.go | 4 +- src/go/types/infer.go | 2 +- src/go/types/instantiate.go | 5 +- src/go/types/named.go | 129 ++++++++++------- src/go/types/predicates.go | 4 +- src/go/types/sizeof_test.go | 2 +- src/go/types/subst.go | 25 ++-- src/go/types/typestring.go | 4 +- src/go/types/typexpr.go | 6 +- src/go/types/unify.go | 4 +- src/go/types/validtype.go | 2 +- 22 files changed, 222 insertions(+), 154 deletions(-) diff --git a/src/cmd/compile/internal/types2/decl.go b/src/cmd/compile/internal/types2/decl.go index a5d29765c65d1..0bc5f9f3e1cc0 100644 --- a/src/cmd/compile/internal/types2/decl.go +++ b/src/cmd/compile/internal/types2/decl.go @@ -508,7 +508,7 @@ func (check *Checker) typeDecl(obj *TypeName, tdecl *syntax.TypeDecl, def *Named } // type definition or generic type declaration - named := check.newNamed(obj, nil, nil, nil) + named := check.newNamed(obj, nil, nil) def.setUnderlying(named) if tdecl.TParamList != nil { @@ -635,7 +635,7 @@ func (check *Checker) collectMethods(obj *TypeName) { // and field names must be distinct." base, _ := obj.typ.(*Named) // shouldn't fail but be conservative if base != nil { - assert(base.targs.Len() == 0) // collectMethods should not be called on an instantiated type + assert(base.TypeArgs().Len() == 0) // collectMethods should not be called on an instantiated type // See issue #52529: we must delay the expansion of underlying here, as // base may not be fully set-up. diff --git a/src/cmd/compile/internal/types2/infer.go b/src/cmd/compile/internal/types2/infer.go index 9e77d67a7de2e..8ab568be59610 100644 --- a/src/cmd/compile/internal/types2/infer.go +++ b/src/cmd/compile/internal/types2/infer.go @@ -434,7 +434,7 @@ func (w *tpWalker) isParameterized(typ Type) (res bool) { return w.isParameterized(t.elem) case *Named: - return w.isParameterizedTypeList(t.targs.list()) + return w.isParameterizedTypeList(t.TypeArgs().list()) case *TypeParam: // t must be one of w.tparams diff --git a/src/cmd/compile/internal/types2/instantiate.go b/src/cmd/compile/internal/types2/instantiate.go index 44ed0319c822e..ed6206e150fe0 100644 --- a/src/cmd/compile/internal/types2/instantiate.go +++ b/src/cmd/compile/internal/types2/instantiate.go @@ -76,10 +76,7 @@ func (check *Checker) instance(pos syntax.Pos, orig Type, targs []Type, ctxt *Co switch orig := orig.(type) { case *Named: - tname := NewTypeName(pos, orig.obj.pkg, orig.obj.name, nil) - named := check.newNamed(tname, orig, nil, nil) // underlying, tparams, and methods are set when named is resolved - named.targs = newTypeList(targs) - res = named + res = check.newNamedInstance(pos, orig, targs) case *Signature: tparams := orig.TypeParams() diff --git a/src/cmd/compile/internal/types2/named.go b/src/cmd/compile/internal/types2/named.go index 77655bc821b0a..133fb6fa88e0c 100644 --- a/src/cmd/compile/internal/types2/named.go +++ b/src/cmd/compile/internal/types2/named.go @@ -5,6 +5,7 @@ package types2 import ( + "cmd/compile/internal/syntax" "sync" "sync/atomic" ) @@ -83,14 +84,15 @@ import ( type Named struct { check *Checker // non-nil during type-checking; nil otherwise obj *TypeName // corresponding declared object for declared types; see above for instantiated types - orig *Named // origin type for instantiated types, this type for declared types - targs *TypeList // type arguments (after instantiation), or nil // fromRHS holds the type (on RHS of declaration) this *Named type is derived // from (for cycle reporting). Only used by validType, and therefore does not // require synchronization. fromRHS Type + // information for instantiated types; nil otherwise + inst *instance + mu sync.Mutex // guards all fields below state_ uint32 // the current state of this type; must only be accessed atomically underlying Type // possibly a *Named during setup; never a *Named once set up completely @@ -102,13 +104,19 @@ type Named struct { // instantiated types, methods are individually expanded when they are first // accessed. methods []*Func - // number of expanded methods (only valid for instantiated named types) - expandedMethods int // expandedMethods <= len(orig.methods) // loader may be provided to lazily load type parameters, underlying type, and methods. loader func(*Named) (tparams []*TypeParam, underlying Type, methods []*Func) } +// instance holds information that is only necessary for instantiated named +// types. +type instance struct { + orig *Named // original, uninstantiated type + targs *TypeList // type arguments + expandedMethods int // number of expanded methods; expandedMethods <= len(orig.methods) +} + // namedState represents the possible states that a named type may assume. type namedState uint32 @@ -125,7 +133,7 @@ func NewNamed(obj *TypeName, underlying Type, methods []*Func) *Named { if _, ok := underlying.(*Named); ok { panic("underlying type must not be *Named") } - return (*Checker)(nil).newNamed(obj, nil, underlying, methods) + return (*Checker)(nil).newNamed(obj, underlying, methods) } // resolve resolves the type parameters, methods, and underlying type of n. @@ -149,19 +157,20 @@ func (n *Named) resolve(ctxt *Context) *Named { return n } - if n.TypeArgs().Len() > 0 { + if n.inst != nil { assert(n.underlying == nil) // n is an unresolved instance assert(n.loader == nil) // instances are created by instantiation, in which case n.loader is nil - n.orig.resolve(ctxt) + orig := n.inst.orig + orig.resolve(ctxt) underlying := n.expandUnderlying(ctxt) - n.tparams = n.orig.tparams + n.tparams = orig.tparams n.underlying = underlying - n.fromRHS = n.orig.fromRHS // for cycle detection + n.fromRHS = orig.fromRHS // for cycle detection - if len(n.orig.methods) == 0 { - n.setState(complete) + if len(orig.methods) == 0 { + n.setState(complete) // nothing further to do } else { n.setState(resolved) } @@ -204,11 +213,8 @@ func (n *Named) setState(state namedState) { } // newNamed is like NewNamed but with a *Checker receiver and additional orig argument. -func (check *Checker) newNamed(obj *TypeName, orig *Named, underlying Type, methods []*Func) *Named { - typ := &Named{check: check, obj: obj, orig: orig, fromRHS: underlying, underlying: underlying, methods: methods} - if typ.orig == nil { - typ.orig = typ - } +func (check *Checker) newNamed(obj *TypeName, underlying Type, methods []*Func) *Named { + typ := &Named{check: check, obj: obj, fromRHS: underlying, underlying: underlying, methods: methods} if obj.typ == nil { obj.typ = typ } @@ -219,8 +225,22 @@ func (check *Checker) newNamed(obj *TypeName, orig *Named, underlying Type, meth return typ } +func (check *Checker) newNamedInstance(pos syntax.Pos, orig *Named, targs []Type) *Named { + assert(len(targs) > 0) + + obj := NewTypeName(pos, orig.obj.pkg, orig.obj.name, nil) + inst := &instance{orig: orig, targs: newTypeList(targs)} + typ := &Named{check: check, obj: obj, inst: inst} + obj.typ = typ + // Ensure that typ is always expanded and sanity-checked. + if check != nil { + check.needsCleanup(typ) + } + return typ +} + func (t *Named) cleanup() { - assert(t.orig.orig == t.orig) + assert(t.inst == nil || t.inst.orig.inst == nil) // Ensure that every defined type created in the course of type-checking has // either non-*Named underlying type, or is unexpanded. // @@ -242,14 +262,21 @@ func (t *Named) cleanup() { // Obj returns the type name for the declaration defining the named type t. For // instantiated types, this is same as the type name of the origin type. -func (t *Named) Obj() *TypeName { return t.orig.obj } // for non-instances this is the same as t.obj +func (t *Named) Obj() *TypeName { + if t.inst == nil { + return t.obj + } + return t.inst.orig.obj +} // Origin returns the generic type from which the named type t is // instantiated. If t is not an instantiated type, the result is t. -func (t *Named) Origin() *Named { return t.orig } - -// TODO(gri) Come up with a better representation and API to distinguish -// between parameterized instantiated and non-instantiated types. +func (t *Named) Origin() *Named { + if t.inst == nil { + return t + } + return t.inst.orig +} // TypeParams returns the type parameters of the named type t, or nil. // The result is non-nil for an (originally) generic type even if it is instantiated. @@ -258,19 +285,26 @@ func (t *Named) TypeParams() *TypeParamList { return t.resolve(nil).tparams } // SetTypeParams sets the type parameters of the named type t. // t must not have type arguments. func (t *Named) SetTypeParams(tparams []*TypeParam) { - assert(t.targs.Len() == 0) + assert(t.inst == nil) t.resolve(nil).tparams = bindTParams(tparams) } // TypeArgs returns the type arguments used to instantiate the named type t. -func (t *Named) TypeArgs() *TypeList { return t.targs } +func (t *Named) TypeArgs() *TypeList { + if t.inst == nil { + return nil + } + return t.inst.targs +} // NumMethods returns the number of explicit methods defined for t. // // For an ordinary or instantiated type t, the receiver base type of these // methods will be the named type t. For an uninstantiated generic type t, each // method receiver will be instantiated with its receiver type parameters. -func (t *Named) NumMethods() int { return len(t.orig.resolve(nil).methods) } +func (t *Named) NumMethods() int { + return len(t.Origin().resolve(nil).methods) +} // Method returns the i'th method of named type t for 0 <= i < t.NumMethods(). func (t *Named) Method(i int) *Func { @@ -280,23 +314,24 @@ func (t *Named) Method(i int) *Func { return t.methods[i] } - assert(t.TypeArgs().Len() > 0) // only instances should have incomplete methods + assert(t.inst != nil) // only instances should have incomplete methods + orig := t.inst.orig t.mu.Lock() defer t.mu.Unlock() - if len(t.methods) != len(t.orig.methods) { + if len(t.methods) != len(orig.methods) { assert(len(t.methods) == 0) - t.methods = make([]*Func, len(t.orig.methods)) + t.methods = make([]*Func, len(orig.methods)) } if t.methods[i] == nil { t.methods[i] = t.expandMethod(i) - t.expandedMethods++ + t.inst.expandedMethods++ // Check if we've created all methods at this point. If we have, mark the // type as fully expanded. - if t.expandedMethods == len(t.orig.methods) { + if t.inst.expandedMethods == len(orig.methods) { t.setState(complete) } } @@ -307,11 +342,9 @@ func (t *Named) Method(i int) *Func { // expandMethod substitutes type arguments in the i'th method for an // instantiated receiver. func (t *Named) expandMethod(i int) *Func { - assert(t.TypeArgs().Len() > 0) // t must be an instance - // t.orig.methods is not lazy. origm is the method instantiated with its // receiver type parameters (the "origin" method). - origm := t.orig.Method(i) + origm := t.inst.orig.Method(i) assert(origm != nil) check := t.check @@ -338,9 +371,9 @@ func (t *Named) expandMethod(i int) *Func { // We can only substitute if we have a correspondence between type arguments // and type parameters. This check is necessary in the presence of invalid // code. - if origSig.RecvTypeParams().Len() == t.targs.Len() { + if origSig.RecvTypeParams().Len() == t.inst.targs.Len() { ctxt := check.bestContext(nil) - smap := makeSubstMap(origSig.RecvTypeParams().list(), t.targs.list()) + smap := makeSubstMap(origSig.RecvTypeParams().list(), t.inst.targs.list()) sig = check.subst(origm.pos, origSig, smap, ctxt).(*Signature) } @@ -365,7 +398,7 @@ func (t *Named) expandMethod(i int) *Func { // SetUnderlying sets the underlying type and marks t as complete. // t must not have type arguments. func (t *Named) SetUnderlying(underlying Type) { - assert(t.targs.Len() == 0) + assert(t.inst == nil) if underlying == nil { panic("underlying type must not be nil") } @@ -381,7 +414,7 @@ func (t *Named) SetUnderlying(underlying Type) { // AddMethod adds method m unless it is already in the method list. // t must not have type arguments. func (t *Named) AddMethod(m *Func) { - assert(t.targs.Len() == 0) + assert(t.inst == nil) t.resolve(nil) if i, _ := lookupMethod(t.methods, m.pkg, m.name, false); i < 0 { t.methods = append(t.methods, m) @@ -493,7 +526,7 @@ func (n *Named) lookupMethod(pkg *Package, name string, foldCase bool) (int, *Fu // If n is an instance, we may not have yet instantiated all of its methods. // Look up the method index in orig, and only instantiate method at the // matching index (if any). - i, _ := lookupMethod(n.orig.methods, pkg, name, foldCase) + i, _ := lookupMethod(n.Origin().methods, pkg, name, foldCase) if i < 0 { return -1, nil } @@ -531,36 +564,39 @@ func (n *Named) expandUnderlying(ctxt *Context) Type { }() } - assert(n.orig.underlying != nil) + assert(n.inst.orig.underlying != nil) - if _, unexpanded := n.orig.underlying.(*Named); unexpanded { + orig := n.inst.orig + targs := n.inst.targs + + if _, unexpanded := orig.underlying.(*Named); unexpanded { // We should only get a Named underlying type here during type checking // (for example, in recursive type declarations). assert(check != nil) } - if n.orig.tparams.Len() != n.targs.Len() { + if orig.tparams.Len() != targs.Len() { // Mismatching arg and tparam length may be checked elsewhere. return Typ[Invalid] } // We must always have a context, to avoid infinite recursion. ctxt = check.bestContext(ctxt) - h := ctxt.instanceHash(n.orig, n.targs.list()) + h := ctxt.instanceHash(orig, targs.list()) // ensure that an instance is recorded for h to avoid infinite recursion. - ctxt.update(h, n.orig, n.TypeArgs().list(), n) + ctxt.update(h, orig, targs.list(), n) - smap := makeSubstMap(n.orig.tparams.list(), n.targs.list()) - underlying := n.check.subst(n.obj.pos, n.orig.underlying, smap, ctxt) + smap := makeSubstMap(orig.tparams.list(), targs.list()) + underlying := n.check.subst(n.obj.pos, orig.underlying, smap, ctxt) // If the underlying type of n is an interface, we need to set the receiver // of its methods accurately -- we set the receiver of interface methods on // the RHS of a type declaration to the defined type. if iface, _ := underlying.(*Interface); iface != nil { - if methods, copied := replaceRecvType(iface.methods, n.orig, n); copied { + if methods, copied := replaceRecvType(iface.methods, orig, n); copied { // If the underlying type doesn't actually use type parameters, it's // possible that it wasn't substituted. In this case we need to create // a new *Interface before modifying receivers. - if iface == n.orig.underlying { + if iface == orig.underlying { old := iface iface = check.newInterface() iface.embeddeds = old.embeddeds @@ -571,6 +607,7 @@ func (n *Named) expandUnderlying(ctxt *Context) Type { iface.methods = methods } } + return underlying } diff --git a/src/cmd/compile/internal/types2/predicates.go b/src/cmd/compile/internal/types2/predicates.go index 6bce26137eb77..6b6c21c780195 100644 --- a/src/cmd/compile/internal/types2/predicates.go +++ b/src/cmd/compile/internal/types2/predicates.go @@ -102,7 +102,7 @@ func isTypeParam(t Type) bool { func isGeneric(t Type) bool { // A parameterized type is only generic if it doesn't have an instantiation already. named, _ := t.(*Named) - return named != nil && named.obj != nil && named.targs == nil && named.TypeParams() != nil + return named != nil && named.obj != nil && named.inst == nil && named.TypeParams().Len() > 0 } // Comparable reports whether values of type T are comparable. @@ -401,7 +401,7 @@ func identical(x, y Type, cmpTags bool, p *ifacePair) bool { if len(xargs) > 0 { // Instances are identical if their original type and type arguments // are identical. - if !Identical(x.orig, y.orig) { + if !Identical(x.Origin(), y.Origin()) { return false } for i, xa := range xargs { diff --git a/src/cmd/compile/internal/types2/sizeof_test.go b/src/cmd/compile/internal/types2/sizeof_test.go index 3f0bf8f3c50f0..17876d1f3c235 100644 --- a/src/cmd/compile/internal/types2/sizeof_test.go +++ b/src/cmd/compile/internal/types2/sizeof_test.go @@ -31,7 +31,7 @@ func TestSizeof(t *testing.T) { {Interface{}, 40, 80}, {Map{}, 16, 32}, {Chan{}, 12, 24}, - {Named{}, 68, 128}, + {Named{}, 60, 112}, {TypeParam{}, 28, 48}, {term{}, 12, 24}, diff --git a/src/cmd/compile/internal/types2/subst.go b/src/cmd/compile/internal/types2/subst.go index 6e41ebdf53907..9af1a71cfebcc 100644 --- a/src/cmd/compile/internal/types2/subst.go +++ b/src/cmd/compile/internal/types2/subst.go @@ -176,7 +176,7 @@ func (subst *subster) typ(typ Type) Type { // In this case the interface will not be substituted here, because its // method signatures do not depend on the type parameter P, but we still // need to create new interface methods to hold the instantiated - // receiver. This is handled by expandNamed. + // receiver. This is handled by Named.expandUnderlying. iface.methods, _ = replaceRecvType(methods, t, iface) return iface } @@ -207,19 +207,20 @@ func (subst *subster) typ(typ Type) Type { } } - // subst is called by expandNamed, so in this function we need to be + // subst is called during expansion, so in this function we need to be // careful not to call any methods that would cause t to be expanded: doing // so would result in deadlock. // - // So we call t.orig.TypeParams() rather than t.TypeParams() here and - // below. - if t.orig.TypeParams().Len() == 0 { + // So we call t.Origin().TypeParams() rather than t.TypeParams(). + orig := t.Origin() + n := orig.TypeParams().Len() + if n == 0 { dump(">>> %s is not parameterized", t) return t // type is not parameterized } var newTArgs []Type - if t.targs.Len() != t.orig.TypeParams().Len() { + if t.TypeArgs().Len() != n { return Typ[Invalid] // error reported elsewhere } @@ -228,14 +229,14 @@ func (subst *subster) typ(typ Type) Type { // For each (existing) type argument targ, determine if it needs // to be substituted; i.e., if it is or contains a type parameter // that has a type argument for it. - for i, targ := range t.targs.list() { + for i, targ := range t.TypeArgs().list() { dump(">>> %d targ = %s", i, targ) new_targ := subst.typ(targ) if new_targ != targ { dump(">>> substituted %d targ %s => %s", i, targ, new_targ) if newTArgs == nil { - newTArgs = make([]Type, t.orig.TypeParams().Len()) - copy(newTArgs, t.targs.list()) + newTArgs = make([]Type, n) + copy(newTArgs, t.TypeArgs().list()) } newTArgs[i] = new_targ } @@ -247,9 +248,9 @@ func (subst *subster) typ(typ Type) Type { } // before creating a new named type, check if we have this one already - h := subst.ctxt.instanceHash(t.orig, newTArgs) + h := subst.ctxt.instanceHash(orig, newTArgs) dump(">>> new type hash: %s", h) - if named := subst.ctxt.lookup(h, t.orig, newTArgs); named != nil { + if named := subst.ctxt.lookup(h, orig, newTArgs); named != nil { dump(">>> found %s", named) return named } @@ -258,7 +259,7 @@ func (subst *subster) typ(typ Type) Type { // recursion. The position used here is irrelevant because validation only // occurs on t (we don't call validType on named), but we use subst.pos to // help with debugging. - return subst.check.instance(subst.pos, t.orig, newTArgs, subst.ctxt) + return subst.check.instance(subst.pos, orig, newTArgs, subst.ctxt) // Note that if we were to expose substitution more generally (not just in // the context of a declaration), we'd have to substitute in diff --git a/src/cmd/compile/internal/types2/typestring.go b/src/cmd/compile/internal/types2/typestring.go index e0f36fcec47cf..c10c2d8973916 100644 --- a/src/cmd/compile/internal/types2/typestring.go +++ b/src/cmd/compile/internal/types2/typestring.go @@ -284,9 +284,9 @@ func (w *typeWriter) typ(typ Type) { w.string(strconv.Itoa(w.ctxt.getID(t))) } w.typeName(t.obj) // when hashing written for readability of the hash only - if t.targs != nil { + if t.inst != nil { // instantiated type - w.typeList(t.targs.list()) + w.typeList(t.inst.targs.list()) } else if w.ctxt == nil && t.TypeParams().Len() != 0 { // For type hashing, don't need to format the TypeParams // parameterized type w.tParamList(t.TypeParams().list()) diff --git a/src/cmd/compile/internal/types2/typexpr.go b/src/cmd/compile/internal/types2/typexpr.go index 020653332d180..ea13eb622d82e 100644 --- a/src/cmd/compile/internal/types2/typexpr.go +++ b/src/cmd/compile/internal/types2/typexpr.go @@ -444,8 +444,8 @@ func (check *Checker) instantiatedType(x syntax.Expr, xlist []syntax.Expr, def * // errors. check.recordInstance(x, inst.TypeArgs().list(), inst) - if check.validateTArgLen(x.Pos(), inst.TypeParams().Len(), inst.targs.Len()) { - if i, err := check.verify(x.Pos(), inst.TypeParams().list(), inst.targs.list()); err != nil { + if check.validateTArgLen(x.Pos(), inst.TypeParams().Len(), inst.TypeArgs().Len()) { + if i, err := check.verify(x.Pos(), inst.TypeParams().list(), inst.TypeArgs().list()); err != nil { // best position for error reporting pos := x.Pos() if i < len(xlist) { @@ -453,7 +453,7 @@ func (check *Checker) instantiatedType(x syntax.Expr, xlist []syntax.Expr, def * } check.softErrorf(pos, "%s", err) } else { - check.mono.recordInstance(check.pkg, x.Pos(), inst.TypeParams().list(), inst.targs.list(), xlist) + check.mono.recordInstance(check.pkg, x.Pos(), inst.TypeParams().list(), inst.TypeArgs().list(), xlist) } } diff --git a/src/cmd/compile/internal/types2/unify.go b/src/cmd/compile/internal/types2/unify.go index a7f68a05b1d28..7063789f3f0c0 100644 --- a/src/cmd/compile/internal/types2/unify.go +++ b/src/cmd/compile/internal/types2/unify.go @@ -546,8 +546,8 @@ func (u *unifier) nify(x, y Type, p *ifacePair) (result bool) { case *Named: // TODO(gri) This code differs now from the parallel code in Checker.identical. Investigate. if y, ok := y.(*Named); ok { - xargs := x.targs.list() - yargs := y.targs.list() + xargs := x.TypeArgs().list() + yargs := y.TypeArgs().list() if len(xargs) != len(yargs) { return false diff --git a/src/cmd/compile/internal/types2/validtype.go b/src/cmd/compile/internal/types2/validtype.go index d495c6788ee07..b69120481b181 100644 --- a/src/cmd/compile/internal/types2/validtype.go +++ b/src/cmd/compile/internal/types2/validtype.go @@ -72,7 +72,7 @@ func (check *Checker) validType0(typ Type, env *tparamEnv, path []Object) typeIn switch check.infoMap[t] { case unknown: check.infoMap[t] = marked - check.infoMap[t] = check.validType0(t.orig.fromRHS, env.push(t), append(path, t.obj)) + check.infoMap[t] = check.validType0(t.Origin().fromRHS, env.push(t), append(path, t.obj)) case marked: // We have seen type t before and thus must have a cycle. check.infoMap[t] = invalid diff --git a/src/go/types/decl.go b/src/go/types/decl.go index b5ff1214dd548..c176042852c63 100644 --- a/src/go/types/decl.go +++ b/src/go/types/decl.go @@ -565,7 +565,7 @@ func (check *Checker) typeDecl(obj *TypeName, tdecl *ast.TypeSpec, def *Named) { } // type definition or generic type declaration - named := check.newNamed(obj, nil, nil, nil) + named := check.newNamed(obj, nil, nil) def.setUnderlying(named) if tdecl.TypeParams != nil { @@ -711,7 +711,7 @@ func (check *Checker) collectMethods(obj *TypeName) { // and field names must be distinct." base, _ := obj.typ.(*Named) // shouldn't fail but be conservative if base != nil { - assert(base.targs.Len() == 0) // collectMethods should not be called on an instantiated type + assert(base.TypeArgs().Len() == 0) // collectMethods should not be called on an instantiated type // See issue #52529: we must delay the expansion of underlying here, as // base may not be fully set-up. diff --git a/src/go/types/infer.go b/src/go/types/infer.go index 031850b8dad4f..ebe6d8ced7f30 100644 --- a/src/go/types/infer.go +++ b/src/go/types/infer.go @@ -434,7 +434,7 @@ func (w *tpWalker) isParameterized(typ Type) (res bool) { return w.isParameterized(t.elem) case *Named: - return w.isParameterizedTypeList(t.targs.list()) + return w.isParameterizedTypeList(t.TypeArgs().list()) case *TypeParam: // t must be one of w.tparams diff --git a/src/go/types/instantiate.go b/src/go/types/instantiate.go index 8be0eab40762d..d420a615728e7 100644 --- a/src/go/types/instantiate.go +++ b/src/go/types/instantiate.go @@ -76,10 +76,7 @@ func (check *Checker) instance(pos token.Pos, orig Type, targs []Type, ctxt *Con switch orig := orig.(type) { case *Named: - tname := NewTypeName(pos, orig.obj.pkg, orig.obj.name, nil) - named := check.newNamed(tname, orig, nil, nil) // underlying, tparams, and methods are set when named is resolved - named.targs = newTypeList(targs) - res = named + res = check.newNamedInstance(pos, orig, targs) case *Signature: tparams := orig.TypeParams() diff --git a/src/go/types/named.go b/src/go/types/named.go index f1c5dd4f81963..71a26f96a16c7 100644 --- a/src/go/types/named.go +++ b/src/go/types/named.go @@ -5,6 +5,7 @@ package types import ( + "go/token" "sync" "sync/atomic" ) @@ -83,14 +84,15 @@ import ( type Named struct { check *Checker // non-nil during type-checking; nil otherwise obj *TypeName // corresponding declared object for declared types; see above for instantiated types - orig *Named // origin type for instantiated types, this type for declared types - targs *TypeList // type arguments (after instantiation), or nil // fromRHS holds the type (on RHS of declaration) this *Named type is derived // from (for cycle reporting). Only used by validType, and therefore does not // require synchronization. fromRHS Type + // information for instantiated types; nil otherwise + inst *instance + mu sync.Mutex // guards all fields below state_ uint32 // the current state of this type; must only be accessed atomically underlying Type // possibly a *Named during setup; never a *Named once set up completely @@ -102,13 +104,19 @@ type Named struct { // instantiated types, methods are individually expanded when they are first // accessed. methods []*Func - // number of expanded methods (only valid for instantiated named types) - expandedMethods int // expandedMethods <= len(orig.methods) // loader may be provided to lazily load type parameters, underlying type, and methods. loader func(*Named) (tparams []*TypeParam, underlying Type, methods []*Func) } +// instance holds information that is only necessary for instantiated named +// types. +type instance struct { + orig *Named // original, uninstantiated type + targs *TypeList // type arguments + expandedMethods int // number of expanded methods; expandedMethods <= len(orig.methods) +} + // namedState represents the possible states that a named type may assume. type namedState uint32 @@ -125,7 +133,7 @@ func NewNamed(obj *TypeName, underlying Type, methods []*Func) *Named { if _, ok := underlying.(*Named); ok { panic("underlying type must not be *Named") } - return (*Checker)(nil).newNamed(obj, nil, underlying, methods) + return (*Checker)(nil).newNamed(obj, underlying, methods) } // resolve resolves the type parameters, methods, and underlying type of n. @@ -149,19 +157,20 @@ func (n *Named) resolve(ctxt *Context) *Named { return n } - if n.TypeArgs().Len() > 0 { + if n.inst != nil { assert(n.underlying == nil) // n is an unresolved instance assert(n.loader == nil) // instances are created by instantiation, in which case n.loader is nil - n.orig.resolve(ctxt) + orig := n.inst.orig + orig.resolve(ctxt) underlying := n.expandUnderlying(ctxt) - n.tparams = n.orig.tparams + n.tparams = orig.tparams n.underlying = underlying - n.fromRHS = n.orig.fromRHS // for cycle detection + n.fromRHS = orig.fromRHS // for cycle detection - if len(n.orig.methods) == 0 { - n.setState(complete) + if len(orig.methods) == 0 { + n.setState(complete) // nothing further to do } else { n.setState(resolved) } @@ -204,11 +213,8 @@ func (n *Named) setState(state namedState) { } // newNamed is like NewNamed but with a *Checker receiver and additional orig argument. -func (check *Checker) newNamed(obj *TypeName, orig *Named, underlying Type, methods []*Func) *Named { - typ := &Named{check: check, obj: obj, orig: orig, fromRHS: underlying, underlying: underlying, methods: methods} - if typ.orig == nil { - typ.orig = typ - } +func (check *Checker) newNamed(obj *TypeName, underlying Type, methods []*Func) *Named { + typ := &Named{check: check, obj: obj, fromRHS: underlying, underlying: underlying, methods: methods} if obj.typ == nil { obj.typ = typ } @@ -219,8 +225,22 @@ func (check *Checker) newNamed(obj *TypeName, orig *Named, underlying Type, meth return typ } +func (check *Checker) newNamedInstance(pos token.Pos, orig *Named, targs []Type) *Named { + assert(len(targs) > 0) + + obj := NewTypeName(pos, orig.obj.pkg, orig.obj.name, nil) + inst := &instance{orig: orig, targs: newTypeList(targs)} + typ := &Named{check: check, obj: obj, inst: inst} + obj.typ = typ + // Ensure that typ is always expanded and sanity-checked. + if check != nil { + check.needsCleanup(typ) + } + return typ +} + func (t *Named) cleanup() { - assert(t.orig.orig == t.orig) + assert(t.inst == nil || t.inst.orig.inst == nil) // Ensure that every defined type created in the course of type-checking has // either non-*Named underlying type, or is unexpanded. // @@ -243,15 +263,20 @@ func (t *Named) cleanup() { // Obj returns the type name for the declaration defining the named type t. For // instantiated types, this is same as the type name of the origin type. func (t *Named) Obj() *TypeName { - return t.orig.obj // for non-instances this is the same as t.obj + if t.inst == nil { + return t.obj + } + return t.inst.orig.obj } // Origin returns the generic type from which the named type t is // instantiated. If t is not an instantiated type, the result is t. -func (t *Named) Origin() *Named { return t.orig } - -// TODO(gri) Come up with a better representation and API to distinguish -// between parameterized instantiated and non-instantiated types. +func (t *Named) Origin() *Named { + if t.inst == nil { + return t + } + return t.inst.orig +} // TypeParams returns the type parameters of the named type t, or nil. // The result is non-nil for an (originally) generic type even if it is instantiated. @@ -260,19 +285,26 @@ func (t *Named) TypeParams() *TypeParamList { return t.resolve(nil).tparams } // SetTypeParams sets the type parameters of the named type t. // t must not have type arguments. func (t *Named) SetTypeParams(tparams []*TypeParam) { - assert(t.targs.Len() == 0) + assert(t.inst == nil) t.resolve(nil).tparams = bindTParams(tparams) } // TypeArgs returns the type arguments used to instantiate the named type t. -func (t *Named) TypeArgs() *TypeList { return t.targs } +func (t *Named) TypeArgs() *TypeList { + if t.inst == nil { + return nil + } + return t.inst.targs +} // NumMethods returns the number of explicit methods defined for t. // // For an ordinary or instantiated type t, the receiver base type of these // methods will be the named type t. For an uninstantiated generic type t, each // method receiver will be instantiated with its receiver type parameters. -func (t *Named) NumMethods() int { return len(t.orig.resolve(nil).methods) } +func (t *Named) NumMethods() int { + return len(t.Origin().resolve(nil).methods) +} // Method returns the i'th method of named type t for 0 <= i < t.NumMethods(). func (t *Named) Method(i int) *Func { @@ -282,23 +314,24 @@ func (t *Named) Method(i int) *Func { return t.methods[i] } - assert(t.TypeArgs().Len() > 0) // only instances should have incomplete methods + assert(t.inst != nil) // only instances should have incomplete methods + orig := t.inst.orig t.mu.Lock() defer t.mu.Unlock() - if len(t.methods) != len(t.orig.methods) { + if len(t.methods) != len(orig.methods) { assert(len(t.methods) == 0) - t.methods = make([]*Func, len(t.orig.methods)) + t.methods = make([]*Func, len(orig.methods)) } if t.methods[i] == nil { t.methods[i] = t.expandMethod(i) - t.expandedMethods++ + t.inst.expandedMethods++ // Check if we've created all methods at this point. If we have, mark the // type as fully expanded. - if t.expandedMethods == len(t.orig.methods) { + if t.inst.expandedMethods == len(orig.methods) { t.setState(complete) } } @@ -309,11 +342,9 @@ func (t *Named) Method(i int) *Func { // expandMethod substitutes type arguments in the i'th method for an // instantiated receiver. func (t *Named) expandMethod(i int) *Func { - assert(t.TypeArgs().Len() > 0) // t must be an instance - // t.orig.methods is not lazy. origm is the method instantiated with its // receiver type parameters (the "origin" method). - origm := t.orig.Method(i) + origm := t.inst.orig.Method(i) assert(origm != nil) check := t.check @@ -340,9 +371,9 @@ func (t *Named) expandMethod(i int) *Func { // We can only substitute if we have a correspondence between type arguments // and type parameters. This check is necessary in the presence of invalid // code. - if origSig.RecvTypeParams().Len() == t.targs.Len() { + if origSig.RecvTypeParams().Len() == t.inst.targs.Len() { ctxt := check.bestContext(nil) - smap := makeSubstMap(origSig.RecvTypeParams().list(), t.targs.list()) + smap := makeSubstMap(origSig.RecvTypeParams().list(), t.inst.targs.list()) sig = check.subst(origm.pos, origSig, smap, ctxt).(*Signature) } @@ -367,7 +398,7 @@ func (t *Named) expandMethod(i int) *Func { // SetUnderlying sets the underlying type and marks t as complete. // t must not have type arguments. func (t *Named) SetUnderlying(underlying Type) { - assert(t.targs.Len() == 0) + assert(t.inst == nil) if underlying == nil { panic("underlying type must not be nil") } @@ -383,7 +414,7 @@ func (t *Named) SetUnderlying(underlying Type) { // AddMethod adds method m unless it is already in the method list. // t must not have type arguments. func (t *Named) AddMethod(m *Func) { - assert(t.targs.Len() == 0) + assert(t.inst == nil) t.resolve(nil) if i, _ := lookupMethod(t.methods, m.pkg, m.name, false); i < 0 { t.methods = append(t.methods, m) @@ -495,7 +526,7 @@ func (n *Named) lookupMethod(pkg *Package, name string, foldCase bool) (int, *Fu // If n is an instance, we may not have yet instantiated all of its methods. // Look up the method index in orig, and only instantiate method at the // matching index (if any). - i, _ := lookupMethod(n.orig.methods, pkg, name, foldCase) + i, _ := lookupMethod(n.Origin().methods, pkg, name, foldCase) if i < 0 { return -1, nil } @@ -533,36 +564,39 @@ func (n *Named) expandUnderlying(ctxt *Context) Type { }() } - assert(n.orig.underlying != nil) + assert(n.inst.orig.underlying != nil) - if _, unexpanded := n.orig.underlying.(*Named); unexpanded { + orig := n.inst.orig + targs := n.inst.targs + + if _, unexpanded := orig.underlying.(*Named); unexpanded { // We should only get a Named underlying type here during type checking // (for example, in recursive type declarations). assert(check != nil) } - if n.orig.tparams.Len() != n.targs.Len() { + if orig.tparams.Len() != targs.Len() { // Mismatching arg and tparam length may be checked elsewhere. return Typ[Invalid] } // We must always have a context, to avoid infinite recursion. ctxt = check.bestContext(ctxt) - h := ctxt.instanceHash(n.orig, n.targs.list()) + h := ctxt.instanceHash(orig, targs.list()) // ensure that an instance is recorded for h to avoid infinite recursion. - ctxt.update(h, n.orig, n.TypeArgs().list(), n) + ctxt.update(h, orig, targs.list(), n) - smap := makeSubstMap(n.orig.tparams.list(), n.targs.list()) - underlying := n.check.subst(n.obj.pos, n.orig.underlying, smap, ctxt) + smap := makeSubstMap(orig.tparams.list(), targs.list()) + underlying := n.check.subst(n.obj.pos, orig.underlying, smap, ctxt) // If the underlying type of n is an interface, we need to set the receiver // of its methods accurately -- we set the receiver of interface methods on // the RHS of a type declaration to the defined type. if iface, _ := underlying.(*Interface); iface != nil { - if methods, copied := replaceRecvType(iface.methods, n.orig, n); copied { + if methods, copied := replaceRecvType(iface.methods, orig, n); copied { // If the underlying type doesn't actually use type parameters, it's // possible that it wasn't substituted. In this case we need to create // a new *Interface before modifying receivers. - if iface == n.orig.underlying { + if iface == orig.underlying { old := iface iface = check.newInterface() iface.embeddeds = old.embeddeds @@ -573,6 +607,7 @@ func (n *Named) expandUnderlying(ctxt *Context) Type { iface.methods = methods } } + return underlying } diff --git a/src/go/types/predicates.go b/src/go/types/predicates.go index 51d056f355951..6e08b76e40602 100644 --- a/src/go/types/predicates.go +++ b/src/go/types/predicates.go @@ -104,7 +104,7 @@ func isTypeParam(t Type) bool { func isGeneric(t Type) bool { // A parameterized type is only generic if it doesn't have an instantiation already. named, _ := t.(*Named) - return named != nil && named.obj != nil && named.targs == nil && named.TypeParams() != nil + return named != nil && named.obj != nil && named.inst == nil && named.TypeParams().Len() > 0 } // Comparable reports whether values of type T are comparable. @@ -403,7 +403,7 @@ func identical(x, y Type, cmpTags bool, p *ifacePair) bool { if len(xargs) > 0 { // Instances are identical if their original type and type arguments // are identical. - if !Identical(x.orig, y.orig) { + if !Identical(x.Origin(), y.Origin()) { return false } for i, xa := range xargs { diff --git a/src/go/types/sizeof_test.go b/src/go/types/sizeof_test.go index 66a69521d2894..d4ce0a7fcd0a4 100644 --- a/src/go/types/sizeof_test.go +++ b/src/go/types/sizeof_test.go @@ -30,7 +30,7 @@ func TestSizeof(t *testing.T) { {Interface{}, 40, 80}, {Map{}, 16, 32}, {Chan{}, 12, 24}, - {Named{}, 68, 128}, + {Named{}, 60, 112}, {TypeParam{}, 28, 48}, {term{}, 12, 24}, diff --git a/src/go/types/subst.go b/src/go/types/subst.go index 63849b921241e..110298cbaeea3 100644 --- a/src/go/types/subst.go +++ b/src/go/types/subst.go @@ -176,7 +176,7 @@ func (subst *subster) typ(typ Type) Type { // In this case the interface will not be substituted here, because its // method signatures do not depend on the type parameter P, but we still // need to create new interface methods to hold the instantiated - // receiver. This is handled by expandNamed. + // receiver. This is handled by Named.expandUnderlying. iface.methods, _ = replaceRecvType(methods, t, iface) return iface } @@ -207,19 +207,20 @@ func (subst *subster) typ(typ Type) Type { } } - // subst is called by expandNamed, so in this function we need to be + // subst is called during expansion, so in this function we need to be // careful not to call any methods that would cause t to be expanded: doing // so would result in deadlock. // - // So we call t.orig.TypeParams() rather than t.TypeParams() here and - // below. - if t.orig.TypeParams().Len() == 0 { + // So we call t.Origin().TypeParams() rather than t.TypeParams(). + orig := t.Origin() + n := orig.TypeParams().Len() + if n == 0 { dump(">>> %s is not parameterized", t) return t // type is not parameterized } var newTArgs []Type - if t.targs.Len() != t.orig.TypeParams().Len() { + if t.TypeArgs().Len() != n { return Typ[Invalid] // error reported elsewhere } @@ -228,14 +229,14 @@ func (subst *subster) typ(typ Type) Type { // For each (existing) type argument targ, determine if it needs // to be substituted; i.e., if it is or contains a type parameter // that has a type argument for it. - for i, targ := range t.targs.list() { + for i, targ := range t.TypeArgs().list() { dump(">>> %d targ = %s", i, targ) new_targ := subst.typ(targ) if new_targ != targ { dump(">>> substituted %d targ %s => %s", i, targ, new_targ) if newTArgs == nil { - newTArgs = make([]Type, t.orig.TypeParams().Len()) - copy(newTArgs, t.targs.list()) + newTArgs = make([]Type, n) + copy(newTArgs, t.TypeArgs().list()) } newTArgs[i] = new_targ } @@ -247,9 +248,9 @@ func (subst *subster) typ(typ Type) Type { } // before creating a new named type, check if we have this one already - h := subst.ctxt.instanceHash(t.orig, newTArgs) + h := subst.ctxt.instanceHash(orig, newTArgs) dump(">>> new type hash: %s", h) - if named := subst.ctxt.lookup(h, t.orig, newTArgs); named != nil { + if named := subst.ctxt.lookup(h, orig, newTArgs); named != nil { dump(">>> found %s", named) return named } @@ -258,7 +259,7 @@ func (subst *subster) typ(typ Type) Type { // recursion. The position used here is irrelevant because validation only // occurs on t (we don't call validType on named), but we use subst.pos to // help with debugging. - return subst.check.instance(subst.pos, t.orig, newTArgs, subst.ctxt) + return subst.check.instance(subst.pos, orig, newTArgs, subst.ctxt) // Note that if we were to expose substitution more generally (not just in // the context of a declaration), we'd have to substitute in diff --git a/src/go/types/typestring.go b/src/go/types/typestring.go index 0325d4a77f5c1..5a2e2c171a98e 100644 --- a/src/go/types/typestring.go +++ b/src/go/types/typestring.go @@ -285,9 +285,9 @@ func (w *typeWriter) typ(typ Type) { w.string(strconv.Itoa(w.ctxt.getID(t))) } w.typeName(t.obj) // when hashing written for readability of the hash only - if t.targs != nil { + if t.inst != nil { // instantiated type - w.typeList(t.targs.list()) + w.typeList(t.inst.targs.list()) } else if w.ctxt == nil && t.TypeParams().Len() != 0 { // For type hashing, don't need to format the TypeParams // parameterized type w.tParamList(t.TypeParams().list()) diff --git a/src/go/types/typexpr.go b/src/go/types/typexpr.go index c7161e00a5501..05bd51a82b2b4 100644 --- a/src/go/types/typexpr.go +++ b/src/go/types/typexpr.go @@ -428,8 +428,8 @@ func (check *Checker) instantiatedType(ix *typeparams.IndexExpr, def *Named) (re // errors. check.recordInstance(ix.Orig, inst.TypeArgs().list(), inst) - if check.validateTArgLen(ix.Pos(), inst.TypeParams().Len(), inst.targs.Len()) { - if i, err := check.verify(ix.Pos(), inst.TypeParams().list(), inst.targs.list()); err != nil { + if check.validateTArgLen(ix.Pos(), inst.TypeParams().Len(), inst.TypeArgs().Len()) { + if i, err := check.verify(ix.Pos(), inst.TypeParams().list(), inst.TypeArgs().list()); err != nil { // best position for error reporting pos := ix.Pos() if i < len(ix.Indices) { @@ -437,7 +437,7 @@ func (check *Checker) instantiatedType(ix *typeparams.IndexExpr, def *Named) (re } check.softErrorf(atPos(pos), _InvalidTypeArg, err.Error()) } else { - check.mono.recordInstance(check.pkg, ix.Pos(), inst.TypeParams().list(), inst.targs.list(), ix.Indices) + check.mono.recordInstance(check.pkg, ix.Pos(), inst.TypeParams().list(), inst.TypeArgs().list(), ix.Indices) } } diff --git a/src/go/types/unify.go b/src/go/types/unify.go index 0742e40d8bd9d..602e304b4a2c0 100644 --- a/src/go/types/unify.go +++ b/src/go/types/unify.go @@ -546,8 +546,8 @@ func (u *unifier) nify(x, y Type, p *ifacePair) (result bool) { case *Named: // TODO(gri) This code differs now from the parallel code in Checker.identical. Investigate. if y, ok := y.(*Named); ok { - xargs := x.targs.list() - yargs := y.targs.list() + xargs := x.TypeArgs().list() + yargs := y.TypeArgs().list() if len(xargs) != len(yargs) { return false diff --git a/src/go/types/validtype.go b/src/go/types/validtype.go index edb4c02ecdacd..0d7a0f308c323 100644 --- a/src/go/types/validtype.go +++ b/src/go/types/validtype.go @@ -71,7 +71,7 @@ func (check *Checker) validType0(typ Type, env *tparamEnv, path []Object) typeIn switch check.infoMap[t] { case unknown: check.infoMap[t] = marked - check.infoMap[t] = check.validType0(t.orig.fromRHS, env.push(t), append(path, t.obj)) + check.infoMap[t] = check.validType0(t.Origin().fromRHS, env.push(t), append(path, t.obj)) case marked: // We have seen type t before and thus must have a cycle. check.infoMap[t] = invalid