Skip to content

Commit

Permalink
cmd/compile/internal/types2: accept constraint literals with elided i…
Browse files Browse the repository at this point in the history
…nterfaces

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>
  • Loading branch information
griesemer committed Oct 1, 2021
1 parent 5279e53 commit dab16c1
Show file tree
Hide file tree
Showing 6 changed files with 106 additions and 21 deletions.
2 changes: 1 addition & 1 deletion src/cmd/compile/internal/types2/check_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
22 changes: 18 additions & 4 deletions src/cmd/compile/internal/types2/decl.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
48 changes: 48 additions & 0 deletions src/cmd/compile/internal/types2/testdata/examples/typesets.go2
Original file line number Diff line number Diff line change
@@ -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 */ ]() {}
Original file line number Diff line number Diff line change
Expand Up @@ -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 */ ]() {}
49 changes: 35 additions & 14 deletions src/cmd/compile/internal/types2/typeparam.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
}

Expand All @@ -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
}

// 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)
}
return &emptyInterface

// 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.
Expand Down
4 changes: 3 additions & 1 deletion test/typeparam/tparam1.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down

0 comments on commit dab16c1

Please sign in to comment.