cmd/compile/internal/types2: accept constraint literals with elided interfaces

When collecting type parameters, wrap constraint literals of the
form ~T or A|B into interfaces so the type checker doesn't have
to deal with these type set expressions syntactically anywhere
else but in interfaces (i.e., union types continue to appear
only as embedded elements in interfaces).

Since a type constraint doesn't need to be an interface anymore,
we can remove the respective restriction. Instead, when accessing
the constraint interface via TypeParam.iface, wrap non-interface
constraints at that point and update the constraint so it happens
only once. By computing the types sets of all type parameters at
before the end of type-checking, we ensure that type constraints
are in their final form when accessed through the API.

For #48424.

Change-Id: I3a47a644ad4ab20f91d93ee39fcf3214bb5a81f9
Reviewed-on: https://go-review.googlesource.com/c/go/+/353139
Trust: Robert Griesemer <gri@golang.org>
Run-TryBot: Robert Griesemer <gri@golang.org>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Robert Findley <rfindley@google.com>
This commit is contained in:
Robert Griesemer 2021-09-29 20:56:36 -07:00
parent 5279e534b5
commit dab16c1c90
6 changed files with 106 additions and 21 deletions

View file

@ -100,7 +100,7 @@ func testFiles(t *testing.T, filenames []string, colDelta uint, manual bool) {
var mode syntax.Mode
if strings.HasSuffix(filenames[0], ".go2") {
mode |= syntax.AllowGenerics | syntax.AllowTypeLists
mode |= syntax.AllowGenerics | syntax.AllowTypeSets | syntax.AllowTypeLists
}
// parse files and collect parser errors
files, errlist := parseFiles(t, filenames, mode)

View file

@ -632,21 +632,35 @@ func (check *Checker) collectTypeParams(dst **TypeParamList, list []*syntax.Fiel
// This also preserves the grouped output of type parameter lists
// when printing type strings.
if i == 0 || f.Type != list[i-1].Type {
bound = check.typ(f.Type)
bound = check.bound(f.Type)
}
tparams[i].bound = bound
}
check.later(func() {
for i, tpar := range tparams {
u := under(tpar.bound)
if _, ok := u.(*Interface); !ok && u != Typ[Invalid] {
check.errorf(list[i].Type, "%s is not an interface", tpar.bound)
if _, ok := under(tpar.bound).(*TypeParam); ok {
check.error(list[i].Type, "cannot use a type parameter as constraint")
}
tpar.iface() // compute type set
}
})
}
func (check *Checker) bound(x syntax.Expr) Type {
// A type set literal of the form ~T and A|B may only appear as constraint;
// embed it in an implicit interface so that only interface type-checking
// needs to take care of such type expressions.
if op, _ := x.(*syntax.Operation); op != nil && (op.Op == syntax.Tilde || op.Op == syntax.Or) {
// TODO(gri) Should mark this interface as "implicit" somehow
// (and propagate the info to types2.Interface) so
// that we can elide the interface again in error
// messages. Could use a sentinel name for the field.
x = &syntax.InterfaceType{MethodList: []*syntax.Field{{Type: x}}}
}
return check.typ(x)
}
func (check *Checker) declareTypeParam(name *syntax.Name) *TypeParam {
// Use Typ[Invalid] for the type constraint to ensure that a type
// is present even if the actual constraint has not been assigned

View file

@ -0,0 +1,48 @@
// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// This file shows some examples of constraint literals with elided interfaces.
// These examples are permitted if proposal issue #48424 is accepted.
package p
// Constraint type sets of the form T, ~T, or A|B may omit the interface.
type (
_[T int] struct{}
_[T ~int] struct{}
_[T int|string] struct{}
_[T ~int|~string] struct{}
)
func min[T int|string](x, y T) T {
if x < y {
return x
}
return y
}
func lookup[M ~map[K]V, K comparable, V any](m M, k K) V {
return m[k]
}
func deref[P ~*E, E any](p P) E {
return *p
}
func _() int {
p := new(int)
return deref(p)
}
func addrOfCopy[V any, P ~*V](v V) P {
return &v
}
func _() *int {
return addrOfCopy(0)
}
// A type parameter may not be embedded in an interface;
// so it can also not be used as a constraint.
func _[A any, B A /* ERROR cannot use a type parameter as constraint */ ]() {}

View file

@ -6,4 +6,4 @@ package p
// A constraint must be an interface; it cannot
// be a type parameter, for instance.
func _[A interface{ ~int }, B A /* ERROR not an interface */ ]() {}
func _[A interface{ ~int }, B A /* ERROR cannot use a type parameter as constraint */ ]() {}

View file

@ -21,8 +21,7 @@ type TypeParam struct {
id uint64 // unique id, for debugging only
obj *TypeName // corresponding type name
index int // type parameter index in source order, starting at 0
// TODO(rfindley): this could also be Typ[Invalid]. Verify that this is handled correctly.
bound Type // *Named or *Interface; underlying type is always *Interface
bound Type // any type, but eventually an *Interface for correct programs (see TypeParam.iface)
}
// Obj returns the type name for the type parameter t.
@ -64,15 +63,6 @@ func (t *TypeParam) SetId(id uint64) {
// Constraint returns the type constraint specified for t.
func (t *TypeParam) Constraint() Type {
// compute the type set if possible (we may not have an interface)
if iface, _ := under(t.bound).(*Interface); iface != nil {
// use the type bound position if we have one
pos := nopos
if n, _ := t.bound.(*Named); n != nil {
pos = n.obj.pos
}
computeInterfaceTypeSet(t.check, pos, iface)
}
return t.bound
}
@ -92,10 +82,41 @@ func (t *TypeParam) String() string { return TypeString(t, nil) }
// iface returns the constraint interface of t.
func (t *TypeParam) iface() *Interface {
if iface, _ := under(t.Constraint()).(*Interface); iface != nil {
return iface
bound := t.bound
// determine constraint interface
var ityp *Interface
switch u := under(bound).(type) {
case *Basic:
if u == Typ[Invalid] {
// error is reported elsewhere
return &emptyInterface
}
case *Interface:
ityp = u
case *TypeParam:
// error is reported in Checker.collectTypeParams
return &emptyInterface
}
return &emptyInterface
// If we don't have an interface, wrap constraint into an implicit interface.
// TODO(gri) mark it as implicit - see comment in Checker.bound
if ityp == nil {
ityp = NewInterfaceType(nil, []Type{bound})
t.bound = ityp // update t.bound for next time (optimization)
}
// compute type set if necessary
if ityp.tset == nil {
// use the (original) type bound position if we have one
pos := nopos
if n, _ := bound.(*Named); n != nil {
pos = n.obj.pos
}
computeInterfaceTypeSet(t.check, pos, ityp)
}
return ityp
}
// structuralType returns the structural type of the type parameter's constraint; or nil.

View file

@ -10,7 +10,9 @@ package tparam1
// The predeclared identifier "any" may be used in place of interface{}.
var _ any
func _(_ any)
type _[_ any] struct{}
const N = 10
@ -32,7 +34,7 @@ type C interface{}
func _[T interface{}]() {}
func _[T C]() {}
func _[T struct{}]() {} // ERROR "not an interface"
func _[T struct{}]() {} // ok if #48424 is accepted
func _[T interface{ m() T }]() {}
func _[T1 interface{ m() T2 }, T2 interface{ m() T1 }]() {
var _ T1