From a4d4c10340957f5c1804134a75b3da36402fe8bb Mon Sep 17 00:00:00 2001 From: Rob Findley Date: Tue, 15 Dec 2020 11:37:06 -0500 Subject: [PATCH] [dev.typeparams] go/types: import lookup logic from dev.go2go Changes from dev.go2go: + Remove support for pointer designation. + Remove support for method type parameters in missingMethod. We could leave this logic in, but it looked sufficiently shaky that I'd rather not bring in the additional complexity. + Remove the strictness flag parameter to assertableTo, since it isn't used. Change-Id: I812b8d1c49f3b714b166f061fbb7f2e683a0ce86 Reviewed-on: https://go-review.googlesource.com/c/go/+/278333 Run-TryBot: Robert Findley TryBot-Result: Go Bot Trust: Robert Findley Trust: Robert Griesemer Reviewed-by: Robert Griesemer --- src/go/types/lookup.go | 91 ++++++++++++++++++++++++++++++--------- src/go/types/methodset.go | 5 ++- 2 files changed, 75 insertions(+), 21 deletions(-) diff --git a/src/go/types/lookup.go b/src/go/types/lookup.go index e7091a63e5..f385ac993f 100644 --- a/src/go/types/lookup.go +++ b/src/go/types/lookup.go @@ -55,7 +55,7 @@ func (check *Checker) lookupFieldOrMethod(T Type, addressable bool, pkg *Package // Thus, if we have a named pointer type, proceed with the underlying // pointer type but discard the result if it is a method since we would // not have found it for T (see also issue 8590). - if t, _ := T.(*Named); t != nil { + if t := asNamed(T); t != nil { if p, _ := t.underlying.(*Pointer); p != nil { obj, index, indirect = check.rawLookupFieldOrMethod(p, false, pkg, name) if _, ok := obj.(*Func); ok { @@ -85,7 +85,8 @@ func (check *Checker) rawLookupFieldOrMethod(T Type, addressable bool, pkg *Pack typ, isPtr := deref(T) // *typ where typ is an interface has no methods. - if isPtr && IsInterface(typ) { + // Be cautious: typ may be nil (issue 39634, crash #3). + if typ == nil || isPtr && IsInterface(typ) { return } @@ -106,12 +107,13 @@ func (check *Checker) rawLookupFieldOrMethod(T Type, addressable bool, pkg *Pack var next []embeddedType // embedded types found at current depth // look for (pkg, name) in all types at current depth + var tpar *TypeParam // set if obj receiver is a type parameter for _, e := range current { typ := e.typ // If we have a named type, we may have associated methods. // Look for those first. - if named, _ := typ.(*Named); named != nil { + if named := asNamed(typ); named != nil { if seen[named] { // We have seen this type before, at a more shallow depth // (note that multiples of this type at the current depth @@ -138,10 +140,15 @@ func (check *Checker) rawLookupFieldOrMethod(T Type, addressable bool, pkg *Pack continue // we can't have a matching field or interface method } - // continue with underlying type - typ = named.underlying + // continue with underlying type, but only if it's not a type parameter + // TODO(gri) is this what we want to do for type parameters? (spec question) + typ = named.under() + if asTypeParam(typ) != nil { + continue + } } + tpar = nil switch t := typ.(type) { case *Struct: // look for a matching field and collect embedded types @@ -187,6 +194,20 @@ func (check *Checker) rawLookupFieldOrMethod(T Type, addressable bool, pkg *Pack obj = m indirect = e.indirect } + + 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 { + assert(m.typ != nil) + index = concat(e.index, i) + if obj != nil || e.multiples { + return nil, index, false // collision + } + tpar = t + obj = m + indirect = e.indirect + } } } @@ -196,8 +217,12 @@ func (check *Checker) rawLookupFieldOrMethod(T Type, addressable bool, pkg *Pack // contains m and the argument list can be assigned to the parameter // list of m. If x is addressable and &x's method set contains m, x.m() // is shorthand for (&x).m()". - if f, _ := obj.(*Func); f != nil && ptrRecv(f) && !indirect && !addressable { - return nil, nil, true // pointer/addressable receiver required + if f, _ := obj.(*Func); f != nil { + // determine if method has a pointer receiver + hasPtrRecv := tpar == nil && ptrRecv(f) + if hasPtrRecv && !indirect && !addressable { + return nil, nil, true // pointer/addressable receiver required + } } return } @@ -269,7 +294,8 @@ func MissingMethod(V Type, T *Interface, static bool) (method *Func, wrongType b return m, typ != nil } -// missingMethod is like MissingMethod but accepts a receiver. +// missingMethod is like MissingMethod but accepts a *Checker as +// receiver and an addressable flag. // The receiver may be nil if missingMethod is invoked through // an exported API call (such as MissingMethod), i.e., when all // methods have been type-checked. @@ -285,25 +311,37 @@ func (check *Checker) missingMethod(V Type, T *Interface, static bool) (method, return } - if ityp, _ := V.Underlying().(*Interface); ityp != nil { + 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 { - _, obj := lookupMethod(ityp.allMethods, m.pkg, m.name) - switch { - case obj == nil: - if static { - return m, nil + _, f := lookupMethod(ityp.allMethods, m.pkg, m.name) + + if f == nil { + // if m is the magic method == we're ok (interfaces are comparable) + if m.name == "==" || !static { + continue } - case !check.identical(obj.Type(), m.typ): - return m, obj + return m, f } + + if !check.identical(f.Type(), m.Type()) { + return m, f + } + + // TODO(rFindley) delete this note once the spec has stabilized to + // exclude method type parameters. + // NOTE: if enabling method type parameters, we need to unify f.Type() + // and m.Type() here to verify that their type parameters align (assuming + // this behaves correctly with respect to type bounds). } + return } // A concrete type implements T if it implements all methods of T. for _, m := range T.allMethods { + // TODO(gri) should this be calling lookupFieldOrMethod instead (and why not)? obj, _, _ := check.rawLookupFieldOrMethod(V, false, m.pkg, m.name) // Check if *V implements this method of T. @@ -318,6 +356,10 @@ func (check *Checker) missingMethod(V Type, T *Interface, static bool) (method, // we must have a method (not a field of matching function type) f, _ := obj.(*Func) if f == nil { + // if m is the magic method == and V is comparable, we're ok + if m.name == "==" && Comparable(V) { + continue + } return m, nil } @@ -326,9 +368,16 @@ func (check *Checker) missingMethod(V Type, T *Interface, static bool) (method, check.objDecl(f, nil) } - if !check.identical(f.typ, m.typ) { + if !check.identical(f.Type(), m.Type()) { return m, f } + + // TODO(rFindley) delete this note once the spec has stabilized to exclude + // method type parameters. + // NOTE: if enabling method type parameters, one needs to subst any + // receiver type parameters for V here, and unify f.Type() with m.Type() to + // verify that their type parameters align (assuming this behaves correctly + // with respect to type bounds). } return @@ -339,11 +388,13 @@ func (check *Checker) missingMethod(V Type, T *Interface, static bool) (method, // method required by V and whether it is missing or just has the wrong type. // The receiver may be nil if assertableTo is invoked through an exported API call // (such as AssertableTo), i.e., when all methods have been type-checked. +// If the global constant forceStrict is set, assertions that are known to fail +// are not permitted. func (check *Checker) assertableTo(V *Interface, T Type) (method, wrongType *Func) { // no static check is required if T is an interface // spec: "If T is an interface type, x.(T) asserts that the // dynamic type of x implements the interface T." - if _, ok := T.Underlying().(*Interface); ok && !forceStrict { + if asInterface(T) != nil && !forceStrict { return } return check.missingMethod(T, V, false) @@ -361,8 +412,8 @@ func deref(typ Type) (Type, bool) { // derefStructPtr dereferences typ if it is a (named or unnamed) pointer to a // (named or unnamed) struct and returns its base. Otherwise it returns typ. func derefStructPtr(typ Type) Type { - if p, _ := typ.Underlying().(*Pointer); p != nil { - if _, ok := p.base.Underlying().(*Struct); ok { + if p := asPointer(typ); p != nil { + if asStruct(p.base) != nil { return p.base } } diff --git a/src/go/types/methodset.go b/src/go/types/methodset.go index c34d732b7a..c44009f1a5 100644 --- a/src/go/types/methodset.go +++ b/src/go/types/methodset.go @@ -73,6 +73,9 @@ func NewMethodSet(T Type) *MethodSet { // WARNING: The code in this function is extremely subtle - do not modify casually! // This function and lookupFieldOrMethod should be kept in sync. + // TODO(gri) This code is out-of-sync with the lookup code at this point. + // Need to update. + // method set up to the current depth, allocated lazily var base methodSet @@ -108,7 +111,7 @@ func NewMethodSet(T Type) *MethodSet { // If we have a named type, we may have associated methods. // Look for those first. - if named, _ := typ.(*Named); named != nil { + if named := asNamed(typ); named != nil { if seen[named] { // We have seen this type before, at a more shallow depth // (note that multiples of this type at the current depth