untyped map[syntax.Expr]exprInfo // map of expressions without final type
delayed []action // stack of delayed action segments; segments are processed in FIFO order
objPath []Object // path of object dependencies during type inference (for cycle reporting)
- defTypes []*Named // defined types created during type checking, for final validation.
+ cleaners []cleaner // list of types that may need a final cleanup at the end of type-checking
// environment within which the current object is type-checked (valid only
// for the duration of type-checking a specific object)
return obj
}
+type cleaner interface {
+ cleanup()
+}
+
+// needsCleanup records objects/types that implement the cleanup method
+// which will be called at the end of type-checking.
+func (check *Checker) needsCleanup(c cleaner) {
+ check.cleaners = append(check.cleaners, c)
+}
+
// NewChecker returns a new Checker instance for a given package.
// Package files may be added incrementally via checker.Files.
func NewChecker(conf *Config, pkg *Package, info *Info) *Checker {
check.methods = nil
check.untyped = nil
check.delayed = nil
+ check.objPath = nil
+ check.cleaners = nil
// determine package name and collect valid files
pkg := check.pkg
print("== processDelayed ==")
check.processDelayed(0) // incl. all functions
- print("== expandDefTypes ==")
- check.expandDefTypes()
+ print("== cleanup ==")
+ check.cleanup()
print("== initOrder ==")
check.initOrder()
check.recvTParamMap = nil
check.brokenAliases = nil
check.unionTypeSets = nil
- check.defTypes = nil
check.ctxt = nil
// TODO(gri) There's more memory we should release at this point.
check.delayed = check.delayed[:top]
}
-func (check *Checker) expandDefTypes() {
- // Ensure that every defined type created in the course of type-checking has
- // either non-*Named underlying, or is unresolved.
- //
- // This guarantees that we don't leak any types whose underlying is *Named,
- // because any unresolved instances will lazily compute their underlying by
- // substituting in the underlying of their origin. The origin must have
- // either been imported or type-checked and expanded here, and in either case
- // its underlying will be fully expanded.
- for i := 0; i < len(check.defTypes); i++ {
- n := check.defTypes[i]
- switch n.underlying.(type) {
- case nil:
- if n.resolver == nil {
- panic("nil underlying")
- }
- case *Named:
- n.under() // n.under may add entries to check.defTypes
- }
- n.check = nil
+// cleanup runs cleanup for all collected cleaners.
+func (check *Checker) cleanup() {
+ // Don't use a range clause since Named.cleanup may add more cleaners.
+ for i := 0; i < len(check.cleaners); i++ {
+ check.cleaners[i].cleanup()
}
+ check.cleaners = nil
}
func (check *Checker) record(x *operand) {
}
// set method receivers if necessary
- typ := new(Interface)
+ typ := (*Checker)(nil).newInterface()
for _, m := range methods {
if sig := m.typ.(*Signature); sig.recv == nil {
sig.recv = NewVar(m.pos, m.pkg, "", typ)
return typ
}
+// check may be nil
+func (check *Checker) newInterface() *Interface {
+ typ := &Interface{check: check}
+ if check != nil {
+ check.needsCleanup(typ)
+ }
+ return typ
+}
+
// MarkImplicit marks the interface t as implicit, meaning this interface
// corresponds to a constraint literal such as ~T or A|B without explicit
// interface embedding. MarkImplicit should be called before any concurrent use
// ----------------------------------------------------------------------------
// Implementation
+func (t *Interface) cleanup() {
+ t.check = nil
+ t.embedPos = nil
+}
+
func (check *Checker) interfaceType(ityp *Interface, iface *syntax.InterfaceType, def *Named) {
addEmbedded := func(pos syntax.Pos, typ Type) {
ityp.embeddeds = append(ityp.embeddeds, typ)
// (don't sort embeddeds: they must correspond to *embedPos entries)
sortMethods(ityp.methods)
- // Compute type set with a non-nil *Checker as soon as possible
- // to report any errors. Subsequent uses of type sets will use
- // this computed type set and won't need to pass in a *Checker.
- //
- // Pin the checker to the interface type in the interim, in case the type set
- // must be used before delayed funcs are processed (see issue #48234).
- // TODO(rfindley): clean up use of *Checker with computeInterfaceTypeSet
- ityp.check = check
+ // Compute type set as soon as possible to report any errors.
+ // Subsequent uses of type sets will use this computed type
+ // set and won't need to pass in a *Checker.
check.later(func() {
computeInterfaceTypeSet(check, iface.Pos(), ityp)
- ityp.check = nil
}).describef(iface, "compute type set for %s", ityp)
}
}
// Ensure that typ is always expanded and sanity-checked.
if check != nil {
- check.defTypes = append(check.defTypes, typ)
+ check.needsCleanup(typ)
}
return typ
}
+func (t *Named) cleanup() {
+ // Ensure that every defined type created in the course of type-checking has
+ // either non-*Named underlying, or is unresolved.
+ //
+ // This guarantees that we don't leak any types whose underlying is *Named,
+ // because any unresolved instances will lazily compute their underlying by
+ // substituting in the underlying of their origin. The origin must have
+ // either been imported or type-checked and expanded here, and in either case
+ // its underlying will be fully expanded.
+ switch t.underlying.(type) {
+ case nil:
+ if t.resolver == nil {
+ panic("nil underlying")
+ }
+ case *Named:
+ t.under() // t.under may add entries to check.cleaners
+ }
+ t.check = nil
+}
+
// Obj returns the type name for the declaration defining the named type t. For
// instantiated types, this is the type name of the base type.
func (t *Named) Obj() *TypeName { return t.orig.obj } // for non-instances this is the same as t.obj
// that it wasn't substituted. In this case we need to create a new
// *Interface before modifying receivers.
if iface == n.orig.underlying {
- iface = &Interface{
- embeddeds: iface.embeddeds,
- complete: iface.complete,
- implicit: iface.implicit, // should be false but be conservative
- }
+ old := iface
+ iface = check.newInterface()
+ iface.embeddeds = old.embeddeds
+ iface.complete = old.complete
+ iface.implicit = old.implicit // should be false but be conservative
underlying = iface
}
iface.methods = methods
methods, mcopied := subst.funcList(t.methods)
embeddeds, ecopied := subst.typeList(t.embeddeds)
if mcopied || ecopied {
- iface := &Interface{embeddeds: embeddeds, implicit: t.implicit, complete: t.complete}
+ iface := subst.check.newInterface()
+ iface.embeddeds = embeddeds
+ iface.implicit = t.implicit
+ iface.complete = t.complete
// If we've changed the interface type, we may need to replace its
// receiver if the receiver type is the original interface. Receivers of
// *Named type are replaced during named type expansion.
return (*Checker)(nil).newTypeParam(obj, constraint)
}
+// check may be nil
func (check *Checker) newTypeParam(obj *TypeName, constraint Type) *TypeParam {
// Always increment lastID, even if it is not used.
id := nextID()
// iface may mutate typ.bound, so we must ensure that iface() is called
// at least once before the resulting TypeParam escapes.
if check != nil {
- check.later(func() {
- typ.iface()
- })
+ check.needsCleanup(typ)
} else if constraint != nil {
typ.iface()
}
// ----------------------------------------------------------------------------
// Implementation
+func (t *TypeParam) cleanup() {
+ t.iface()
+ t.check = nil
+}
+
// iface returns the constraint interface of t.
-// TODO(gri) If we make tparamIsIface the default, this should be renamed to under
-// (similar to Named.under).
func (t *TypeParam) iface() *Interface {
bound := t.bound
return typ
case *syntax.InterfaceType:
- typ := new(Interface)
+ typ := check.newInterface()
def.setUnderlying(typ)
if def != nil {
typ.obj = def.obj
untyped map[ast.Expr]exprInfo // map of expressions without final type
delayed []action // stack of delayed action segments; segments are processed in FIFO order
objPath []Object // path of object dependencies during type inference (for cycle reporting)
- defTypes []*Named // defined types created during type checking, for final validation.
+ cleaners []cleaner // list of types that may need a final cleanup at the end of type-checking
// environment within which the current object is type-checked (valid only
// for the duration of type-checking a specific object)
return obj
}
+type cleaner interface {
+ cleanup()
+}
+
+// needsCleanup records objects/types that implement the cleanup method
+// which will be called at the end of type-checking.
+func (check *Checker) needsCleanup(c cleaner) {
+ check.cleaners = append(check.cleaners, c)
+}
+
// NewChecker returns a new Checker instance for a given package.
// Package files may be added incrementally via checker.Files.
func NewChecker(conf *Config, fset *token.FileSet, pkg *Package, info *Info) *Checker {
check.methods = nil
check.untyped = nil
check.delayed = nil
+ check.objPath = nil
+ check.cleaners = nil
// determine package name and collect valid files
pkg := check.pkg
defer check.handleBailout(&err)
+ print := func(msg string) {
+ if trace {
+ fmt.Println()
+ fmt.Println(msg)
+ }
+ }
+
+ print("== initFiles ==")
check.initFiles(files)
+ print("== collectObjects ==")
check.collectObjects()
+ print("== packageObjects ==")
check.packageObjects()
+ print("== processDelayed ==")
check.processDelayed(0) // incl. all functions
- check.expandDefTypes()
+ print("== cleanup ==")
+ check.cleanup()
+ print("== initOrder ==")
check.initOrder()
if !check.conf.DisableUnusedImportCheck {
+ print("== unusedImports ==")
check.unusedImports()
}
+ print("== recordUntyped ==")
check.recordUntyped()
if check.firstErr == nil {
check.recvTParamMap = nil
check.brokenAliases = nil
check.unionTypeSets = nil
- check.defTypes = nil
check.ctxt = nil
// TODO(rFindley) There's more memory we should release at this point.
check.delayed = check.delayed[:top]
}
-func (check *Checker) expandDefTypes() {
- // Ensure that every defined type created in the course of type-checking has
- // either non-*Named underlying, or is unresolved.
- //
- // This guarantees that we don't leak any types whose underlying is *Named,
- // because any unresolved instances will lazily compute their underlying by
- // substituting in the underlying of their origin. The origin must have
- // either been imported or type-checked and expanded here, and in either case
- // its underlying will be fully expanded.
- for i := 0; i < len(check.defTypes); i++ {
- n := check.defTypes[i]
- switch n.underlying.(type) {
- case nil:
- if n.resolver == nil {
- panic("nil underlying")
- }
- case *Named:
- n.under() // n.under may add entries to check.defTypes
- }
- n.check = nil
+// cleanup runs cleanup for all collected cleaners.
+func (check *Checker) cleanup() {
+ // Don't use a range clause since Named.cleanup may add more cleaners.
+ for i := 0; i < len(check.cleaners); i++ {
+ check.cleaners[i].cleanup()
}
+ check.cleaners = nil
}
func (check *Checker) record(x *operand) {
}
// set method receivers if necessary
- typ := new(Interface)
+ typ := (*Checker)(nil).newInterface()
for _, m := range methods {
if sig := m.typ.(*Signature); sig.recv == nil {
sig.recv = NewVar(m.pos, m.pkg, "", typ)
return typ
}
+// check may be nil
+func (check *Checker) newInterface() *Interface {
+ typ := &Interface{check: check}
+ if check != nil {
+ check.needsCleanup(typ)
+ }
+ return typ
+}
+
// MarkImplicit marks the interface t as implicit, meaning this interface
// corresponds to a constraint literal such as ~T or A|B without explicit
// interface embedding. MarkImplicit should be called before any concurrent use
// ----------------------------------------------------------------------------
// Implementation
+func (t *Interface) cleanup() {
+ t.check = nil
+ t.embedPos = nil
+}
+
func (check *Checker) interfaceType(ityp *Interface, iface *ast.InterfaceType, def *Named) {
addEmbedded := func(pos token.Pos, typ Type) {
ityp.embeddeds = append(ityp.embeddeds, typ)
sortMethods(ityp.methods)
// (don't sort embeddeds: they must correspond to *embedPos entries)
- // Compute type set with a non-nil *Checker as soon as possible
- // to report any errors. Subsequent uses of type sets will use
- // this computed type set and won't need to pass in a *Checker.
- //
- // Pin the checker to the interface type in the interim, in case the type set
- // must be used before delayed funcs are processed (see issue #48234).
- // TODO(rfindley): clean up use of *Checker with computeInterfaceTypeSet
- ityp.check = check
+ // Compute type set as soon as possible to report any errors.
+ // Subsequent uses of type sets will use this computed type
+ // set and won't need to pass in a *Checker.
check.later(func() {
computeInterfaceTypeSet(check, iface.Pos(), ityp)
- ityp.check = nil
}).describef(iface, "compute type set for %s", ityp)
}
}
// Ensure that typ is always expanded and sanity-checked.
if check != nil {
- check.defTypes = append(check.defTypes, typ)
+ check.needsCleanup(typ)
}
return typ
}
+func (t *Named) cleanup() {
+ // Ensure that every defined type created in the course of type-checking has
+ // either non-*Named underlying, or is unresolved.
+ //
+ // This guarantees that we don't leak any types whose underlying is *Named,
+ // because any unresolved instances will lazily compute their underlying by
+ // substituting in the underlying of their origin. The origin must have
+ // either been imported or type-checked and expanded here, and in either case
+ // its underlying will be fully expanded.
+ switch t.underlying.(type) {
+ case nil:
+ if t.resolver == nil {
+ panic("nil underlying")
+ }
+ case *Named:
+ t.under() // t.under may add entries to check.cleaners
+ }
+ t.check = nil
+}
+
// Obj returns the type name for the declaration defining the named type t. For
// instantiated types, this is the type name of the base type.
func (t *Named) Obj() *TypeName {
// that it wasn't substituted. In this case we need to create a new
// *Interface before modifying receivers.
if iface == n.orig.underlying {
- iface = &Interface{
- embeddeds: iface.embeddeds,
- complete: iface.complete,
- implicit: iface.implicit, // should be false but be conservative
- }
+ old := iface
+ iface = check.newInterface()
+ iface.embeddeds = old.embeddeds
+ iface.complete = old.complete
+ iface.implicit = old.implicit // should be false but be conservative
underlying = iface
}
iface.methods = methods
methods, mcopied := subst.funcList(t.methods)
embeddeds, ecopied := subst.typeList(t.embeddeds)
if mcopied || ecopied {
- iface := &Interface{embeddeds: embeddeds, implicit: t.implicit, complete: t.complete}
+ iface := subst.check.newInterface()
+ iface.embeddeds = embeddeds
+ iface.implicit = t.implicit
+ iface.complete = t.complete
// If we've changed the interface type, we may need to replace its
// receiver if the receiver type is the original interface. Receivers of
// *Named type are replaced during named type expansion.
return (*Checker)(nil).newTypeParam(obj, constraint)
}
+// check may be nil
func (check *Checker) newTypeParam(obj *TypeName, constraint Type) *TypeParam {
// Always increment lastID, even if it is not used.
id := nextID()
// iface may mutate typ.bound, so we must ensure that iface() is called
// at least once before the resulting TypeParam escapes.
if check != nil {
- check.later(func() {
- typ.iface()
- })
+ check.needsCleanup(typ)
} else if constraint != nil {
typ.iface()
}
// ----------------------------------------------------------------------------
// Implementation
+func (t *TypeParam) cleanup() {
+ t.iface()
+ t.check = nil
+}
+
// iface returns the constraint interface of t.
-// TODO(gri) If we make tparamIsIface the default, this should be renamed to under
-// (similar to Named.under).
func (t *TypeParam) iface() *Interface {
bound := t.bound
return typ
case *ast.InterfaceType:
- typ := new(Interface)
+ typ := check.newInterface()
def.setUnderlying(typ)
if def != nil {
typ.obj = def.obj