mirror of
https://github.com/golang/go
synced 2024-11-02 13:21:55 +00:00
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:
parent
5279e534b5
commit
dab16c1c90
6 changed files with 106 additions and 21 deletions
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
48
src/cmd/compile/internal/types2/testdata/examples/typesets.go2
vendored
Normal file
48
src/cmd/compile/internal/types2/testdata/examples/typesets.go2
vendored
Normal 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 */ ]() {}
|
|
@ -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 */ ]() {}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue