x.mode = invalid
return
}
- } else if T == nil || IsInterface(T) {
+ } else if T == nil || IsInterface(T) && !isTypeParam(T) {
target = Default(x.typ)
}
newType, val, code := check.implicitTypeAndValue(x, target)
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:
// 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()
case 1:
check.expr(x, call.ArgList[0])
if x.mode != invalid {
- if t, _ := under(T).(*Interface); t != nil {
+ if t, _ := under(T).(*Interface); t != nil && !isTypeParam(T) {
if !t.IsMethodSet() {
check.errorf(call, "cannot use interface %s in conversion (contains specific type constraints or is comparable)", T)
break
// (See also the TODO below.)
if x.typ == Typ[UntypedNil] {
// ok
- } else if IsInterface(T) || constArg && !isConstType(T) {
+ } else if IsInterface(T) && !isTypeParam(T) || constArg && !isConstType(T) {
final = Default(x.typ)
} else if isInteger(x.typ) && allString(T) {
final = x.typ
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
}
}
}
// optimization: if we don't have type parameters, we're done
- Vp, _ := V.(*TypeParam)
- Tp, _ := T.(*TypeParam)
if Vp == nil && Tp == nil {
return false
}
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
}
}
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
return nil, nil, _InvalidUntypedConversion
}
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
+ }
+ break
+ }
// Update operand types to the default type rather than the target
// (interface) type: values must have concrete dynamic types.
// Untyped nil was handled upfront.
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) {
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
x.expr = e
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
// 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 == "" {
func isInterfacePtr(T Type) bool {
p, _ := under(T).(*Pointer)
- return p != nil && IsInterface(p.base)
+ return p != nil && IsInterface(p.base) && !isTypeParam(p.base)
}
// assertableTo reports whether a value of type V can be asserted to have type T.
// 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 check.conf.CompilerErrorMessages {
*reason = check.sprintf("%s does not implement %s (%s is pointer to interface, not interface)", x.typ, T, T)
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 {
// 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
// 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 {
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
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)
})
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.
embeddedPos := pos
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, "embedded field type cannot be unsafe.Pointer")
}
case *Pointer:
check.error(embeddedPos, "embedded field type cannot be a pointer")
case *TypeParam:
+ assert(!tparamIsIface)
check.error(embeddedPos, "embedded field type cannot be a (pointer to a) type parameter")
case *Interface:
+ if tparamIsIface && isTypeParam(t) {
+ check.error(embeddedPos, "embedded field type cannot be a (pointer to a) type parameter")
+ break
+ }
if isPtr {
check.error(embeddedPos, "embedded field type cannot be a pointer to an 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
}
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.
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
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
}
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) {
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] {
func (check *Checker) varType(e syntax.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 {
pos := syntax.StartPos(e)