From 4b5fdb0b7a362cb6fa6ad551757104e490483121 Mon Sep 17 00:00:00 2001 From: Robert Griesemer Date: Thu, 17 Jun 2021 17:49:15 -0700 Subject: [PATCH] [dev.typeparams] cmd/compile/internal/types2: introduce type set abstraction for interfaces With this change, interfaces are "completed" on-demand, when needed, and the respective information (set of all methods, type constraints) is recorded in a new typeSet data structure. As a consequence, interfaces don't need to be explicitly completed anymore and (internal) uses of interfaces have become much simpler. This change also introduces a new field Interface.complete to indicate that all methods and embedded elements have been set up. This prevent the computation and recording (!) of a partial type set for erroneous programs (if we compute the partial type set and store it, subsequent type set accesses use the wrong type set which may lead to follow-on errors). Change-Id: I1ffc907f7d0fb93b3e987fe5ff9c6fa5cae00d7f Reviewed-on: https://go-review.googlesource.com/c/go/+/329309 Trust: Robert Griesemer Run-TryBot: Robert Griesemer TryBot-Result: Go Bot Reviewed-by: Robert Findley --- src/cmd/compile/internal/types2/builtins.go | 2 +- src/cmd/compile/internal/types2/call.go | 1 - src/cmd/compile/internal/types2/expr.go | 1 - src/cmd/compile/internal/types2/infer.go | 27 ++-- src/cmd/compile/internal/types2/interface.go | 64 +++++----- src/cmd/compile/internal/types2/lookup.go | 17 +-- src/cmd/compile/internal/types2/predicates.go | 12 +- src/cmd/compile/internal/types2/sanitize.go | 8 +- .../compile/internal/types2/sizeof_test.go | 3 +- src/cmd/compile/internal/types2/subst.go | 15 ++- .../types2/testdata/check/cycles4.src | 15 ++- src/cmd/compile/internal/types2/type.go | 119 ++++++------------ src/cmd/compile/internal/types2/typeset.go | 70 +++++++++++ src/cmd/compile/internal/types2/typestring.go | 13 +- src/cmd/compile/internal/types2/typexpr.go | 8 +- src/cmd/compile/internal/types2/unify.go | 12 +- src/cmd/compile/internal/types2/universe.go | 4 +- 17 files changed, 205 insertions(+), 186 deletions(-) create mode 100644 src/cmd/compile/internal/types2/typeset.go diff --git a/src/cmd/compile/internal/types2/builtins.go b/src/cmd/compile/internal/types2/builtins.go index 8f2d849ef5..ffe872e7ab 100644 --- a/src/cmd/compile/internal/types2/builtins.go +++ b/src/cmd/compile/internal/types2/builtins.go @@ -783,7 +783,7 @@ func (check *Checker) applyTypeFunc(f func(Type) Type, x Type) Type { tpar := NewTypeName(nopos, check.pkg, "", nil) ptyp := check.NewTypeParam(tpar, 0, &emptyInterface) // assigns type to tpar as a side-effect tsum := newUnion(rtypes, tildes) - ptyp.bound = &Interface{allMethods: markComplete, allTypes: tsum} + ptyp.bound = &Interface{complete: true, tset: &TypeSet{types: tsum}} return ptyp } diff --git a/src/cmd/compile/internal/types2/call.go b/src/cmd/compile/internal/types2/call.go index 8c717cd1e5..8c17a2f808 100644 --- a/src/cmd/compile/internal/types2/call.go +++ b/src/cmd/compile/internal/types2/call.go @@ -99,7 +99,6 @@ func (check *Checker) callExpr(x *operand, call *syntax.CallExpr) exprKind { check.expr(x, call.ArgList[0]) if x.mode != invalid { if t := asInterface(T); t != nil { - check.completeInterface(nopos, t) if t.IsConstraint() { check.errorf(call, "cannot use interface %s in conversion (contains type list or is comparable)", T) break diff --git a/src/cmd/compile/internal/types2/expr.go b/src/cmd/compile/internal/types2/expr.go index d1cb27de16..7fba179e44 100644 --- a/src/cmd/compile/internal/types2/expr.go +++ b/src/cmd/compile/internal/types2/expr.go @@ -735,7 +735,6 @@ func (check *Checker) implicitTypeAndValue(x *operand, target Type) (Type, const // Update operand types to the default type rather than the target // (interface) type: values must have concrete dynamic types. // Untyped nil was handled upfront. - check.completeInterface(nopos, t) if !t.Empty() { return nil, nil, _InvalidUntypedConversion // cannot assign untyped values to non-empty interfaces } diff --git a/src/cmd/compile/internal/types2/infer.go b/src/cmd/compile/internal/types2/infer.go index 63cd63aacc..791e25e9f0 100644 --- a/src/cmd/compile/internal/types2/infer.go +++ b/src/cmd/compile/internal/types2/infer.go @@ -321,24 +321,13 @@ func (w *tpWalker) isParameterized(typ Type) (res bool) { return w.isParameterized(t.params) || w.isParameterized(t.results) case *Interface: - if t.allMethods != nil { - // interface is complete - quick test - for _, m := range t.allMethods { - if w.isParameterized(m.typ) { - return true - } + tset := t.typeSet() + for _, m := range tset.methods { + if w.isParameterized(m.typ) { + return true } - return w.isParameterized(t.allTypes) } - - return t.iterate(func(t *Interface) bool { - for _, m := range t.methods { - if w.isParameterized(m.typ) { - return true - } - } - return w.isParameterizedList(t.embeddeds) - }, nil) + return w.isParameterized(tset.types) case *Map: return w.isParameterized(t.key) || w.isParameterized(t.elem) @@ -476,15 +465,15 @@ func (check *Checker) inferB(tparams []*TypeName, targs []Type, report bool) (ty // structuralType returns the structural type of a constraint, if any. func (check *Checker) structuralType(constraint Type) Type { if iface, _ := under(constraint).(*Interface); iface != nil { - check.completeInterface(nopos, iface) - if u, _ := iface.allTypes.(*Union); u != nil { + types := iface.typeSet().types + if u, _ := types.(*Union); u != nil { if u.NumTerms() == 1 { // TODO(gri) do we need to respect tilde? return u.types[0] } return nil } - return iface.allTypes + return types } return nil } diff --git a/src/cmd/compile/internal/types2/interface.go b/src/cmd/compile/internal/types2/interface.go index c79026f00d..4dee923422 100644 --- a/src/cmd/compile/internal/types2/interface.go +++ b/src/cmd/compile/internal/types2/interface.go @@ -95,9 +95,13 @@ func (check *Checker) interfaceType(ityp *Interface, iface *syntax.InterfaceType check.posMap[ityp] = append(check.posMap[ityp], tlist[0].(*syntax.Operation).X.Pos()) } + // All methods and embedded elements for this interface are collected; + // i.e., this interface is may be used in a type set computation. + ityp.complete = true + if len(ityp.methods) == 0 && len(ityp.embeddeds) == 0 { // empty interface - ityp.allMethods = markComplete + ityp.tset = &topTypeSet return } @@ -105,7 +109,10 @@ func (check *Checker) interfaceType(ityp *Interface, iface *syntax.InterfaceType sortMethods(ityp.methods) sortTypes(ityp.embeddeds) - check.later(func() { check.completeInterface(iface.Pos(), ityp) }) + // Compute type set with a non-nil *Checker as soon as possible + // to report any errors. Subsequent uses of type sets should be + // using this computed type set and won't need to pass in a *Checker. + check.later(func() { newTypeSet(check, iface.Pos(), ityp) }) } func flattenUnion(list []syntax.Expr, x syntax.Expr) []syntax.Expr { @@ -116,26 +123,27 @@ func flattenUnion(list []syntax.Expr, x syntax.Expr) []syntax.Expr { return append(list, x) } -func (check *Checker) completeInterface(pos syntax.Pos, ityp *Interface) { - if ityp.allMethods != nil { - return +// newTypeSet may be called with check == nil. +// TODO(gri) move this function into typeset.go eventually +func newTypeSet(check *Checker, pos syntax.Pos, ityp *Interface) *TypeSet { + if ityp.tset != nil { + return ityp.tset } - // completeInterface may be called via the LookupFieldOrMethod, - // MissingMethod, Identical, or IdenticalIgnoreTags external API - // in which case check will be nil. In this case, type-checking - // must be finished and all interfaces should have been completed. - if check == nil { - panic("internal error: incomplete interface") + // If the interface is not fully set up yet, the type set will + // not be complete, which may lead to errors when using the the + // type set (e.g. missing method). Don't compute a partial type + // set (and don't store it!), so that we still compute the full + // type set eventually. Instead, return the top type set and + // let any follow-on errors play out. + // + // TODO(gri) Consider recording when this happens and reporting + // it as an error (but only if there were no other errors so to + // to not have unnecessary follow-on errors). + if !ityp.complete { + return &topTypeSet } - completeInterface(check, pos, ityp) -} - -// completeInterface may be called with check == nil. -func completeInterface(check *Checker, pos syntax.Pos, ityp *Interface) { - assert(ityp.allMethods == nil) - if check != nil && check.conf.Trace { // Types don't generally have position information. // If we don't have a valid pos provided, try to use @@ -144,11 +152,11 @@ func completeInterface(check *Checker, pos syntax.Pos, ityp *Interface) { pos = ityp.methods[0].pos } - check.trace(pos, "complete %s", ityp) + check.trace(pos, "type set for %s", ityp) check.indent++ defer func() { check.indent-- - check.trace(pos, "=> %s (methods = %v, types = %v)", ityp, ityp.allMethods, ityp.allTypes) + check.trace(pos, "=> %s ", ityp.typeSet()) }() } @@ -157,7 +165,7 @@ func completeInterface(check *Checker, pos syntax.Pos, ityp *Interface) { // have valid interfaces. Mark the interface as complete to avoid // infinite recursion if the validType check occurs later for some // reason. - ityp.allMethods = markComplete + ityp.tset = new(TypeSet) // TODO(gri) is this sufficient? // Methods of embedded interfaces are collected unchanged; i.e., the identity // of a method I.m's Func Object of an interface I is the same as that of @@ -231,13 +239,11 @@ func completeInterface(check *Checker, pos syntax.Pos, ityp *Interface) { var types Type switch t := under(typ).(type) { case *Interface: - if t.allMethods == nil { - completeInterface(check, pos, t) - } - for _, m := range t.allMethods { + tset := newTypeSet(check, pos, t) + for _, m := range tset.methods { addMethod(pos, m, false) // use embedding position pos rather than m.pos } - types = t.allTypes + types = tset.types case *Union: // TODO(gri) combine with default case once we have // converted all tests to new notation and we @@ -274,9 +280,11 @@ func completeInterface(check *Checker, pos syntax.Pos, ityp *Interface) { if methods != nil { sortMethods(methods) - ityp.allMethods = methods + ityp.tset.methods = methods } - ityp.allTypes = allTypes + ityp.tset.types = allTypes + + return ityp.tset } func sortTypes(list []Type) { diff --git a/src/cmd/compile/internal/types2/lookup.go b/src/cmd/compile/internal/types2/lookup.go index 93ed620449..9fcec44d53 100644 --- a/src/cmd/compile/internal/types2/lookup.go +++ b/src/cmd/compile/internal/types2/lookup.go @@ -182,9 +182,7 @@ func (check *Checker) rawLookupFieldOrMethod(T Type, addressable bool, pkg *Pack case *Interface: // look for a matching method - // TODO(gri) t.allMethods is sorted - use binary search - check.completeInterface(nopos, t) - if i, m := lookupMethod(t.allMethods, pkg, name); m != nil { + if i, m := t.typeSet().LookupMethod(pkg, name); m != nil { assert(m.typ != nil) index = concat(e.index, i) if obj != nil || e.multiples { @@ -195,7 +193,7 @@ func (check *Checker) rawLookupFieldOrMethod(T Type, addressable bool, pkg *Pack } case *TypeParam: - if i, m := lookupMethod(t.Bound().allMethods, pkg, name); m != nil { + if i, m := t.Bound().typeSet().LookupMethod(pkg, name); m != nil { assert(m.typ != nil) index = concat(e.index, i) if obj != nil || e.multiples { @@ -307,18 +305,15 @@ func MissingMethod(V Type, T *Interface, static bool) (method *Func, wrongType b // To improve error messages, also report the wrong signature // when the method exists on *V instead of V. func (check *Checker) missingMethod(V Type, T *Interface, static bool) (method, wrongType *Func) { - check.completeInterface(nopos, T) - // fast path for common case if T.Empty() { return } if ityp := asInterface(V); ityp != nil { - check.completeInterface(nopos, ityp) - // TODO(gri) allMethods is sorted - can do this more efficiently - for _, m := range T.allMethods { - _, f := lookupMethod(ityp.allMethods, m.pkg, m.name) + // TODO(gri) the methods are sorted - could do this more efficiently + for _, m := range T.typeSet().methods { + _, f := ityp.typeSet().LookupMethod(m.pkg, m.name) if f == nil { // if m is the magic method == we're ok (interfaces are comparable) @@ -356,7 +351,7 @@ func (check *Checker) missingMethod(V Type, T *Interface, static bool) (method, // A concrete type implements T if it implements all methods of T. Vd, _ := deref(V) Vn := asNamed(Vd) - for _, m := range T.allMethods { + for _, m := range T.typeSet().methods { // TODO(gri) should this be calling lookupFieldOrMethod instead (and why not)? obj, _, _ := check.rawLookupFieldOrMethod(V, false, m.pkg, m.name) diff --git a/src/cmd/compile/internal/types2/predicates.go b/src/cmd/compile/internal/types2/predicates.go index 66de249044..73af127188 100644 --- a/src/cmd/compile/internal/types2/predicates.go +++ b/src/cmd/compile/internal/types2/predicates.go @@ -287,16 +287,8 @@ func (check *Checker) identical0(x, y Type, cmpTags bool, p *ifacePair) bool { // the same names and identical function types. Lower-case method names from // different packages are always different. The order of the methods is irrelevant. if y, ok := y.(*Interface); ok { - // If identical0 is called (indirectly) via an external API entry point - // (such as Identical, IdenticalIgnoreTags, etc.), check is nil. But in - // that case, interfaces are expected to be complete and lazy completion - // here is not needed. - if check != nil { - check.completeInterface(nopos, x) - check.completeInterface(nopos, y) - } - a := x.allMethods - b := y.allMethods + a := x.typeSet().methods + b := y.typeSet().methods if len(a) == len(b) { // Interface types are the only types where cycles can occur // that are not "terminated" via named types; and such cycles diff --git a/src/cmd/compile/internal/types2/sanitize.go b/src/cmd/compile/internal/types2/sanitize.go index 406b46e574..3d2323a0a2 100644 --- a/src/cmd/compile/internal/types2/sanitize.go +++ b/src/cmd/compile/internal/types2/sanitize.go @@ -112,9 +112,11 @@ func (s sanitizer) typ(typ Type) Type { case *Interface: s.funcList(t.methods) s.typeList(t.embeddeds) - s.funcList(t.allMethods) - if allTypes := s.typ(t.allTypes); allTypes != t.allTypes { - t.allTypes = allTypes + // TODO(gri) do we need to sanitize type sets? + tset := t.typeSet() + s.funcList(tset.methods) + if types := s.typ(tset.types); types != tset.types { + tset.types = types } case *Map: diff --git a/src/cmd/compile/internal/types2/sizeof_test.go b/src/cmd/compile/internal/types2/sizeof_test.go index 3cb162764c..0b1f7dacad 100644 --- a/src/cmd/compile/internal/types2/sizeof_test.go +++ b/src/cmd/compile/internal/types2/sizeof_test.go @@ -28,7 +28,7 @@ func TestSizeof(t *testing.T) { {Tuple{}, 12, 24}, {Signature{}, 44, 88}, {Union{}, 24, 48}, - {Interface{}, 52, 104}, + {Interface{}, 40, 80}, {Map{}, 16, 32}, {Chan{}, 12, 24}, {Named{}, 84, 160}, @@ -49,6 +49,7 @@ func TestSizeof(t *testing.T) { // Misc {Scope{}, 56, 96}, {Package{}, 40, 80}, + {TypeSet{}, 20, 40}, } for _, test := range tests { diff --git a/src/cmd/compile/internal/types2/subst.go b/src/cmd/compile/internal/types2/subst.go index 3ef65c2e92..38bd07b8a2 100644 --- a/src/cmd/compile/internal/types2/subst.go +++ b/src/cmd/compile/internal/types2/subst.go @@ -136,6 +136,7 @@ func (check *Checker) instantiate(pos syntax.Pos, typ Type, targs []Type, poslis // satisfies reports whether the type argument targ satisfies the constraint of type parameter // parameter tpar (after any of its type parameters have been substituted through smap). // A suitable error is reported if the result is false. +// TODO(gri) This should be a method of interfaces or type sets. func (check *Checker) satisfies(pos syntax.Pos, targ Type, tpar *TypeParam, smap *substMap) bool { iface := tpar.Bound() if iface.Empty() { @@ -150,8 +151,7 @@ func (check *Checker) satisfies(pos syntax.Pos, targ Type, tpar *TypeParam, smap // targ must implement iface (methods) // - check only if we have methods - check.completeInterface(nopos, iface) - if len(iface.allMethods) > 0 { + if iface.NumMethods() > 0 { // If the type argument is a pointer to a type parameter, the type argument's // method set is empty. // TODO(gri) is this what we want? (spec question) @@ -182,7 +182,7 @@ func (check *Checker) satisfies(pos syntax.Pos, targ Type, tpar *TypeParam, smap } // targ's underlying type must also be one of the interface types listed, if any - if iface.allTypes == nil { + if iface.typeSet().types == nil { return true // nothing to do } @@ -190,7 +190,7 @@ func (check *Checker) satisfies(pos syntax.Pos, targ Type, tpar *TypeParam, smap // list of iface types (i.e., the targ type list must be a non-empty subset of the iface types). if targ := asTypeParam(targ); targ != nil { targBound := targ.Bound() - if targBound.allTypes == nil { + if targBound.typeSet().types == nil { check.softErrorf(pos, "%s does not satisfy %s (%s has no type constraints)", targ, tpar.bound, targ) return false } @@ -198,7 +198,7 @@ func (check *Checker) satisfies(pos syntax.Pos, targ Type, tpar *TypeParam, smap // TODO(gri) incorporate tilde information! if !iface.isSatisfiedBy(typ) { // TODO(gri) match this error message with the one below (or vice versa) - check.softErrorf(pos, "%s does not satisfy %s (%s type constraint %s not found in %s)", targ, tpar.bound, targ, typ, iface.allTypes) + check.softErrorf(pos, "%s does not satisfy %s (%s type constraint %s not found in %s)", targ, tpar.bound, targ, typ, iface.typeSet().types) return false } return true @@ -207,7 +207,7 @@ func (check *Checker) satisfies(pos syntax.Pos, targ Type, tpar *TypeParam, smap // Otherwise, targ's type or underlying type must also be one of the interface types listed, if any. if !iface.isSatisfiedBy(targ) { - check.softErrorf(pos, "%s does not satisfy %s (%s not found in %s)", targ, tpar.bound, targ, iface.allTypes) + check.softErrorf(pos, "%s does not satisfy %s (%s not found in %s)", targ, tpar.bound, targ, iface.typeSet().types) return false } @@ -312,12 +312,11 @@ func (subst *subster) typ(typ Type) Type { methods, mcopied := subst.funcList(t.methods) embeddeds, ecopied := subst.typeList(t.embeddeds) if mcopied || ecopied { - iface := &Interface{methods: methods, embeddeds: embeddeds} + iface := &Interface{methods: methods, embeddeds: embeddeds, complete: t.complete} if subst.check == nil { panic("internal error: cannot instantiate interfaces yet") } subst.check.posMap[iface] = subst.check.posMap[t] // satisfy completeInterface requirement - subst.check.completeInterface(nopos, iface) return iface } diff --git a/src/cmd/compile/internal/types2/testdata/check/cycles4.src b/src/cmd/compile/internal/types2/testdata/check/cycles4.src index 445babca68..924aabf475 100644 --- a/src/cmd/compile/internal/types2/testdata/check/cycles4.src +++ b/src/cmd/compile/internal/types2/testdata/check/cycles4.src @@ -4,6 +4,8 @@ package p +import "unsafe" + // Check that all methods of T are collected before // determining the result type of m (which embeds // all methods of T). @@ -13,7 +15,7 @@ type T interface { E } -var _ = T.m(nil).m().e() +var _ int = T.m(nil).m().e() type E interface { e() int @@ -22,7 +24,7 @@ type E interface { // Check that unresolved forward chains are followed // (see also comment in resolver.go, checker.typeDecl). -var _ = C.m(nil).m().e() +var _ int = C.m(nil).m().e() type A B @@ -108,3 +110,12 @@ type Element interface { type Event interface { Target() Element } + +// Check that accessing an interface method too early doesn't lead +// to follow-on errors due to an incorrectly computed type set. + +type T8 interface { + m() [unsafe.Sizeof(T8.m /* ERROR undefined */ )]int +} + +var _ = T8.m // no error expected here diff --git a/src/cmd/compile/internal/types2/type.go b/src/cmd/compile/internal/types2/type.go index 10cb651d0c..122e408ead 100644 --- a/src/cmd/compile/internal/types2/type.go +++ b/src/cmd/compile/internal/types2/type.go @@ -264,18 +264,20 @@ func (s *Signature) Variadic() bool { return s.variadic } // An Interface represents an interface type. type Interface struct { + obj Object // type name object defining this interface; or nil (for better error messages) methods []*Func // ordered list of explicitly declared methods - embeddeds []Type // ordered list of explicitly embedded types + embeddeds []Type // ordered list of explicitly embedded elements + complete bool // indicates that obj, methods, and embeddeds are set and type set can be computed - allMethods []*Func // ordered list of methods declared with or embedded in this interface (TODO(gri): replace with mset) - allTypes Type // intersection of all embedded and locally declared types (TODO(gri) need better field name) - - obj Object // type declaration defining this interface; or nil (for better error messages) + tset *TypeSet // type set described by this interface, computed lazily } +// typeSet returns the type set for interface t. +func (t *Interface) typeSet() *TypeSet { return newTypeSet(nil, nopos, t) } + // is reports whether interface t represents types that all satisfy f. func (t *Interface) is(f func(Type, bool) bool) bool { - switch t := t.allTypes.(type) { + switch t := t.typeSet().types.(type) { case nil, *top: // TODO(gri) should settle on top or nil to represent this case return false // we must have at least one type! (was bug) @@ -286,21 +288,14 @@ func (t *Interface) is(f func(Type, bool) bool) bool { } } -// emptyInterface represents the empty (completed) interface -var emptyInterface = Interface{allMethods: markComplete} +// emptyInterface represents the empty interface +var emptyInterface = Interface{complete: true, tset: &topTypeSet} -// markComplete is used to mark an empty interface as completely -// set up by setting the allMethods field to a non-nil empty slice. -var markComplete = make([]*Func, 0) - -// NewInterface returns a new (incomplete) interface for the given methods and embedded types. -// Each embedded type must have an underlying type of interface type. -// NewInterface takes ownership of the provided methods and may modify their types by setting -// missing receivers. To compute the method set of the interface, Complete must be called. +// NewInterface returns a new interface for the given methods and embedded types. +// NewInterface takes ownership of the provided methods and may modify their types +// by setting missing receivers. // -// Deprecated: Use NewInterfaceType instead which allows any (even non-defined) interface types -// to be embedded. This is necessary for interfaces that embed alias type names referring to -// non-defined (literal) interface types. +// Deprecated: Use NewInterfaceType instead which allows arbitrary embedded types. func NewInterface(methods []*Func, embeddeds []*Named) *Interface { tnames := make([]Type, len(embeddeds)) for i, t := range embeddeds { @@ -309,9 +304,9 @@ func NewInterface(methods []*Func, embeddeds []*Named) *Interface { return NewInterfaceType(methods, tnames) } -// NewInterfaceType returns a new (incomplete) interface for the given methods and embedded types. -// NewInterfaceType takes ownership of the provided methods and may modify their types by setting -// missing receivers. To compute the method set of the interface, Complete must be called. +// NewInterfaceType returns a new interface for the given methods and embedded types. +// NewInterfaceType takes ownership of the provided methods and may modify their types +// by setting missing receivers. func NewInterfaceType(methods []*Func, embeddeds []Type) *Interface { if len(methods) == 0 && len(embeddeds) == 0 { return &emptyInterface @@ -331,6 +326,8 @@ func NewInterfaceType(methods []*Func, embeddeds []Type) *Interface { typ.methods = methods typ.embeddeds = embeddeds + typ.complete = true + return typ } @@ -354,72 +351,27 @@ func (t *Interface) Embedded(i int) *Named { tname, _ := t.embeddeds[i].(*Named) func (t *Interface) EmbeddedType(i int) Type { return t.embeddeds[i] } // NumMethods returns the total number of methods of interface t. -// The interface must have been completed. -func (t *Interface) NumMethods() int { t.Complete(); return len(t.allMethods) } +func (t *Interface) NumMethods() int { return t.typeSet().NumMethods() } // Method returns the i'th method of interface t for 0 <= i < t.NumMethods(). // The methods are ordered by their unique Id. -// The interface must have been completed. -func (t *Interface) Method(i int) *Func { t.Complete(); return t.allMethods[i] } +func (t *Interface) Method(i int) *Func { return t.typeSet().Method(i) } // Empty reports whether t is the empty interface. -func (t *Interface) Empty() bool { - t.Complete() - return len(t.allMethods) == 0 && t.allTypes == nil -} - -// HasTypeList reports whether interface t has a type list, possibly from an embedded type. -func (t *Interface) HasTypeList() bool { - t.Complete() - return t.allTypes != nil -} +func (t *Interface) Empty() bool { return t.typeSet().IsTop() } // IsComparable reports whether interface t is or embeds the predeclared interface "comparable". -func (t *Interface) IsComparable() bool { - t.Complete() - _, m := lookupMethod(t.allMethods, nil, "==") - return m != nil -} +func (t *Interface) IsComparable() bool { return t.typeSet().IsComparable() } -// IsConstraint reports t.HasTypeList() || t.IsComparable(). -func (t *Interface) IsConstraint() bool { - return t.HasTypeList() || t.IsComparable() -} - -// iterate calls f with t and then with any embedded interface of t, recursively, until f returns true. -// iterate reports whether any call to f returned true. -// TODO(gri) This is now only used by infer.go - see if we can eliminate it. -func (t *Interface) iterate(f func(*Interface) bool, seen map[*Interface]bool) bool { - if f(t) { - return true - } - for _, e := range t.embeddeds { - // e should be an interface but be careful (it may be invalid) - if e := asInterface(e); e != nil { - // Cyclic interfaces such as "type E interface { E }" are not permitted - // but they are still constructed and we need to detect such cycles. - if seen[e] { - continue - } - if seen == nil { - seen = make(map[*Interface]bool) - } - seen[e] = true - if e.iterate(f, seen) { - return true - } - } - } - return false -} +// IsConstraint reports whether interface t is not just a method set. +func (t *Interface) IsConstraint() bool { return !t.typeSet().IsMethodSet() } // isSatisfiedBy reports whether interface t's type list is satisfied by the type typ. // If the type list is empty (absent), typ trivially satisfies the interface. // TODO(gri) This is not a great name. Eventually, we should have a more comprehensive // "implements" predicate. func (t *Interface) isSatisfiedBy(typ Type) bool { - t.Complete() - switch t := t.allTypes.(type) { + switch t := t.typeSet().types.(type) { case nil: return true // no type restrictions case *Union: @@ -430,15 +382,22 @@ func (t *Interface) isSatisfiedBy(typ Type) bool { } } -// Complete computes the interface's method set. It must be called by users of +// Complete computes the interface's type set. It must be called by users of // NewInterfaceType and NewInterface after the interface's embedded types are // fully defined and before using the interface type in any way other than to // form other types. The interface must not contain duplicate methods or a // panic occurs. Complete returns the receiver. +// +// Deprecated: Type sets are now computed lazily, on demand; this function +// is only here for backward-compatibility. It does not have to +// be called explicitly anymore. func (t *Interface) Complete() *Interface { - if t.allMethods == nil { - completeInterface(nil, nopos, t) - } + // Some tests are still depending on the state change + // (string representation of an Interface not containing an + // /* incomplete */ marker) caused by the explicit Complete + // call, so we compute the type set eagerly here. + t.complete = true + t.typeSet() return t } @@ -674,7 +633,7 @@ func (t *TypeParam) Bound() *Interface { pos = n.obj.pos } // TODO(gri) switch this to an unexported method on Checker. - t.check.completeInterface(pos, iface) + newTypeSet(t.check, pos, iface) return iface } @@ -700,7 +659,7 @@ func optype(typ Type) Type { // for a type parameter list of the form: // (type T interface { type T }). // See also issue #39680. - if a := t.Bound().allTypes; a != nil { + if a := t.Bound().typeSet().types; a != nil { // If we have a union with a single entry, ignore // any tilde because under(~t) == under(t). if u, _ := a.(*Union); u != nil && u.NumTerms() == 1 { diff --git a/src/cmd/compile/internal/types2/typeset.go b/src/cmd/compile/internal/types2/typeset.go new file mode 100644 index 0000000000..44e5929413 --- /dev/null +++ b/src/cmd/compile/internal/types2/typeset.go @@ -0,0 +1,70 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package types2 + +import ( + "bytes" +) + +// topTypeSet may be used as type set for the empty interface. +var topTypeSet TypeSet + +// A TypeSet represents the type set of an interface. +type TypeSet struct { + // TODO(gri) consider using a set for the methods for faster lookup + methods []*Func // all methods of the interface; sorted by unique ID + types Type // typically a *Union; nil means no type restrictions +} + +func (s *TypeSet) String() string { + if s.IsTop() { + return "⊤" + } + + var buf bytes.Buffer + buf.WriteByte('{') + for i, m := range s.methods { + if i > 0 { + buf.WriteByte(';') + } + buf.WriteByte(' ') + buf.WriteString(m.String()) + } + if len(s.methods) > 0 && s.types != nil { + buf.WriteByte(';') + } + if s.types != nil { + buf.WriteByte(' ') + writeType(&buf, s.types, nil, nil) + } + + buf.WriteString(" }") // there was a least one method or type + return buf.String() +} + +// IsTop reports whether type set s is the top type set (corresponding to the empty interface). +func (s *TypeSet) IsTop() bool { return len(s.methods) == 0 && s.types == nil } + +// IsMethodSet reports whether the type set s is described by a single set of methods. +func (s *TypeSet) IsMethodSet() bool { return s.types == nil && !s.IsComparable() } + +// IsComparable reports whether each type in the set is comparable. +func (s *TypeSet) IsComparable() bool { + _, m := s.LookupMethod(nil, "==") + return m != nil +} + +// NumMethods returns the number of methods available. +func (s *TypeSet) NumMethods() int { return len(s.methods) } + +// Method returns the i'th method of type set s for 0 <= i < s.NumMethods(). +// The methods are ordered by their unique ID. +func (s *TypeSet) Method(i int) *Func { return s.methods[i] } + +// LookupMethod returns the index of and method with matching package and name, or (-1, nil). +func (s *TypeSet) LookupMethod(pkg *Package, name string) (int, *Func) { + // TODO(gri) s.methods is sorted - consider binary search + return lookupMethod(s.methods, pkg, name) +} diff --git a/src/cmd/compile/internal/types2/typestring.go b/src/cmd/compile/internal/types2/typestring.go index f08c41c2a3..4925252b39 100644 --- a/src/cmd/compile/internal/types2/typestring.go +++ b/src/cmd/compile/internal/types2/typestring.go @@ -189,7 +189,8 @@ func writeType(buf *bytes.Buffer, typ Type, qf Qualifier, visited []Type) { if gcCompatibilityMode { // print flattened interface // (useful to compare against gc-generated interfaces) - for i, m := range t.allMethods { + tset := t.typeSet() + for i, m := range tset.methods { if i > 0 { buf.WriteString("; ") } @@ -197,12 +198,12 @@ func writeType(buf *bytes.Buffer, typ Type, qf Qualifier, visited []Type) { writeSignature(buf, m.typ.(*Signature), qf, visited) empty = false } - if !empty && t.allTypes != nil { + if !empty && tset.types != nil { buf.WriteString("; ") } - if t.allTypes != nil { + if tset.types != nil { buf.WriteString("type ") - writeType(buf, t.allTypes, qf, visited) + writeType(buf, tset.types, qf, visited) } } else { // print explicit interface methods and embedded types @@ -225,7 +226,9 @@ func writeType(buf *bytes.Buffer, typ Type, qf Qualifier, visited []Type) { empty = false } } - if debug && (t.allMethods == nil || len(t.methods) > len(t.allMethods)) { + // print /* incomplete */ if needed to satisfy existing tests + // TODO(gri) get rid of this eventually + if debug && t.tset == nil { if !empty { buf.WriteByte(' ') } diff --git a/src/cmd/compile/internal/types2/typexpr.go b/src/cmd/compile/internal/types2/typexpr.go index 583bb464b2..fe676be2ef 100644 --- a/src/cmd/compile/internal/types2/typexpr.go +++ b/src/cmd/compile/internal/types2/typexpr.go @@ -141,12 +141,12 @@ func (check *Checker) ordinaryType(pos syntax.Pos, typ Type) { // interface methods. Delay this check to the end of type-checking. check.later(func() { if t := asInterface(typ); t != nil { - check.completeInterface(pos, t) // TODO(gri) is this the correct position? - if t.allTypes != nil { - check.softErrorf(pos, "interface contains type constraints (%s)", t.allTypes) + tset := newTypeSet(check, pos, t) // TODO(gri) is this the correct position? + if tset.types != nil { + check.softErrorf(pos, "interface contains type constraints (%s)", tset.types) return } - if t.IsComparable() { + if tset.IsComparable() { check.softErrorf(pos, "interface is (or embeds) comparable") } } diff --git a/src/cmd/compile/internal/types2/unify.go b/src/cmd/compile/internal/types2/unify.go index e5983dd40c..9a51dcb6d4 100644 --- a/src/cmd/compile/internal/types2/unify.go +++ b/src/cmd/compile/internal/types2/unify.go @@ -361,16 +361,8 @@ func (u *unifier) nify(x, y Type, p *ifacePair) bool { // the same names and identical function types. Lower-case method names from // different packages are always different. The order of the methods is irrelevant. if y, ok := y.(*Interface); ok { - // If identical0 is called (indirectly) via an external API entry point - // (such as Identical, IdenticalIgnoreTags, etc.), check is nil. But in - // that case, interfaces are expected to be complete and lazy completion - // here is not needed. - if u.check != nil { - u.check.completeInterface(nopos, x) - u.check.completeInterface(nopos, y) - } - a := x.allMethods - b := y.allMethods + a := x.typeSet().methods + b := y.typeSet().methods if len(a) == len(b) { // Interface types are the only types where cycles can occur // that are not "terminated" via named types; and such cycles diff --git a/src/cmd/compile/internal/types2/universe.go b/src/cmd/compile/internal/types2/universe.go index 76d4e55e84..2bcc49778e 100644 --- a/src/cmd/compile/internal/types2/universe.go +++ b/src/cmd/compile/internal/types2/universe.go @@ -88,7 +88,7 @@ func defPredeclaredTypes() { res := NewVar(nopos, nil, "", Typ[String]) sig := &Signature{results: NewTuple(res)} err := NewFunc(nopos, nil, "Error", sig) - typ := &Named{underlying: NewInterfaceType([]*Func{err}, nil).Complete()} + typ := &Named{underlying: NewInterfaceType([]*Func{err}, nil)} sig.recv = NewVar(nopos, nil, "", typ) def(NewTypeName(nopos, nil, "error", typ)) } @@ -216,7 +216,7 @@ func defPredeclaredComparable() { // set up later to match the usual interface method assumptions. sig := new(Signature) eql := NewFunc(nopos, nil, "==", sig) - iface := NewInterfaceType([]*Func{eql}, nil).Complete() + iface := NewInterfaceType([]*Func{eql}, nil) // set up the defined type for the interface obj := NewTypeName(nopos, nil, "comparable", nil)