]> Cypherpunks.ru repositories - gostls13.git/commitdiff
[dev.typeparams] go/types: introduce type set abstraction for interfaces
authorRob Findley <rfindley@google.com>
Fri, 16 Jul 2021 02:49:00 +0000 (22:49 -0400)
committerRobert Findley <rfindley@google.com>
Fri, 16 Jul 2021 23:04:35 +0000 (23:04 +0000)
This is a port of CL 329309 to go/types, with minor updates for API
differences and to handle methodset.go, which doesn't exist in types2.

A couple pre-existing comments were adjusted to match types2.

Change-Id: I3fd556e1326013a694ff5edb8518ca24c27bd10b
Reviewed-on: https://go-review.googlesource.com/c/go/+/334894
Trust: Robert Findley <rfindley@google.com>
Run-TryBot: Robert Findley <rfindley@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Robert Griesemer <gri@golang.org>
19 files changed:
src/go/types/api_typeparams.go
src/go/types/builtins.go
src/go/types/call.go
src/go/types/expr.go
src/go/types/infer.go
src/go/types/interface.go
src/go/types/lookup.go
src/go/types/methodset.go
src/go/types/predicates.go
src/go/types/sanitize.go
src/go/types/sizeof_test.go
src/go/types/subst.go
src/go/types/testdata/check/cycles4.src
src/go/types/type.go
src/go/types/typeset.go [new file with mode: 0644]
src/go/types/typestring.go
src/go/types/typexpr.go
src/go/types/unify.go
src/go/types/universe.go

index 6aaefbb6b29af89c0c3e6a5789dadde88f85c44c..864103df6375b781f165284c824d5fa51d6b8776 100644 (file)
@@ -21,10 +21,6 @@ func NewTypeParam(obj *TypeName, index int, bound Type) *TypeParam {
 func (s *Signature) TParams() []*TypeName           { return s._TParams() }
 func (s *Signature) SetTParams(tparams []*TypeName) { s._SetTParams(tparams) }
 
-func (t *Interface) HasTypeList() bool  { return t._HasTypeList() }
-func (t *Interface) IsComparable() bool { return t._IsComparable() }
-func (t *Interface) IsConstraint() bool { return t._IsConstraint() }
-
 func (t *Named) TParams() []*TypeName { return t._TParams() }
 func (t *Named) TArgs() []Type        { return t._TArgs() }
 func (t *Named) SetTArgs(args []Type) { t._SetTArgs(args) }
index cfaeab611b0bd126a7e7b3a949d1f46851ed5a35..5670790856a12530dbcf057b5f516078a80750aa 100644 (file)
@@ -785,7 +785,7 @@ func (check *Checker) applyTypeFunc(f func(Type) Type, x Type) Type {
                tpar := NewTypeName(token.NoPos, nil /* = Universe pkg */, "<type parameter>", 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
        }
index 337ee741c6610c8438f65e7d2252decb9dcf6965..cef5e9fc590a4fa3819ba46365c0e4dbba7f79c6 100644 (file)
@@ -109,8 +109,7 @@ func (check *Checker) callExpr(x *operand, call *ast.CallExpr) exprKind {
                                        break
                                }
                                if t := asInterface(T); t != nil {
-                                       check.completeInterface(token.NoPos, t)
-                                       if t._IsConstraint() {
+                                       if t.IsConstraint() {
                                                check.errorf(call, _Todo, "cannot use interface %s in conversion (contains type list or is comparable)", T)
                                                break
                                        }
index 95f2a8d6ab5d3f44b9962be6f60ebb5452782112..c8adea45e29c9d63ffb73bf0817ec0c6af52748d 100644 (file)
@@ -682,7 +682,6 @@ func (check *Checker) implicitTypeAndValue(x *operand, target Type) (Type, const
                        return Typ[UntypedNil], nil, 0
                }
                // cannot assign untyped values to non-empty interfaces
-               check.completeInterface(token.NoPos, t)
                if !t.Empty() {
                        return nil, nil, _InvalidUntypedConversion
                }
index 5a4f939bb131a7a1fe69c5cfeab76e89be80a144..ae53f68e48a395c845b49ed50b0a663291f934f0 100644 (file)
@@ -316,24 +316,13 @@ func (w *tpWalker) isParameterized(typ Type) (res bool) {
                return w.isParameterized(t.params) || w.isParameterized(t.results)
 
        case *Interface:
-               if t.allMethods != nil {
-                       // TODO(rFindley) at some point we should enforce completeness here
-                       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)
@@ -471,15 +460,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(token.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
 }
index 947e76dc17eb634041b80b305d469da28112e0a1..3a4da569ab23c72692848d6bf01b779feac48ff2 100644 (file)
@@ -98,9 +98,13 @@ func (check *Checker) interfaceType(ityp *Interface, iface *ast.InterfaceType, d
                check.posMap[ityp] = append(check.posMap[ityp], tlist[0].(*ast.UnaryExpr).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
        }
 
@@ -108,7 +112,10 @@ func (check *Checker) interfaceType(ityp *Interface, iface *ast.InterfaceType, d
        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 []ast.Expr, x ast.Expr) []ast.Expr {
@@ -119,24 +126,26 @@ func flattenUnion(list []ast.Expr, x ast.Expr) []ast.Expr {
        return append(list, x)
 }
 
-func (check *Checker) completeInterface(pos token.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 token.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 token.Pos, ityp *Interface) {
-       assert(ityp.allMethods == nil)
 
        if check != nil && trace {
                // Types don't generally have position information.
@@ -146,11 +155,11 @@ func completeInterface(check *Checker, pos token.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())
                }()
        }
 
@@ -159,7 +168,7 @@ func completeInterface(check *Checker, pos token.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
@@ -229,14 +238,12 @@ func completeInterface(check *Checker, pos token.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
@@ -273,9 +280,11 @@ func completeInterface(check *Checker, pos token.Pos, ityp *Interface) {
 
        if methods != nil {
                sort.Sort(byUniqueMethodName(methods))
-               ityp.allMethods = methods
+               ityp.tset.methods = methods
        }
-       ityp.allTypes = allTypes
+       ityp.tset.types = allTypes
+
+       return ityp.tset
 }
 
 func sortTypes(list []Type) {
index 5b22c4744e453e57be27f5ce6e3584362ee4addf..4ce4b3217c94a77b9d313206594976872566a367 100644 (file)
@@ -186,9 +186,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(token.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 {
@@ -199,9 +197,7 @@ func (check *Checker) rawLookupFieldOrMethod(T Type, addressable bool, pkg *Pack
                                }
 
                        case *_TypeParam:
-                               // only consider explicit methods in the type parameter bound, not
-                               // methods that may be common to all types in the type list.
-                               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 +303,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(token.NoPos, T)
-
        // fast path for common case
        if T.Empty() {
                return
        }
 
        if ityp := asInterface(V); ityp != nil {
-               check.completeInterface(token.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 +349,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)
 
index ae8011a2eeef92f8246631d13cf24d1cc5780504..71d634bf36e8a9d717218c9736f08e921c6ace77 100644 (file)
@@ -157,10 +157,10 @@ func NewMethodSet(T Type) *MethodSet {
                                }
 
                        case *Interface:
-                               mset = mset.add(t.allMethods, e.index, true, e.multiples)
+                               mset = mset.add(t.typeSet().methods, e.index, true, e.multiples)
 
                        case *_TypeParam:
-                               mset = mset.add(t.Bound().allMethods, e.index, true, e.multiples)
+                               mset = mset.add(t.Bound().typeSet().methods, e.index, true, e.multiples)
                        }
                }
 
index 9f3e3245978449e7db3276c9d623b4cc4ec5181f..7f6eee812090ca3c5ae69ce827757c23d0502dbd 100644 (file)
@@ -6,10 +6,6 @@
 
 package types
 
-import (
-       "go/token"
-)
-
 // isNamed reports whether typ has a name.
 // isNamed may be called with types that are not fully set up.
 func isNamed(typ Type) bool {
@@ -109,7 +105,7 @@ func comparable(T Type, seen map[Type]bool) bool {
        //
        // is not comparable because []byte is not comparable.
        if t := asTypeParam(T); t != nil && optype(t) == theTop {
-               return t.Bound()._IsComparable()
+               return t.Bound().IsComparable()
        }
 
        switch t := optype(T).(type) {
@@ -133,7 +129,7 @@ func comparable(T Type, seen map[Type]bool) bool {
                        return comparable(t, seen)
                })
        case *_TypeParam:
-               return t.Bound()._IsComparable()
+               return t.Bound().IsComparable()
        }
        return false
 }
@@ -291,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(token.NoPos, x)
-                               check.completeInterface(token.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
index f54ab68624bddbbdd4d3ae92c786f0fe60142418..df09a6a38f6288b9d361e85c7641a3423dc838cf 100644 (file)
@@ -113,9 +113,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:
index 9710edab15bf37fa3074601866770e786b2aff76..05a171f49894e394756255f7a78c406abf365bb5 100644 (file)
@@ -27,7 +27,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},
@@ -48,6 +48,7 @@ func TestSizeof(t *testing.T) {
                // Misc
                {Scope{}, 40, 80},
                {Package{}, 40, 80},
+               {TypeSet{}, 20, 40},
        }
        for _, test := range tests {
                got := reflect.TypeOf(test.val).Size()
index dc30bfbe673b3c1e7ae9a80323108e7208f4e186..0e2e7f408acdc7533b8356a1f73184304c597f7e 100644 (file)
@@ -139,6 +139,7 @@ func (check *Checker) instantiate(pos token.Pos, typ Type, targs []Type, poslist
 // 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 token.Pos, targ Type, tpar *_TypeParam, smap *substMap) bool {
        iface := tpar.Bound()
        if iface.Empty() {
@@ -153,8 +154,7 @@ func (check *Checker) satisfies(pos token.Pos, targ Type, tpar *_TypeParam, smap
 
        // targ must implement iface (methods)
        // - check only if we have methods
-       check.completeInterface(token.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)
@@ -186,7 +186,7 @@ func (check *Checker) satisfies(pos token.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
        }
 
@@ -194,7 +194,7 @@ func (check *Checker) satisfies(pos token.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(atPos(pos), _Todo, "%s does not satisfy %s (%s has no type constraints)", targ, tpar.bound, targ)
                        return false
                }
@@ -202,7 +202,7 @@ func (check *Checker) satisfies(pos token.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(atPos(pos), 0, "%s does not satisfy %s (%s type constraint %s not found in %s)", targ, tpar.bound, targ, typ, iface.allTypes)
+                               check.softErrorf(atPos(pos), 0, "%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
@@ -211,7 +211,7 @@ func (check *Checker) satisfies(pos token.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(atPos(pos), _Todo, "%s does not satisfy %s (%s not found in %s)", targ, tpar.bound, targ, iface.allTypes)
+               check.softErrorf(atPos(pos), _Todo, "%s does not satisfy %s (%s not found in %s)", targ, tpar.bound, targ, iface.typeSet().types)
                return false
        }
 
@@ -316,12 +316,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(token.NoPos, iface)
                        return iface
                }
 
index 445babca68bc3db73a1cc192f366480171fbc659..924aabf475f9640cdfa6da8834fa9a8db6aa0a2b 100644 (file)
@@ -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
index d555a8f684d52cbe8a2a1140af744722bc8b5213..4dcc511b9317a5b7f38a7e525509302469d6c0f7 100644 (file)
@@ -258,18 +258,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, token.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)
@@ -281,20 +283,13 @@ func (t *Interface) is(f func(Type, bool) bool) bool {
 }
 
 // emptyInterface represents the empty (completed) interface
-var emptyInterface = Interface{allMethods: markComplete}
-
-// 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)
+var emptyInterface = Interface{complete: true, tset: &topTypeSet}
 
-// 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 {
@@ -303,12 +298,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.
-// Each embedded type must have an underlying type of interface type (this property is not
-// verified for defined types, which may be in the process of being set up and which don't
-// have a valid underlying type yet).
-// 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
@@ -338,6 +330,8 @@ func NewInterfaceType(methods []*Func, embeddeds []Type) *Interface {
 
        typ.methods = methods
        typ.embeddeds = embeddeds
+       typ.complete = true
+
        return typ
 }
 
@@ -361,64 +355,20 @@ 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
-}
-
-// _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) Empty() bool { return t.typeSet().IsTop() }
 
-// _IsConstraint reports t.HasTypeList() || t.IsComparable().
-func (t *Interface) _IsConstraint() bool {
-       return t._HasTypeList() || t._IsComparable()
-}
+// IsComparable reports whether interface t is or embeds the predeclared interface "comparable".
+func (t *Interface) IsComparable() bool { return t.typeSet().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(rfindley) 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.
@@ -426,7 +376,7 @@ func (t *Interface) iterate(f func(*Interface) bool, seen map[*Interface]bool) b
 //           "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:
@@ -437,15 +387,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, token.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
 }
 
@@ -668,7 +625,7 @@ func (t *_TypeParam) Bound() *Interface {
                pos = n.obj.pos
        }
        // TODO(rFindley) switch this to an unexported method on Checker.
-       t.check.completeInterface(pos, iface)
+       newTypeSet(t.check, pos, iface)
        return iface
 }
 
@@ -685,7 +642,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 && a != typ {
+               if a := t.Bound().typeSet().types; a != nil && a != typ {
                        // 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/go/types/typeset.go b/src/go/types/typeset.go
new file mode 100644 (file)
index 0000000..9ba04b9
--- /dev/null
@@ -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 types
+
+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)
+}
index 79b4f74ff3e52c6b521ca2b63cb2f1b1b5d26010..fb398de502a3ab40cd4a525ab81f926519fb442a 100644 (file)
@@ -190,7 +190,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("; ")
                                }
@@ -198,12 +199,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
@@ -226,7 +227,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(' ')
                        }
index 249a3ac5c5c6bbfcb7593fe6ef649e210cd09696..070b0ade3e8220fcbeb27c8efed43fffd093b7f9 100644 (file)
@@ -140,12 +140,12 @@ func (check *Checker) ordinaryType(pos positioner, typ Type) {
        // type-checking.
        check.later(func() {
                if t := asInterface(typ); t != nil {
-                       check.completeInterface(pos.Pos(), t) // TODO(gri) is this the correct position?
-                       if t.allTypes != nil {
-                               check.softErrorf(pos, _Todo, "interface contains type constraints (%s)", t.allTypes)
+                       tset := newTypeSet(check, pos.Pos(), t) // TODO(gri) is this the correct position?
+                       if tset.types != nil {
+                               check.softErrorf(pos, _Todo, "interface contains type constraints (%s)", tset.types)
                                return
                        }
-                       if t._IsComparable() {
+                       if tset.IsComparable() {
                                check.softErrorf(pos, _Todo, "interface is (or embeds) comparable")
                        }
                }
index 7c58c6c51280501038f50f2aceca4d909f27b304..bc611db347955e0401d64ffd55cd21f44e9bc7df 100644 (file)
@@ -8,7 +8,6 @@ package types
 
 import (
        "bytes"
-       "go/token"
        "sort"
 )
 
@@ -361,16 +360,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(token.NoPos, x)
-                               u.check.completeInterface(token.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
index d7feb2c609efa93829bc90bd9176d1d9a423bf94..7ce401827e35a62412a8c4aec9d7b5b08c0dff27 100644 (file)
@@ -90,7 +90,7 @@ func defPredeclaredTypes() {
                res := NewVar(token.NoPos, nil, "", Typ[String])
                sig := &Signature{results: NewTuple(res)}
                err := NewFunc(token.NoPos, nil, "Error", sig)
-               typ := &Named{underlying: NewInterfaceType([]*Func{err}, nil).Complete()}
+               typ := &Named{underlying: NewInterfaceType([]*Func{err}, nil)}
                sig.recv = NewVar(token.NoPos, nil, "", typ)
                def(NewTypeName(token.NoPos, nil, "error", typ))
        }
@@ -218,7 +218,7 @@ func defPredeclaredComparable() {
        // set up later to match the usual interface method assumptions.
        sig := new(Signature)
        eql := NewFunc(token.NoPos, nil, "==", sig)
-       iface := NewInterfaceType([]*Func{eql}, nil).Complete()
+       iface := NewInterfaceType([]*Func{eql}, nil)
 
        // set up the defined type for the interface
        obj := NewTypeName(token.NoPos, nil, "comparable", nil)