Skip to content

Commit

Permalink
go/types: underlying type of a type parameter is its constraint inter…
Browse files Browse the repository at this point in the history
…face

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>
  • Loading branch information
findleyr committed Nov 17, 2021
1 parent aa34ea2 commit 88474d4
Show file tree
Hide file tree
Showing 15 changed files with 231 additions and 39 deletions.
2 changes: 1 addition & 1 deletion src/go/types/assignments.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
30 changes: 27 additions & 3 deletions src/go/types/builtins.go
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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()
Expand Down
2 changes: 1 addition & 1 deletion src/go/types/call.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
16 changes: 9 additions & 7 deletions src/go/types/conversions.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
}
}
Expand Down Expand Up @@ -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
}
Expand Down
34 changes: 31 additions & 3 deletions src/go/types/expr.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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
Expand Down
86 changes: 86 additions & 0 deletions src/go/types/index.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 3 additions & 3 deletions src/go/types/lookup.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 == "" {
Expand All @@ -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.
Expand Down
9 changes: 5 additions & 4 deletions src/go/types/operand.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down
27 changes: 19 additions & 8 deletions src/go/types/predicates.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 {
Expand All @@ -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
Expand All @@ -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)
})
Expand Down
1 change: 1 addition & 0 deletions src/go/types/sizes.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Loading

0 comments on commit 88474d4

Please sign in to comment.