]> Cypherpunks.ru repositories - gostls13.git/commitdiff
cmd/compile/internal/types2: accept constraint literals with elided interfaces
authorRobert Griesemer <gri@golang.org>
Thu, 30 Sep 2021 03:56:36 +0000 (20:56 -0700)
committerRobert Griesemer <gri@golang.org>
Fri, 1 Oct 2021 17:18:34 +0000 (17:18 +0000)
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>
src/cmd/compile/internal/types2/check_test.go
src/cmd/compile/internal/types2/decl.go
src/cmd/compile/internal/types2/testdata/examples/typesets.go2 [new file with mode: 0644]
src/cmd/compile/internal/types2/testdata/fixedbugs/issue39723.go2
src/cmd/compile/internal/types2/typeparam.go
test/typeparam/tparam1.go

index 26c8eba7273a4c5dfa2876185674add51467cf2b..1ca2eea5c6ac6579e5e20fed8b938ee579163e28 100644 (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)
index 128e89dec65d7f2ecc9afe2d1e730764f6bff801..10c63355e94209ed3bc59178db515f9cf072e143 100644 (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
diff --git a/src/cmd/compile/internal/types2/testdata/examples/typesets.go2 b/src/cmd/compile/internal/types2/testdata/examples/typesets.go2
new file mode 100644 (file)
index 0000000..0a1b0f5
--- /dev/null
@@ -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 */ ]() {}
index d5311ed3e7538e9350a0df3dc23b137a88503ae4..00885238e69c9a26ce5ef6f44539c0cba86152b1 100644 (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 */ ]() {}
index 505596f57192907a5693bca9dba411ae361bfd55..c295702fe5942bdcc42da97c4d7ab6ae91c92ae7 100644 (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
+       }
+
+       // 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.
index 3b4260c1023f053eca960f53627925d1fca4e604..ef024ce40f08b6516bee45da6f57b605cfc6be70 100644 (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