]> Cypherpunks.ru repositories - gostls13.git/commitdiff
go/types: underlying type of a type parameter is its constraint interface
authorRobert Findley <rfindley@google.com>
Wed, 17 Nov 2021 15:39:36 +0000 (10:39 -0500)
committerRobert Findley <rfindley@google.com>
Wed, 17 Nov 2021 19:50:58 +0000 (19:50 +0000)
This is a port of CL 359016 from types2 to go/types. Some of the code
around untyped nil differed (because we have to treat untyped nil
differently in go/types for historical reasons).

Updates #47916

Change-Id: Ifc428ed977bf2f4f84cc831f1a3527156940d7b8
Reviewed-on: https://go-review.googlesource.com/c/go/+/364716
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>
15 files changed:
src/go/types/assignments.go
src/go/types/builtins.go
src/go/types/call.go
src/go/types/conversions.go
src/go/types/expr.go
src/go/types/index.go
src/go/types/lookup.go
src/go/types/operand.go
src/go/types/predicates.go
src/go/types/sizes.go
src/go/types/struct.go
src/go/types/type.go
src/go/types/typeparam.go
src/go/types/typeset.go
src/go/types/typexpr.go

index 8645834a6e144b85e0f9a10c6c23608c239f59b2..7e6a230b48c2e52f386b8a67ee87df1671ad03f3 100644 (file)
@@ -37,7 +37,7 @@ func (check *Checker) assignment(x *operand, T Type, context string) {
                // bool, rune, int, float64, complex128 or string respectively, depending
                // on whether the value is a boolean, rune, integer, floating-point,
                // complex, or string constant."
-               if T == nil || IsInterface(T) {
+               if T == nil || IsInterface(T) && !isTypeParam(T) {
                        if T == nil && x.typ == Typ[UntypedNil] {
                                check.errorf(x, _UntypedNil, "use of untyped nil in %s", context)
                                x.mode = invalid
index 5418d66aebfcc8f9fc113ebed69abfe33083bd1a..5abfe8d35b9f8ee634f916aac415b4262f2d96a1 100644 (file)
@@ -179,7 +179,28 @@ func (check *Checker) builtin(x *operand, call *ast.CallExpr, id builtinId) (_ b
                                mode = value
                        }
 
+               case *Interface:
+                       if tparamIsIface && isTypeParam(x.typ) {
+                               if t.typeSet().underIs(func(t Type) bool {
+                                       switch t := arrayPtrDeref(t).(type) {
+                                       case *Basic:
+                                               if isString(t) && id == _Len {
+                                                       return true
+                                               }
+                                       case *Array, *Slice, *Chan:
+                                               return true
+                                       case *Map:
+                                               if id == _Len {
+                                                       return true
+                                               }
+                                       }
+                                       return false
+                               }) {
+                                       mode = value
+                               }
+                       }
                case *TypeParam:
+                       assert(!tparamIsIface)
                        if t.underIs(func(t Type) bool {
                                switch t := arrayPtrDeref(t).(type) {
                                case *Basic:
@@ -797,16 +818,19 @@ func (check *Checker) builtin(x *operand, call *ast.CallExpr, id builtinId) (_ b
 
 // hasVarSize reports if the size of type t is variable due to type parameters.
 func hasVarSize(t Type) bool {
-       switch t := under(t).(type) {
+       switch u := under(t).(type) {
        case *Array:
-               return hasVarSize(t.elem)
+               return hasVarSize(u.elem)
        case *Struct:
-               for _, f := range t.fields {
+               for _, f := range u.fields {
                        if hasVarSize(f.typ) {
                                return true
                        }
                }
+       case *Interface:
+               return isTypeParam(t)
        case *TypeParam:
+               assert(!tparamIsIface)
                return true
        case *Named, *Union:
                unreachable()
index 7cb6027f3bcc8386d44ef98ca8c26fafe8da3b85..940c0ff468f014dadfefd01ad426213aae003502 100644 (file)
@@ -141,7 +141,7 @@ func (check *Checker) callExpr(x *operand, call *ast.CallExpr) exprKind {
                                        check.errorf(call.Args[0], _BadDotDotDotSyntax, "invalid use of ... in conversion to %s", T)
                                        break
                                }
-                               if t, _ := under(T).(*Interface); t != nil {
+                               if t, _ := under(T).(*Interface); t != nil && !isTypeParam(T) {
                                        if !t.IsMethodSet() {
                                                check.errorf(call, _MisplacedConstraintIface, "cannot use interface %s in conversion (contains specific type constraints or is comparable)", T)
                                                break
index 530a29c5dd24a88bcb9422e065d5e803d01594d1..5995d5920f4592ed91cf98bb5d7ff648925e2b3d 100644 (file)
@@ -98,7 +98,7 @@ func (check *Checker) conversion(x *operand, T Type) {
                // - Keep untyped nil for untyped nil arguments.
                // - For integer to string conversions, keep the argument type.
                //   (See also the TODO below.)
-               if IsInterface(T) || constArg && !isConstType(T) || x.isNil() {
+               if IsInterface(T) && !isTypeParam(T) || constArg && !isConstType(T) || x.isNil() {
                        final = Default(x.typ) // default type of untyped nil is untyped nil
                } else if isInteger(x.typ) && allString(T) {
                        final = x.typ
@@ -129,19 +129,23 @@ func (x *operand) convertibleTo(check *Checker, T Type, cause *string) bool {
                return true
        }
 
-       // "V and T have identical underlying types if tags are ignored"
+       // "V and T have identical underlying types if tags are ignored
+       // and V and T are not type parameters"
        V := x.typ
        Vu := under(V)
        Tu := under(T)
-       if IdenticalIgnoreTags(Vu, Tu) {
+       Vp, _ := V.(*TypeParam)
+       Tp, _ := T.(*TypeParam)
+       if IdenticalIgnoreTags(Vu, Tu) && Vp == nil && Tp == nil {
                return true
        }
 
        // "V and T are unnamed pointer types and their pointer base types
-       // have identical underlying types if tags are ignored"
+       // have identical underlying types if tags are ignored
+       // and their pointer base types are not type parameters"
        if V, ok := V.(*Pointer); ok {
                if T, ok := T.(*Pointer); ok {
-                       if IdenticalIgnoreTags(under(V.base), under(T.base)) {
+                       if IdenticalIgnoreTags(under(V.base), under(T.base)) && !isTypeParam(V.base) && !isTypeParam(T.base) {
                                return true
                        }
                }
@@ -195,8 +199,6 @@ func (x *operand) convertibleTo(check *Checker, T Type, cause *string) bool {
        }
 
        // optimization: if we don't have type parameters, we're done
-       Vp, _ := V.(*TypeParam)
-       Tp, _ := T.(*TypeParam)
        if Vp == nil && Tp == nil {
                return false
        }
index 5e66a4a4b571a691ea3064a3ef7107c9b2be4ec6..84eb59d1d09fe33b06ce0653f8b9600410c2c920 100644 (file)
@@ -598,7 +598,11 @@ func (check *Checker) updateExprVal(x ast.Expr, val constant.Value) {
 func (check *Checker) convertUntyped(x *operand, target Type) {
        newType, val, code := check.implicitTypeAndValue(x, target)
        if code != 0 {
-               check.invalidConversion(code, x, safeUnderlying(target))
+               t := target
+               if !tparamIsIface || !isTypeParam(target) {
+                       t = safeUnderlying(target)
+               }
+               check.invalidConversion(code, x, t)
                x.mode = invalid
                return
        }
@@ -678,6 +682,7 @@ func (check *Checker) implicitTypeAndValue(x *operand, target Type) (Type, const
                }
        case *TypeParam:
                // TODO(gri) review this code - doesn't look quite right
+               assert(!tparamIsIface)
                ok := u.underIs(func(t Type) bool {
                        if t == nil {
                                return false
@@ -693,6 +698,24 @@ func (check *Checker) implicitTypeAndValue(x *operand, target Type) (Type, const
                        return Typ[UntypedNil], nil, 0
                }
        case *Interface:
+               if tparamIsIface && isTypeParam(target) {
+                       // TODO(gri) review this code - doesn't look quite right
+                       ok := u.typeSet().underIs(func(t Type) bool {
+                               if t == nil {
+                                       return false
+                               }
+                               target, _, _ := check.implicitTypeAndValue(x, t)
+                               return target != nil
+                       })
+                       if !ok {
+                               return nil, nil, _InvalidUntypedConversion
+                       }
+                       // keep nil untyped (was bug #39755)
+                       if x.isNil() {
+                               return Typ[UntypedNil], nil, 0
+                       }
+                       break
+               }
                // Values must have concrete dynamic types. If the value is nil,
                // keep it untyped (this is important for tools such as go vet which
                // need the dynamic type for argument checking of say, print
@@ -961,8 +984,9 @@ func (check *Checker) binary(x *operand, e ast.Expr, lhs, rhs ast.Expr, op token
                return
        }
 
+       // TODO(gri) make canMix more efficient - called for each binary operation
        canMix := func(x, y *operand) bool {
-               if IsInterface(x.typ) || IsInterface(y.typ) {
+               if IsInterface(x.typ) && !isTypeParam(x.typ) || IsInterface(y.typ) && !isTypeParam(y.typ) {
                        return true
                }
                if allBoolean(x.typ) != allBoolean(y.typ) {
@@ -1219,7 +1243,11 @@ func (check *Checker) exprInternal(x *operand, e ast.Expr, hint Type) exprKind {
                case hint != nil:
                        // no composite literal type present - use hint (element type of enclosing type)
                        typ = hint
-                       base, _ = deref(under(typ)) // *T implies &T{}
+                       base = typ
+                       if !isTypeParam(typ) {
+                               base = under(typ)
+                       }
+                       base, _ = deref(base) // *T implies &T{}
 
                default:
                        // TODO(gri) provide better error messages depending on context
index 0284716277590d13153dd7777da0e35952808fbd..2ff33814e57970688090e25b3b3e162668c8db54 100644 (file)
@@ -100,8 +100,94 @@ func (check *Checker) indexExpr(x *operand, e *typeparams.IndexExpr) (isFuncInst
                x.expr = e.Orig
                return false
 
+       case *Interface:
+               // Note: The body of this 'if' statement is the same as the body
+               //       of the case for type parameters below. If we keep both
+               //       these branches we should factor out the code.
+               if tparamIsIface && isTypeParam(x.typ) {
+                       // TODO(gri) report detailed failure cause for better error messages
+                       var key, elem Type // key != nil: we must have all maps
+                       mode := variable   // non-maps result mode
+                       // TODO(gri) factor out closure and use it for non-typeparam cases as well
+                       if typ.typeSet().underIs(func(u Type) bool {
+                               l := int64(-1) // valid if >= 0
+                               var k, e Type  // k is only set for maps
+                               switch t := u.(type) {
+                               case *Basic:
+                                       if isString(t) {
+                                               e = universeByte
+                                               mode = value
+                                       }
+                               case *Array:
+                                       l = t.len
+                                       e = t.elem
+                                       if x.mode != variable {
+                                               mode = value
+                                       }
+                               case *Pointer:
+                                       if t, _ := under(t.base).(*Array); t != nil {
+                                               l = t.len
+                                               e = t.elem
+                                       }
+                               case *Slice:
+                                       e = t.elem
+                               case *Map:
+                                       k = t.key
+                                       e = t.elem
+                               }
+                               if e == nil {
+                                       return false
+                               }
+                               if elem == nil {
+                                       // first type
+                                       length = l
+                                       key, elem = k, e
+                                       return true
+                               }
+                               // all map keys must be identical (incl. all nil)
+                               // (that is, we cannot mix maps with other types)
+                               if !Identical(key, k) {
+                                       return false
+                               }
+                               // all element types must be identical
+                               if !Identical(elem, e) {
+                                       return false
+                               }
+                               // track the minimal length for arrays, if any
+                               if l >= 0 && l < length {
+                                       length = l
+                               }
+                               return true
+                       }) {
+                               // For maps, the index expression must be assignable to the map key type.
+                               if key != nil {
+                                       index := check.singleIndex(e)
+                                       if index == nil {
+                                               x.mode = invalid
+                                               return false
+                                       }
+                                       var k operand
+                                       check.expr(&k, index)
+                                       check.assignment(&k, key, "map index")
+                                       // ok to continue even if indexing failed - map element type is known
+                                       x.mode = mapindex
+                                       x.typ = elem
+                                       x.expr = e
+                                       return false
+                               }
+
+                               // no maps
+                               valid = true
+                               x.mode = mode
+                               x.typ = elem
+                       }
+               }
        case *TypeParam:
+               // Note: The body of this case is the same as the body of the 'if'
+               //       statement in the interface case above. If we keep both
+               //       these branches we should factor out the code.
                // TODO(gri) report detailed failure cause for better error messages
+               assert(!tparamIsIface)
                var key, elem Type // key != nil: we must have all maps
                mode := variable   // non-maps result mode
                // TODO(gri) factor out closure and use it for non-typeparam cases as well
index 6855ccdf27c7114d0f18cf3cb5b81acdd1e935c9..16a989019919d7363f65449225fd645cdb100da8 100644 (file)
@@ -429,11 +429,11 @@ func (check *Checker) missingMethodReason(V, T Type, m, wrongType *Func) string
                // an extra formatting option for types2.Type that doesn't print out
                // 'func'.
                r = strings.Replace(r, "^^func", "", -1)
-       } else if IsInterface(T) {
+       } else if IsInterface(T) && !isTypeParam(T) {
                if isInterfacePtr(V) {
                        r = fmt.Sprintf("(%s is pointer to interface, not interface)", V)
                }
-       } else if isInterfacePtr(T) {
+       } else if isInterfacePtr(T) && !isTypeParam(T) {
                r = fmt.Sprintf("(%s is pointer to interface, not interface)", T)
        }
        if r == "" {
@@ -444,7 +444,7 @@ func (check *Checker) missingMethodReason(V, T Type, m, wrongType *Func) string
 
 func isInterfacePtr(T Type) bool {
        p, _ := under(T).(*Pointer)
-       return p != nil && IsInterface(p.base)
+       return p != nil && IsInterface(p.base) && !isTypeParam(T)
 }
 
 // assertableTo reports whether a value of type V can be asserted to have type T.
index e8b5d00de4c8731bd125f960d855bfc98b041246..8cc5eda8669bc5b407e321fa55e8d6ebfb74e23f 100644 (file)
@@ -267,13 +267,14 @@ func (x *operand) assignableTo(check *Checker, T Type, reason *string) (bool, er
        // Vu is typed
 
        // x's type V and T have identical underlying types
-       // and at least one of V or T is not a named type.
-       if Identical(Vu, Tu) && (!hasName(V) || !hasName(T)) {
+       // and at least one of V or T is not a named type
+       // and neither V nor T is a type parameter.
+       if Identical(Vu, Tu) && (!hasName(V) || !hasName(T)) && Vp == nil && Tp == nil {
                return true, 0
        }
 
        // T is an interface type and x implements T and T is not a type parameter
-       if Ti, ok := Tu.(*Interface); ok {
+       if Ti, ok := Tu.(*Interface); ok && Tp == nil {
                if m, wrongType := check.missingMethod(V, Ti, true); m != nil /* Implements(V, Ti) */ {
                        if reason != nil {
                                if compilerErrorMessages {
@@ -306,7 +307,7 @@ func (x *operand) assignableTo(check *Checker, T Type, reason *string) (bool, er
                        }
                        return false, _InvalidIfaceAssign
                }
-               if Vi, _ := Vu.(*Interface); Vi != nil {
+               if Vi, _ := Vu.(*Interface); Vi != nil && Vp == nil {
                        if m, _ := check.missingMethod(T, Vi, true); m == nil {
                                // T implements Vi, so give hint about type assertion.
                                if reason != nil {
index 78ad6c4f231c855ebde212a006b71c0e3a2178a1..5204eb0c29a7a4b637e3671d59aa886ef1c26cc8 100644 (file)
@@ -49,13 +49,10 @@ func allNumericOrString(typ Type) bool { return allBasic(typ, IsNumeric|IsString
 // for all specific types of the type parameter's type set.
 // allBasic(t, info) is an optimized version of isBasic(structuralType(t), info).
 func allBasic(t Type, info BasicInfo) bool {
-       switch u := under(t).(type) {
-       case *Basic:
-               return u.info&info != 0
-       case *TypeParam:
-               return u.is(func(t *term) bool { return t != nil && isBasic(t.typ, info) })
+       if tpar, _ := t.(*TypeParam); tpar != nil {
+               return tpar.is(func(t *term) bool { return t != nil && isBasic(t.typ, info) })
        }
-       return false
+       return isBasic(t, info)
 }
 
 // hasName reports whether t has a name. This includes
@@ -124,7 +121,7 @@ func comparable(T Type, seen map[Type]bool) bool {
                // assume invalid types to be comparable
                // to avoid follow-up errors
                return t.kind != UntypedNil
-       case *Pointer, *Interface, *Chan:
+       case *Pointer, *Chan:
                return true
        case *Struct:
                for _, f := range t.fields {
@@ -135,7 +132,13 @@ func comparable(T Type, seen map[Type]bool) bool {
                return true
        case *Array:
                return comparable(t.elem, seen)
+       case *Interface:
+               if tparamIsIface && isTypeParam(T) {
+                       return t.IsComparable()
+               }
+               return true
        case *TypeParam:
+               assert(!tparamIsIface)
                return t.iface().IsComparable()
        }
        return false
@@ -146,9 +149,17 @@ func hasNil(t Type) bool {
        switch u := under(t).(type) {
        case *Basic:
                return u.kind == UnsafePointer
-       case *Slice, *Pointer, *Signature, *Interface, *Map, *Chan:
+       case *Slice, *Pointer, *Signature, *Map, *Chan:
+               return true
+       case *Interface:
+               if tparamIsIface && isTypeParam(t) {
+                       return u.typeSet().underIs(func(u Type) bool {
+                               return u != nil && hasNil(u)
+                       })
+               }
                return true
        case *TypeParam:
+               assert(!tparamIsIface)
                return u.underIs(func(u Type) bool {
                        return u != nil && hasNil(u)
                })
index 9a119138dd5536a5ab84b8fab884ff76273162ae..a92152506235b58f57087941efb5230c61ba979c 100644 (file)
@@ -67,6 +67,7 @@ func (s *StdSizes) Alignof(T Type) int64 {
        case *Slice, *Interface:
                // Multiword data structures are effectively structs
                // in which each element has size WordSize.
+               assert(!tparamIsIface || !isTypeParam(T))
                return s.WordSize
        case *Basic:
                // Strings are like slices and interfaces.
index 84af8a3f48df29acedece4f1085d3aa4b11396d1..53204dc381248703e7bb422b62165f0eb440a14e 100644 (file)
@@ -143,24 +143,29 @@ func (check *Checker) structType(styp *Struct, e *ast.StructType) {
 
                        check.later(func() {
                                t, isPtr := deref(embeddedTyp)
-                               switch t := under(t).(type) {
+                               switch u := under(t).(type) {
                                case *Basic:
                                        if t == Typ[Invalid] {
                                                // error was reported before
                                                return
                                        }
                                        // unsafe.Pointer is treated like a regular pointer
-                                       if t.kind == UnsafePointer {
+                                       if u.kind == UnsafePointer {
                                                check.error(embeddedPos, _InvalidPtrEmbed, "embedded field type cannot be unsafe.Pointer")
                                        }
                                case *Pointer:
                                        check.error(embeddedPos, _InvalidPtrEmbed, "embedded field type cannot be a pointer")
                                case *TypeParam:
+                                       assert(!tparamIsIface)
                                        // This error code here is inconsistent with other error codes for
                                        // invalid embedding, because this restriction may be relaxed in the
                                        // future, and so it did not warrant a new error code.
                                        check.error(embeddedPos, _MisplacedTypeParam, "embedded field type cannot be a (pointer to a) type parameter")
                                case *Interface:
+                                       if tparamIsIface && isTypeParam(t) {
+                                               check.error(embeddedPos, _MisplacedTypeParam, "embedded field type cannot be a (pointer to a) type parameter")
+                                               break
+                                       }
                                        if isPtr {
                                                check.error(embeddedPos, _InvalidPtrEmbed, "embedded field type cannot be a pointer to an interface")
                                        }
index 756bdcf0a57fc0f980a8738a03ea9bf326ef3287..dcf678a27aa57e937ca64ceb9aea7cecab4642a4 100644 (file)
@@ -21,8 +21,13 @@ type Type interface {
 // under must only be called when a type is known
 // to be fully set up.
 func under(t Type) Type {
-       if n := asNamed(t); n != nil {
-               return n.under()
+       switch t := t.(type) {
+       case *Named:
+               return t.under()
+       case *TypeParam:
+               if tparamIsIface {
+                       return t.iface()
+               }
        }
        return t
 }
index 731b746d05f288c159a079bca1ecb4b1b8034d9f..084130fc74f5c5fb436af71b15ec5c3fdf16cfd0 100644 (file)
@@ -9,6 +9,12 @@ import (
        "sync/atomic"
 )
 
+// If set, the underlying type of a type parameter is
+// is the underlying type of its type constraint, i.e.,
+// an interface. With that, a type parameter satisfies
+// isInterface.
+const tparamIsIface = false
+
 // Note: This is a uint32 rather than a uint64 because the
 // respective 64 bit atomic instructions are not available
 // on all platforms.
@@ -72,13 +78,21 @@ func (t *TypeParam) SetConstraint(bound Type) {
        t.bound = bound
 }
 
-func (t *TypeParam) Underlying() Type { return t }
-func (t *TypeParam) String() string   { return TypeString(t, nil) }
+func (t *TypeParam) Underlying() Type {
+       if tparamIsIface {
+               return t.iface()
+       }
+       return t
+}
+
+func (t *TypeParam) String() string { return TypeString(t, nil) }
 
 // ----------------------------------------------------------------------------
 // Implementation
 
 // iface returns the constraint interface of t.
+// TODO(gri) If we make tparamIsIface the default, this should be renamed to under
+//           (similar to Named.under).
 func (t *TypeParam) iface() *Interface {
        bound := t.bound
 
@@ -91,8 +105,13 @@ func (t *TypeParam) iface() *Interface {
                        return &emptyInterface
                }
        case *Interface:
+               if tparamIsIface && isTypeParam(bound) {
+                       // error is reported in Checker.collectTypeParams
+                       return &emptyInterface
+               }
                ityp = u
        case *TypeParam:
+               assert(!tparamIsIface)
                // error is reported in Checker.collectTypeParams
                return &emptyInterface
        }
index d0464aeaa0c22bc8a23c910aa19e163923b23a61..d98080069c9a0b7b9fcc2e1e60ad5ac160ff9cb9 100644 (file)
@@ -266,6 +266,8 @@ func computeInterfaceTypeSet(check *Checker, pos token.Pos, ityp *Interface) *_T
                var terms termlist
                switch u := under(typ).(type) {
                case *Interface:
+                       // For now we don't permit type parameters as constraints.
+                       assert(!isTypeParam(typ))
                        tset := computeInterfaceTypeSet(check, pos, u)
                        // If typ is local, an error was already reported where typ is specified/defined.
                        if check != nil && check.isImportedConstraint(typ) && !check.allowVersion(check.pkg, 1, 18) {
@@ -365,6 +367,8 @@ func computeUnionTypeSet(check *Checker, pos token.Pos, utyp *Union) *_TypeSet {
                var terms termlist
                switch u := under(t.typ).(type) {
                case *Interface:
+                       // For now we don't permit type parameters as constraints.
+                       assert(!isTypeParam(t.typ))
                        terms = computeInterfaceTypeSet(check, pos, u).terms
                default:
                        if t.typ == Typ[Invalid] {
index 89264ee9eb52e92dbeee46a44767271a7cb901c7..c6ab7cd564f23342b086b984715fe60b12ef618c 100644 (file)
@@ -141,9 +141,15 @@ func (check *Checker) typ(e ast.Expr) Type {
 // constraint interface.
 func (check *Checker) varType(e ast.Expr) Type {
        typ := check.definedType(e, nil)
-       // We don't want to call under() (via toInterface) or complete interfaces while we
-       // are in the middle of type-checking parameter declarations that might belong to
-       // interface methods. Delay this check to the end of type-checking.
+
+       // If we have a type parameter there's nothing to do.
+       if isTypeParam(typ) {
+               return typ
+       }
+
+       // We don't want to call under() or complete interfaces while we are in
+       // the middle of type-checking parameter declarations that might belong
+       // to interface methods. Delay this check to the end of type-checking.
        check.later(func() {
                if t, _ := under(typ).(*Interface); t != nil {
                        tset := computeInterfaceTypeSet(check, e.Pos(), t) // TODO(gri) is this the correct position?