Skip to content

Commit 88474d4

Browse files
committed
go/types: underlying type of a type parameter is its constraint interface
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 <[email protected]> Run-TryBot: Robert Findley <[email protected]> TryBot-Result: Go Bot <[email protected]> Reviewed-by: Robert Griesemer <[email protected]>
1 parent aa34ea2 commit 88474d4

15 files changed

+231
-39
lines changed

src/go/types/assignments.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ func (check *Checker) assignment(x *operand, T Type, context string) {
3737
// bool, rune, int, float64, complex128 or string respectively, depending
3838
// on whether the value is a boolean, rune, integer, floating-point,
3939
// complex, or string constant."
40-
if T == nil || IsInterface(T) {
40+
if T == nil || IsInterface(T) && !isTypeParam(T) {
4141
if T == nil && x.typ == Typ[UntypedNil] {
4242
check.errorf(x, _UntypedNil, "use of untyped nil in %s", context)
4343
x.mode = invalid

src/go/types/builtins.go

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,28 @@ func (check *Checker) builtin(x *operand, call *ast.CallExpr, id builtinId) (_ b
179179
mode = value
180180
}
181181

182+
case *Interface:
183+
if tparamIsIface && isTypeParam(x.typ) {
184+
if t.typeSet().underIs(func(t Type) bool {
185+
switch t := arrayPtrDeref(t).(type) {
186+
case *Basic:
187+
if isString(t) && id == _Len {
188+
return true
189+
}
190+
case *Array, *Slice, *Chan:
191+
return true
192+
case *Map:
193+
if id == _Len {
194+
return true
195+
}
196+
}
197+
return false
198+
}) {
199+
mode = value
200+
}
201+
}
182202
case *TypeParam:
203+
assert(!tparamIsIface)
183204
if t.underIs(func(t Type) bool {
184205
switch t := arrayPtrDeref(t).(type) {
185206
case *Basic:
@@ -797,16 +818,19 @@ func (check *Checker) builtin(x *operand, call *ast.CallExpr, id builtinId) (_ b
797818

798819
// hasVarSize reports if the size of type t is variable due to type parameters.
799820
func hasVarSize(t Type) bool {
800-
switch t := under(t).(type) {
821+
switch u := under(t).(type) {
801822
case *Array:
802-
return hasVarSize(t.elem)
823+
return hasVarSize(u.elem)
803824
case *Struct:
804-
for _, f := range t.fields {
825+
for _, f := range u.fields {
805826
if hasVarSize(f.typ) {
806827
return true
807828
}
808829
}
830+
case *Interface:
831+
return isTypeParam(t)
809832
case *TypeParam:
833+
assert(!tparamIsIface)
810834
return true
811835
case *Named, *Union:
812836
unreachable()

src/go/types/call.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ func (check *Checker) callExpr(x *operand, call *ast.CallExpr) exprKind {
141141
check.errorf(call.Args[0], _BadDotDotDotSyntax, "invalid use of ... in conversion to %s", T)
142142
break
143143
}
144-
if t, _ := under(T).(*Interface); t != nil {
144+
if t, _ := under(T).(*Interface); t != nil && !isTypeParam(T) {
145145
if !t.IsMethodSet() {
146146
check.errorf(call, _MisplacedConstraintIface, "cannot use interface %s in conversion (contains specific type constraints or is comparable)", T)
147147
break

src/go/types/conversions.go

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ func (check *Checker) conversion(x *operand, T Type) {
9898
// - Keep untyped nil for untyped nil arguments.
9999
// - For integer to string conversions, keep the argument type.
100100
// (See also the TODO below.)
101-
if IsInterface(T) || constArg && !isConstType(T) || x.isNil() {
101+
if IsInterface(T) && !isTypeParam(T) || constArg && !isConstType(T) || x.isNil() {
102102
final = Default(x.typ) // default type of untyped nil is untyped nil
103103
} else if isInteger(x.typ) && allString(T) {
104104
final = x.typ
@@ -129,19 +129,23 @@ func (x *operand) convertibleTo(check *Checker, T Type, cause *string) bool {
129129
return true
130130
}
131131

132-
// "V and T have identical underlying types if tags are ignored"
132+
// "V and T have identical underlying types if tags are ignored
133+
// and V and T are not type parameters"
133134
V := x.typ
134135
Vu := under(V)
135136
Tu := under(T)
136-
if IdenticalIgnoreTags(Vu, Tu) {
137+
Vp, _ := V.(*TypeParam)
138+
Tp, _ := T.(*TypeParam)
139+
if IdenticalIgnoreTags(Vu, Tu) && Vp == nil && Tp == nil {
137140
return true
138141
}
139142

140143
// "V and T are unnamed pointer types and their pointer base types
141-
// have identical underlying types if tags are ignored"
144+
// have identical underlying types if tags are ignored
145+
// and their pointer base types are not type parameters"
142146
if V, ok := V.(*Pointer); ok {
143147
if T, ok := T.(*Pointer); ok {
144-
if IdenticalIgnoreTags(under(V.base), under(T.base)) {
148+
if IdenticalIgnoreTags(under(V.base), under(T.base)) && !isTypeParam(V.base) && !isTypeParam(T.base) {
145149
return true
146150
}
147151
}
@@ -195,8 +199,6 @@ func (x *operand) convertibleTo(check *Checker, T Type, cause *string) bool {
195199
}
196200

197201
// optimization: if we don't have type parameters, we're done
198-
Vp, _ := V.(*TypeParam)
199-
Tp, _ := T.(*TypeParam)
200202
if Vp == nil && Tp == nil {
201203
return false
202204
}

src/go/types/expr.go

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -598,7 +598,11 @@ func (check *Checker) updateExprVal(x ast.Expr, val constant.Value) {
598598
func (check *Checker) convertUntyped(x *operand, target Type) {
599599
newType, val, code := check.implicitTypeAndValue(x, target)
600600
if code != 0 {
601-
check.invalidConversion(code, x, safeUnderlying(target))
601+
t := target
602+
if !tparamIsIface || !isTypeParam(target) {
603+
t = safeUnderlying(target)
604+
}
605+
check.invalidConversion(code, x, t)
602606
x.mode = invalid
603607
return
604608
}
@@ -678,6 +682,7 @@ func (check *Checker) implicitTypeAndValue(x *operand, target Type) (Type, const
678682
}
679683
case *TypeParam:
680684
// TODO(gri) review this code - doesn't look quite right
685+
assert(!tparamIsIface)
681686
ok := u.underIs(func(t Type) bool {
682687
if t == nil {
683688
return false
@@ -693,6 +698,24 @@ func (check *Checker) implicitTypeAndValue(x *operand, target Type) (Type, const
693698
return Typ[UntypedNil], nil, 0
694699
}
695700
case *Interface:
701+
if tparamIsIface && isTypeParam(target) {
702+
// TODO(gri) review this code - doesn't look quite right
703+
ok := u.typeSet().underIs(func(t Type) bool {
704+
if t == nil {
705+
return false
706+
}
707+
target, _, _ := check.implicitTypeAndValue(x, t)
708+
return target != nil
709+
})
710+
if !ok {
711+
return nil, nil, _InvalidUntypedConversion
712+
}
713+
// keep nil untyped (was bug #39755)
714+
if x.isNil() {
715+
return Typ[UntypedNil], nil, 0
716+
}
717+
break
718+
}
696719
// Values must have concrete dynamic types. If the value is nil,
697720
// keep it untyped (this is important for tools such as go vet which
698721
// 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
961984
return
962985
}
963986

987+
// TODO(gri) make canMix more efficient - called for each binary operation
964988
canMix := func(x, y *operand) bool {
965-
if IsInterface(x.typ) || IsInterface(y.typ) {
989+
if IsInterface(x.typ) && !isTypeParam(x.typ) || IsInterface(y.typ) && !isTypeParam(y.typ) {
966990
return true
967991
}
968992
if allBoolean(x.typ) != allBoolean(y.typ) {
@@ -1219,7 +1243,11 @@ func (check *Checker) exprInternal(x *operand, e ast.Expr, hint Type) exprKind {
12191243
case hint != nil:
12201244
// no composite literal type present - use hint (element type of enclosing type)
12211245
typ = hint
1222-
base, _ = deref(under(typ)) // *T implies &T{}
1246+
base = typ
1247+
if !isTypeParam(typ) {
1248+
base = under(typ)
1249+
}
1250+
base, _ = deref(base) // *T implies &T{}
12231251

12241252
default:
12251253
// TODO(gri) provide better error messages depending on context

src/go/types/index.go

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,8 +100,94 @@ func (check *Checker) indexExpr(x *operand, e *typeparams.IndexExpr) (isFuncInst
100100
x.expr = e.Orig
101101
return false
102102

103+
case *Interface:
104+
// Note: The body of this 'if' statement is the same as the body
105+
// of the case for type parameters below. If we keep both
106+
// these branches we should factor out the code.
107+
if tparamIsIface && isTypeParam(x.typ) {
108+
// TODO(gri) report detailed failure cause for better error messages
109+
var key, elem Type // key != nil: we must have all maps
110+
mode := variable // non-maps result mode
111+
// TODO(gri) factor out closure and use it for non-typeparam cases as well
112+
if typ.typeSet().underIs(func(u Type) bool {
113+
l := int64(-1) // valid if >= 0
114+
var k, e Type // k is only set for maps
115+
switch t := u.(type) {
116+
case *Basic:
117+
if isString(t) {
118+
e = universeByte
119+
mode = value
120+
}
121+
case *Array:
122+
l = t.len
123+
e = t.elem
124+
if x.mode != variable {
125+
mode = value
126+
}
127+
case *Pointer:
128+
if t, _ := under(t.base).(*Array); t != nil {
129+
l = t.len
130+
e = t.elem
131+
}
132+
case *Slice:
133+
e = t.elem
134+
case *Map:
135+
k = t.key
136+
e = t.elem
137+
}
138+
if e == nil {
139+
return false
140+
}
141+
if elem == nil {
142+
// first type
143+
length = l
144+
key, elem = k, e
145+
return true
146+
}
147+
// all map keys must be identical (incl. all nil)
148+
// (that is, we cannot mix maps with other types)
149+
if !Identical(key, k) {
150+
return false
151+
}
152+
// all element types must be identical
153+
if !Identical(elem, e) {
154+
return false
155+
}
156+
// track the minimal length for arrays, if any
157+
if l >= 0 && l < length {
158+
length = l
159+
}
160+
return true
161+
}) {
162+
// For maps, the index expression must be assignable to the map key type.
163+
if key != nil {
164+
index := check.singleIndex(e)
165+
if index == nil {
166+
x.mode = invalid
167+
return false
168+
}
169+
var k operand
170+
check.expr(&k, index)
171+
check.assignment(&k, key, "map index")
172+
// ok to continue even if indexing failed - map element type is known
173+
x.mode = mapindex
174+
x.typ = elem
175+
x.expr = e
176+
return false
177+
}
178+
179+
// no maps
180+
valid = true
181+
x.mode = mode
182+
x.typ = elem
183+
}
184+
}
103185
case *TypeParam:
186+
// Note: The body of this case is the same as the body of the 'if'
187+
// statement in the interface case above. If we keep both
188+
// these branches we should factor out the code.
104189
// TODO(gri) report detailed failure cause for better error messages
190+
assert(!tparamIsIface)
105191
var key, elem Type // key != nil: we must have all maps
106192
mode := variable // non-maps result mode
107193
// TODO(gri) factor out closure and use it for non-typeparam cases as well

src/go/types/lookup.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -429,11 +429,11 @@ func (check *Checker) missingMethodReason(V, T Type, m, wrongType *Func) string
429429
// an extra formatting option for types2.Type that doesn't print out
430430
// 'func'.
431431
r = strings.Replace(r, "^^func", "", -1)
432-
} else if IsInterface(T) {
432+
} else if IsInterface(T) && !isTypeParam(T) {
433433
if isInterfacePtr(V) {
434434
r = fmt.Sprintf("(%s is pointer to interface, not interface)", V)
435435
}
436-
} else if isInterfacePtr(T) {
436+
} else if isInterfacePtr(T) && !isTypeParam(T) {
437437
r = fmt.Sprintf("(%s is pointer to interface, not interface)", T)
438438
}
439439
if r == "" {
@@ -444,7 +444,7 @@ func (check *Checker) missingMethodReason(V, T Type, m, wrongType *Func) string
444444

445445
func isInterfacePtr(T Type) bool {
446446
p, _ := under(T).(*Pointer)
447-
return p != nil && IsInterface(p.base)
447+
return p != nil && IsInterface(p.base) && !isTypeParam(T)
448448
}
449449

450450
// assertableTo reports whether a value of type V can be asserted to have type T.

src/go/types/operand.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -267,13 +267,14 @@ func (x *operand) assignableTo(check *Checker, T Type, reason *string) (bool, er
267267
// Vu is typed
268268

269269
// x's type V and T have identical underlying types
270-
// and at least one of V or T is not a named type.
271-
if Identical(Vu, Tu) && (!hasName(V) || !hasName(T)) {
270+
// and at least one of V or T is not a named type
271+
// and neither V nor T is a type parameter.
272+
if Identical(Vu, Tu) && (!hasName(V) || !hasName(T)) && Vp == nil && Tp == nil {
272273
return true, 0
273274
}
274275

275276
// T is an interface type and x implements T and T is not a type parameter
276-
if Ti, ok := Tu.(*Interface); ok {
277+
if Ti, ok := Tu.(*Interface); ok && Tp == nil {
277278
if m, wrongType := check.missingMethod(V, Ti, true); m != nil /* Implements(V, Ti) */ {
278279
if reason != nil {
279280
if compilerErrorMessages {
@@ -306,7 +307,7 @@ func (x *operand) assignableTo(check *Checker, T Type, reason *string) (bool, er
306307
}
307308
return false, _InvalidIfaceAssign
308309
}
309-
if Vi, _ := Vu.(*Interface); Vi != nil {
310+
if Vi, _ := Vu.(*Interface); Vi != nil && Vp == nil {
310311
if m, _ := check.missingMethod(T, Vi, true); m == nil {
311312
// T implements Vi, so give hint about type assertion.
312313
if reason != nil {

src/go/types/predicates.go

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -49,13 +49,10 @@ func allNumericOrString(typ Type) bool { return allBasic(typ, IsNumeric|IsString
4949
// for all specific types of the type parameter's type set.
5050
// allBasic(t, info) is an optimized version of isBasic(structuralType(t), info).
5151
func allBasic(t Type, info BasicInfo) bool {
52-
switch u := under(t).(type) {
53-
case *Basic:
54-
return u.info&info != 0
55-
case *TypeParam:
56-
return u.is(func(t *term) bool { return t != nil && isBasic(t.typ, info) })
52+
if tpar, _ := t.(*TypeParam); tpar != nil {
53+
return tpar.is(func(t *term) bool { return t != nil && isBasic(t.typ, info) })
5754
}
58-
return false
55+
return isBasic(t, info)
5956
}
6057

6158
// hasName reports whether t has a name. This includes
@@ -124,7 +121,7 @@ func comparable(T Type, seen map[Type]bool) bool {
124121
// assume invalid types to be comparable
125122
// to avoid follow-up errors
126123
return t.kind != UntypedNil
127-
case *Pointer, *Interface, *Chan:
124+
case *Pointer, *Chan:
128125
return true
129126
case *Struct:
130127
for _, f := range t.fields {
@@ -135,7 +132,13 @@ func comparable(T Type, seen map[Type]bool) bool {
135132
return true
136133
case *Array:
137134
return comparable(t.elem, seen)
135+
case *Interface:
136+
if tparamIsIface && isTypeParam(T) {
137+
return t.IsComparable()
138+
}
139+
return true
138140
case *TypeParam:
141+
assert(!tparamIsIface)
139142
return t.iface().IsComparable()
140143
}
141144
return false
@@ -146,9 +149,17 @@ func hasNil(t Type) bool {
146149
switch u := under(t).(type) {
147150
case *Basic:
148151
return u.kind == UnsafePointer
149-
case *Slice, *Pointer, *Signature, *Interface, *Map, *Chan:
152+
case *Slice, *Pointer, *Signature, *Map, *Chan:
153+
return true
154+
case *Interface:
155+
if tparamIsIface && isTypeParam(t) {
156+
return u.typeSet().underIs(func(u Type) bool {
157+
return u != nil && hasNil(u)
158+
})
159+
}
150160
return true
151161
case *TypeParam:
162+
assert(!tparamIsIface)
152163
return u.underIs(func(u Type) bool {
153164
return u != nil && hasNil(u)
154165
})

src/go/types/sizes.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ func (s *StdSizes) Alignof(T Type) int64 {
6767
case *Slice, *Interface:
6868
// Multiword data structures are effectively structs
6969
// in which each element has size WordSize.
70+
assert(!tparamIsIface || !isTypeParam(T))
7071
return s.WordSize
7172
case *Basic:
7273
// Strings are like slices and interfaces.

0 commit comments

Comments
 (0)