[dev.typeparams] go/types: introduce type set abstraction for interfaces

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>
This commit is contained in:
Rob Findley 2021-07-15 22:49:00 -04:00 committed by Robert Findley
parent b296e54618
commit 24f9eb2de3
19 changed files with 210 additions and 203 deletions

View 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) TParams() []*TypeName { return s._TParams() }
func (s *Signature) SetTParams(tparams []*TypeName) { s._SetTParams(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) TParams() []*TypeName { return t._TParams() }
func (t *Named) TArgs() []Type { return t._TArgs() } func (t *Named) TArgs() []Type { return t._TArgs() }
func (t *Named) SetTArgs(args []Type) { t._SetTArgs(args) } func (t *Named) SetTArgs(args []Type) { t._SetTArgs(args) }

View 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) tpar := NewTypeName(token.NoPos, nil /* = Universe pkg */, "<type parameter>", nil)
ptyp := check.newTypeParam(tpar, 0, &emptyInterface) // assigns type to tpar as a side-effect ptyp := check.newTypeParam(tpar, 0, &emptyInterface) // assigns type to tpar as a side-effect
tsum := newUnion(rtypes, tildes) tsum := newUnion(rtypes, tildes)
ptyp.bound = &Interface{allMethods: markComplete, allTypes: tsum} ptyp.bound = &Interface{complete: true, tset: &TypeSet{types: tsum}}
return ptyp return ptyp
} }

View file

@ -109,8 +109,7 @@ func (check *Checker) callExpr(x *operand, call *ast.CallExpr) exprKind {
break break
} }
if t := asInterface(T); t != nil { 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) check.errorf(call, _Todo, "cannot use interface %s in conversion (contains type list or is comparable)", T)
break break
} }

View file

@ -682,7 +682,6 @@ func (check *Checker) implicitTypeAndValue(x *operand, target Type) (Type, const
return Typ[UntypedNil], nil, 0 return Typ[UntypedNil], nil, 0
} }
// cannot assign untyped values to non-empty interfaces // cannot assign untyped values to non-empty interfaces
check.completeInterface(token.NoPos, t)
if !t.Empty() { if !t.Empty() {
return nil, nil, _InvalidUntypedConversion return nil, nil, _InvalidUntypedConversion
} }

View file

@ -316,24 +316,13 @@ func (w *tpWalker) isParameterized(typ Type) (res bool) {
return w.isParameterized(t.params) || w.isParameterized(t.results) return w.isParameterized(t.params) || w.isParameterized(t.results)
case *Interface: case *Interface:
if t.allMethods != nil { tset := t.typeSet()
// TODO(rFindley) at some point we should enforce completeness here for _, m := range tset.methods {
for _, m := range t.allMethods { if w.isParameterized(m.typ) {
if w.isParameterized(m.typ) { return true
return true
}
} }
return w.isParameterized(t.allTypes)
} }
return w.isParameterized(tset.types)
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)
case *Map: case *Map:
return w.isParameterized(t.key) || w.isParameterized(t.elem) 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. // structuralType returns the structural type of a constraint, if any.
func (check *Checker) structuralType(constraint Type) Type { func (check *Checker) structuralType(constraint Type) Type {
if iface, _ := under(constraint).(*Interface); iface != nil { if iface, _ := under(constraint).(*Interface); iface != nil {
check.completeInterface(token.NoPos, iface) types := iface.typeSet().types
if u, _ := iface.allTypes.(*Union); u != nil { if u, _ := types.(*Union); u != nil {
if u.NumTerms() == 1 { if u.NumTerms() == 1 {
// TODO(gri) do we need to respect tilde? // TODO(gri) do we need to respect tilde?
return u.types[0] return u.types[0]
} }
return nil return nil
} }
return iface.allTypes return types
} }
return nil return nil
} }

View 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()) 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 { if len(ityp.methods) == 0 && len(ityp.embeddeds) == 0 {
// empty interface // empty interface
ityp.allMethods = markComplete ityp.tset = &topTypeSet
return return
} }
@ -108,7 +112,10 @@ func (check *Checker) interfaceType(ityp *Interface, iface *ast.InterfaceType, d
sortMethods(ityp.methods) sortMethods(ityp.methods)
sortTypes(ityp.embeddeds) 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 { 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) return append(list, x)
} }
func (check *Checker) completeInterface(pos token.Pos, ityp *Interface) { // newTypeSet may be called with check == nil.
if ityp.allMethods != nil { // TODO(gri) move this function into typeset.go eventually
return func newTypeSet(check *Checker, pos token.Pos, ityp *Interface) *TypeSet {
if ityp.tset != nil {
return ityp.tset
} }
// completeInterface may be called via the LookupFieldOrMethod, // If the interface is not fully set up yet, the type set will
// MissingMethod, Identical, or IdenticalIgnoreTags external API // not be complete, which may lead to errors when using the the
// in which case check will be nil. In this case, type-checking // type set (e.g. missing method). Don't compute a partial type
// must be finished and all interfaces should have been completed. // set (and don't store it!), so that we still compute the full
if check == nil { // type set eventually. Instead, return the top type set and
panic("internal error: incomplete interface") // 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 { if check != nil && trace {
// Types don't generally have position information. // 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 pos = ityp.methods[0].pos
} }
check.trace(pos, "complete %s", ityp) check.trace(pos, "type set for %s", ityp)
check.indent++ check.indent++
defer func() { defer func() {
check.indent-- 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 // have valid interfaces. Mark the interface as complete to avoid
// infinite recursion if the validType check occurs later for some // infinite recursion if the validType check occurs later for some
// reason. // reason.
ityp.allMethods = markComplete ityp.tset = new(TypeSet) // TODO(gri) is this sufficient?
// Methods of embedded interfaces are collected unchanged; i.e., the identity // 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 // 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 var types Type
switch t := under(typ).(type) { switch t := under(typ).(type) {
case *Interface: case *Interface:
if t.allMethods == nil { tset := newTypeSet(check, pos, t)
completeInterface(check, pos, t) for _, m := range tset.methods {
}
for _, m := range t.allMethods {
addMethod(pos, m, false) // use embedding position pos rather than m.pos addMethod(pos, m, false) // use embedding position pos rather than m.pos
} }
types = t.allTypes types = tset.types
case *Union: case *Union:
// TODO(gri) combine with default case once we have // TODO(gri) combine with default case once we have
// converted all tests to new notation and we // converted all tests to new notation and we
@ -273,9 +280,11 @@ func completeInterface(check *Checker, pos token.Pos, ityp *Interface) {
if methods != nil { if methods != nil {
sort.Sort(byUniqueMethodName(methods)) 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) { func sortTypes(list []Type) {

View file

@ -186,9 +186,7 @@ func (check *Checker) rawLookupFieldOrMethod(T Type, addressable bool, pkg *Pack
case *Interface: case *Interface:
// look for a matching method // look for a matching method
// TODO(gri) t.allMethods is sorted - use binary search if i, m := t.typeSet().LookupMethod(pkg, name); m != nil {
check.completeInterface(token.NoPos, t)
if i, m := lookupMethod(t.allMethods, pkg, name); m != nil {
assert(m.typ != nil) assert(m.typ != nil)
index = concat(e.index, i) index = concat(e.index, i)
if obj != nil || e.multiples { if obj != nil || e.multiples {
@ -199,9 +197,7 @@ func (check *Checker) rawLookupFieldOrMethod(T Type, addressable bool, pkg *Pack
} }
case *_TypeParam: case *_TypeParam:
// only consider explicit methods in the type parameter bound, not if i, m := t.Bound().typeSet().LookupMethod(pkg, name); m != nil {
// methods that may be common to all types in the type list.
if i, m := lookupMethod(t.Bound().allMethods, pkg, name); m != nil {
assert(m.typ != nil) assert(m.typ != nil)
index = concat(e.index, i) index = concat(e.index, i)
if obj != nil || e.multiples { 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 // To improve error messages, also report the wrong signature
// when the method exists on *V instead of V. // when the method exists on *V instead of V.
func (check *Checker) missingMethod(V Type, T *Interface, static bool) (method, wrongType *Func) { func (check *Checker) missingMethod(V Type, T *Interface, static bool) (method, wrongType *Func) {
check.completeInterface(token.NoPos, T)
// fast path for common case // fast path for common case
if T.Empty() { if T.Empty() {
return return
} }
if ityp := asInterface(V); ityp != nil { if ityp := asInterface(V); ityp != nil {
check.completeInterface(token.NoPos, ityp) // TODO(gri) the methods are sorted - could do this more efficiently
// TODO(gri) allMethods is sorted - can do this more efficiently for _, m := range T.typeSet().methods {
for _, m := range T.allMethods { _, f := ityp.typeSet().LookupMethod(m.pkg, m.name)
_, f := lookupMethod(ityp.allMethods, m.pkg, m.name)
if f == nil { if f == nil {
// if m is the magic method == we're ok (interfaces are comparable) // 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. // A concrete type implements T if it implements all methods of T.
Vd, _ := deref(V) Vd, _ := deref(V)
Vn := asNamed(Vd) 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)? // TODO(gri) should this be calling lookupFieldOrMethod instead (and why not)?
obj, _, _ := check.rawLookupFieldOrMethod(V, false, m.pkg, m.name) obj, _, _ := check.rawLookupFieldOrMethod(V, false, m.pkg, m.name)

View file

@ -157,10 +157,10 @@ func NewMethodSet(T Type) *MethodSet {
} }
case *Interface: 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: 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)
} }
} }

View file

@ -6,10 +6,6 @@
package types package types
import (
"go/token"
)
// isNamed reports whether typ has a name. // isNamed reports whether typ has a name.
// isNamed may be called with types that are not fully set up. // isNamed may be called with types that are not fully set up.
func isNamed(typ Type) bool { 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. // is not comparable because []byte is not comparable.
if t := asTypeParam(T); t != nil && optype(t) == theTop { if t := asTypeParam(T); t != nil && optype(t) == theTop {
return t.Bound()._IsComparable() return t.Bound().IsComparable()
} }
switch t := optype(T).(type) { switch t := optype(T).(type) {
@ -133,7 +129,7 @@ func comparable(T Type, seen map[Type]bool) bool {
return comparable(t, seen) return comparable(t, seen)
}) })
case *_TypeParam: case *_TypeParam:
return t.Bound()._IsComparable() return t.Bound().IsComparable()
} }
return false 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 // the same names and identical function types. Lower-case method names from
// different packages are always different. The order of the methods is irrelevant. // different packages are always different. The order of the methods is irrelevant.
if y, ok := y.(*Interface); ok { if y, ok := y.(*Interface); ok {
// If identical0 is called (indirectly) via an external API entry point a := x.typeSet().methods
// (such as Identical, IdenticalIgnoreTags, etc.), check is nil. But in b := y.typeSet().methods
// 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
if len(a) == len(b) { if len(a) == len(b) {
// Interface types are the only types where cycles can occur // Interface types are the only types where cycles can occur
// that are not "terminated" via named types; and such cycles // that are not "terminated" via named types; and such cycles

View file

@ -113,9 +113,11 @@ func (s sanitizer) typ(typ Type) Type {
case *Interface: case *Interface:
s.funcList(t.methods) s.funcList(t.methods)
s.typeList(t.embeddeds) s.typeList(t.embeddeds)
s.funcList(t.allMethods) // TODO(gri) do we need to sanitize type sets?
if allTypes := s.typ(t.allTypes); allTypes != t.allTypes { tset := t.typeSet()
t.allTypes = allTypes s.funcList(tset.methods)
if types := s.typ(tset.types); types != tset.types {
tset.types = types
} }
case *Map: case *Map:

View file

@ -27,7 +27,7 @@ func TestSizeof(t *testing.T) {
{Tuple{}, 12, 24}, {Tuple{}, 12, 24},
{Signature{}, 44, 88}, {Signature{}, 44, 88},
{Union{}, 24, 48}, {Union{}, 24, 48},
{Interface{}, 52, 104}, {Interface{}, 40, 80},
{Map{}, 16, 32}, {Map{}, 16, 32},
{Chan{}, 12, 24}, {Chan{}, 12, 24},
{Named{}, 84, 160}, {Named{}, 84, 160},
@ -48,6 +48,7 @@ func TestSizeof(t *testing.T) {
// Misc // Misc
{Scope{}, 40, 80}, {Scope{}, 40, 80},
{Package{}, 40, 80}, {Package{}, 40, 80},
{TypeSet{}, 20, 40},
} }
for _, test := range tests { for _, test := range tests {
got := reflect.TypeOf(test.val).Size() got := reflect.TypeOf(test.val).Size()

View 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 // 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). // parameter tpar (after any of its type parameters have been substituted through smap).
// A suitable error is reported if the result is false. // 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 { func (check *Checker) satisfies(pos token.Pos, targ Type, tpar *_TypeParam, smap *substMap) bool {
iface := tpar.Bound() iface := tpar.Bound()
if iface.Empty() { if iface.Empty() {
@ -153,8 +154,7 @@ func (check *Checker) satisfies(pos token.Pos, targ Type, tpar *_TypeParam, smap
// targ must implement iface (methods) // targ must implement iface (methods)
// - check only if we have methods // - check only if we have methods
check.completeInterface(token.NoPos, iface) if iface.NumMethods() > 0 {
if len(iface.allMethods) > 0 {
// If the type argument is a pointer to a type parameter, the type argument's // If the type argument is a pointer to a type parameter, the type argument's
// method set is empty. // method set is empty.
// TODO(gri) is this what we want? (spec question) // 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 // 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 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). // 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 { if targ := asTypeParam(targ); targ != nil {
targBound := targ.Bound() 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) check.softErrorf(atPos(pos), _Todo, "%s does not satisfy %s (%s has no type constraints)", targ, tpar.bound, targ)
return false return false
} }
@ -202,7 +202,7 @@ func (check *Checker) satisfies(pos token.Pos, targ Type, tpar *_TypeParam, smap
// TODO(gri) incorporate tilde information! // TODO(gri) incorporate tilde information!
if !iface.isSatisfiedBy(typ) { if !iface.isSatisfiedBy(typ) {
// TODO(gri) match this error message with the one below (or vice versa) // 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 false
} }
return true 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. // Otherwise, targ's type or underlying type must also be one of the interface types listed, if any.
if !iface.isSatisfiedBy(targ) { 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 return false
} }
@ -316,12 +316,11 @@ func (subst *subster) typ(typ Type) Type {
methods, mcopied := subst.funcList(t.methods) methods, mcopied := subst.funcList(t.methods)
embeddeds, ecopied := subst.typeList(t.embeddeds) embeddeds, ecopied := subst.typeList(t.embeddeds)
if mcopied || ecopied { if mcopied || ecopied {
iface := &Interface{methods: methods, embeddeds: embeddeds} iface := &Interface{methods: methods, embeddeds: embeddeds, complete: t.complete}
if subst.check == nil { if subst.check == nil {
panic("internal error: cannot instantiate interfaces yet") panic("internal error: cannot instantiate interfaces yet")
} }
subst.check.posMap[iface] = subst.check.posMap[t] // satisfy completeInterface requirement subst.check.posMap[iface] = subst.check.posMap[t] // satisfy completeInterface requirement
subst.check.completeInterface(token.NoPos, iface)
return iface return iface
} }

View file

@ -4,6 +4,8 @@
package p package p
import "unsafe"
// Check that all methods of T are collected before // Check that all methods of T are collected before
// determining the result type of m (which embeds // determining the result type of m (which embeds
// all methods of T). // all methods of T).
@ -13,7 +15,7 @@ type T interface {
E E
} }
var _ = T.m(nil).m().e() var _ int = T.m(nil).m().e()
type E interface { type E interface {
e() int e() int
@ -22,7 +24,7 @@ type E interface {
// Check that unresolved forward chains are followed // Check that unresolved forward chains are followed
// (see also comment in resolver.go, checker.typeDecl). // (see also comment in resolver.go, checker.typeDecl).
var _ = C.m(nil).m().e() var _ int = C.m(nil).m().e()
type A B type A B
@ -108,3 +110,12 @@ type Element interface {
type Event interface { type Event interface {
Target() Element 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

View file

@ -258,18 +258,20 @@ func (s *Signature) Variadic() bool { return s.variadic }
// An Interface represents an interface type. // An Interface represents an interface type.
type Interface struct { 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 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) tset *TypeSet // type set described by this interface, computed lazily
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)
} }
// 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. // is reports whether interface t represents types that all satisfy f.
func (t *Interface) is(f func(Type, bool) bool) bool { func (t *Interface) is(f func(Type, bool) bool) bool {
switch t := t.allTypes.(type) { switch t := t.typeSet().types.(type) {
case nil, *top: case nil, *top:
// TODO(gri) should settle on top or nil to represent this case // TODO(gri) should settle on top or nil to represent this case
return false // we must have at least one type! (was bug) 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 // emptyInterface represents the empty (completed) interface
var emptyInterface = Interface{allMethods: markComplete} var emptyInterface = Interface{complete: true, tset: &topTypeSet}
// markComplete is used to mark an empty interface as completely // NewInterface returns a new interface for the given methods and embedded types.
// set up by setting the allMethods field to a non-nil empty slice. // NewInterface takes ownership of the provided methods and may modify their types
var markComplete = make([]*Func, 0) // by setting missing receivers.
// 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.
// //
// Deprecated: Use NewInterfaceType instead which allows any (even non-defined) interface types // Deprecated: Use NewInterfaceType instead which allows arbitrary embedded types.
// to be embedded. This is necessary for interfaces that embed alias type names referring to
// non-defined (literal) interface types.
func NewInterface(methods []*Func, embeddeds []*Named) *Interface { func NewInterface(methods []*Func, embeddeds []*Named) *Interface {
tnames := make([]Type, len(embeddeds)) tnames := make([]Type, len(embeddeds))
for i, t := range embeddeds { for i, t := range embeddeds {
@ -303,12 +298,9 @@ func NewInterface(methods []*Func, embeddeds []*Named) *Interface {
return NewInterfaceType(methods, tnames) return NewInterfaceType(methods, tnames)
} }
// NewInterfaceType returns a new (incomplete) interface for the given methods and embedded types. // NewInterfaceType returns a new interface for the given methods and embedded types.
// Each embedded type must have an underlying type of interface type (this property is not // NewInterfaceType takes ownership of the provided methods and may modify their types
// verified for defined types, which may be in the process of being set up and which don't // by setting missing receivers.
// 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.
func NewInterfaceType(methods []*Func, embeddeds []Type) *Interface { func NewInterfaceType(methods []*Func, embeddeds []Type) *Interface {
if len(methods) == 0 && len(embeddeds) == 0 { if len(methods) == 0 && len(embeddeds) == 0 {
return &emptyInterface return &emptyInterface
@ -338,6 +330,8 @@ func NewInterfaceType(methods []*Func, embeddeds []Type) *Interface {
typ.methods = methods typ.methods = methods
typ.embeddeds = embeddeds typ.embeddeds = embeddeds
typ.complete = true
return typ 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] } func (t *Interface) EmbeddedType(i int) Type { return t.embeddeds[i] }
// NumMethods returns the total number of methods of interface t. // NumMethods returns the total number of methods of interface t.
// The interface must have been completed. func (t *Interface) NumMethods() int { return t.typeSet().NumMethods() }
func (t *Interface) NumMethods() int { t.Complete(); return len(t.allMethods) }
// Method returns the i'th method of interface t for 0 <= i < t.NumMethods(). // Method returns the i'th method of interface t for 0 <= i < t.NumMethods().
// The methods are ordered by their unique Id. // The methods are ordered by their unique Id.
// The interface must have been completed. func (t *Interface) Method(i int) *Func { return t.typeSet().Method(i) }
func (t *Interface) Method(i int) *Func { t.Complete(); return t.allMethods[i] }
// Empty reports whether t is the empty interface. // Empty reports whether t is the empty interface.
func (t *Interface) Empty() bool { func (t *Interface) Empty() bool { return t.typeSet().IsTop() }
t.Complete()
return len(t.allMethods) == 0 && t.allTypes == nil
}
// _HasTypeList reports whether interface t has a type list, possibly from an embedded type. // IsComparable reports whether interface t is or embeds the predeclared interface "comparable".
func (t *Interface) _HasTypeList() bool { func (t *Interface) IsComparable() bool { return t.typeSet().IsComparable() }
t.Complete()
return t.allTypes != nil
}
// _IsComparable reports whether interface t is or embeds the predeclared interface "comparable". // IsConstraint reports whether interface t is not just a method set.
func (t *Interface) _IsComparable() bool { func (t *Interface) IsConstraint() bool { return !t.typeSet().IsMethodSet() }
t.Complete()
_, m := lookupMethod(t.allMethods, nil, "==")
return m != nil
}
// _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(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
}
// isSatisfiedBy reports whether interface t's type list is satisfied by the type typ. // 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. // 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. // "implements" predicate.
func (t *Interface) isSatisfiedBy(typ Type) bool { func (t *Interface) isSatisfiedBy(typ Type) bool {
t.Complete() t.Complete()
switch t := t.allTypes.(type) { switch t := t.typeSet().types.(type) {
case nil: case nil:
return true // no type restrictions return true // no type restrictions
case *Union: 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 // NewInterfaceType and NewInterface after the interface's embedded types are
// fully defined and before using the interface type in any way other than to // 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 // form other types. The interface must not contain duplicate methods or a
// panic occurs. Complete returns the receiver. // 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 { func (t *Interface) Complete() *Interface {
if t.allMethods == nil { // Some tests are still depending on the state change
completeInterface(nil, token.NoPos, t) // (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 return t
} }
@ -668,7 +625,7 @@ func (t *_TypeParam) Bound() *Interface {
pos = n.obj.pos pos = n.obj.pos
} }
// TODO(rFindley) switch this to an unexported method on Checker. // TODO(rFindley) switch this to an unexported method on Checker.
t.check.completeInterface(pos, iface) newTypeSet(t.check, pos, iface)
return iface return iface
} }
@ -685,7 +642,7 @@ func optype(typ Type) Type {
// for a type parameter list of the form: // for a type parameter list of the form:
// (type T interface { type T }). // (type T interface { type T }).
// See also issue #39680. // 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 // If we have a union with a single entry, ignore
// any tilde because under(~t) == under(t). // any tilde because under(~t) == under(t).
if u, _ := a.(*Union); u != nil && u.NumTerms() == 1 { if u, _ := a.(*Union); u != nil && u.NumTerms() == 1 {

70
src/go/types/typeset.go Normal file
View file

@ -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)
}

View file

@ -190,7 +190,8 @@ func writeType(buf *bytes.Buffer, typ Type, qf Qualifier, visited []Type) {
if gcCompatibilityMode { if gcCompatibilityMode {
// print flattened interface // print flattened interface
// (useful to compare against gc-generated interfaces) // (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 { if i > 0 {
buf.WriteString("; ") buf.WriteString("; ")
} }
@ -198,12 +199,12 @@ func writeType(buf *bytes.Buffer, typ Type, qf Qualifier, visited []Type) {
writeSignature(buf, m.typ.(*Signature), qf, visited) writeSignature(buf, m.typ.(*Signature), qf, visited)
empty = false empty = false
} }
if !empty && t.allTypes != nil { if !empty && tset.types != nil {
buf.WriteString("; ") buf.WriteString("; ")
} }
if t.allTypes != nil { if tset.types != nil {
buf.WriteString("type ") buf.WriteString("type ")
writeType(buf, t.allTypes, qf, visited) writeType(buf, tset.types, qf, visited)
} }
} else { } else {
// print explicit interface methods and embedded types // print explicit interface methods and embedded types
@ -226,7 +227,9 @@ func writeType(buf *bytes.Buffer, typ Type, qf Qualifier, visited []Type) {
empty = false 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 { if !empty {
buf.WriteByte(' ') buf.WriteByte(' ')
} }

View file

@ -140,12 +140,12 @@ func (check *Checker) ordinaryType(pos positioner, typ Type) {
// type-checking. // type-checking.
check.later(func() { check.later(func() {
if t := asInterface(typ); t != nil { if t := asInterface(typ); t != nil {
check.completeInterface(pos.Pos(), t) // TODO(gri) is this the correct position? tset := newTypeSet(check, pos.Pos(), t) // TODO(gri) is this the correct position?
if t.allTypes != nil { if tset.types != nil {
check.softErrorf(pos, _Todo, "interface contains type constraints (%s)", t.allTypes) check.softErrorf(pos, _Todo, "interface contains type constraints (%s)", tset.types)
return return
} }
if t._IsComparable() { if tset.IsComparable() {
check.softErrorf(pos, _Todo, "interface is (or embeds) comparable") check.softErrorf(pos, _Todo, "interface is (or embeds) comparable")
} }
} }

View file

@ -8,7 +8,6 @@ package types
import ( import (
"bytes" "bytes"
"go/token"
"sort" "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 // the same names and identical function types. Lower-case method names from
// different packages are always different. The order of the methods is irrelevant. // different packages are always different. The order of the methods is irrelevant.
if y, ok := y.(*Interface); ok { if y, ok := y.(*Interface); ok {
// If identical0 is called (indirectly) via an external API entry point a := x.typeSet().methods
// (such as Identical, IdenticalIgnoreTags, etc.), check is nil. But in b := y.typeSet().methods
// 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
if len(a) == len(b) { if len(a) == len(b) {
// Interface types are the only types where cycles can occur // Interface types are the only types where cycles can occur
// that are not "terminated" via named types; and such cycles // that are not "terminated" via named types; and such cycles

View file

@ -90,7 +90,7 @@ func defPredeclaredTypes() {
res := NewVar(token.NoPos, nil, "", Typ[String]) res := NewVar(token.NoPos, nil, "", Typ[String])
sig := &Signature{results: NewTuple(res)} sig := &Signature{results: NewTuple(res)}
err := NewFunc(token.NoPos, nil, "Error", sig) 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) sig.recv = NewVar(token.NoPos, nil, "", typ)
def(NewTypeName(token.NoPos, nil, "error", typ)) def(NewTypeName(token.NoPos, nil, "error", typ))
} }
@ -218,7 +218,7 @@ func defPredeclaredComparable() {
// set up later to match the usual interface method assumptions. // set up later to match the usual interface method assumptions.
sig := new(Signature) sig := new(Signature)
eql := NewFunc(token.NoPos, nil, "==", sig) 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 // set up the defined type for the interface
obj := NewTypeName(token.NoPos, nil, "comparable", nil) obj := NewTypeName(token.NoPos, nil, "comparable", nil)