mirror of
https://github.com/golang/go
synced 2024-11-11 23:10:23 -07:00
[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 <gri@golang.org> Run-TryBot: Robert Griesemer <gri@golang.org> TryBot-Result: Go Bot <gobot@golang.org> Reviewed-by: Robert Findley <rfindley@google.com>
This commit is contained in:
parent
f503740ccf
commit
4b5fdb0b7a
@ -783,7 +783,7 @@ func (check *Checker) applyTypeFunc(f func(Type) Type, x Type) Type {
|
||||
tpar := NewTypeName(nopos, check.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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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
|
||||
|
@ -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:
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
70
src/cmd/compile/internal/types2/typeset.go
Normal file
70
src/cmd/compile/internal/types2/typeset.go
Normal 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 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)
|
||||
}
|
@ -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(' ')
|
||||
}
|
||||
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user