]> Cypherpunks repositories - gostls13.git/commitdiff
cmd/compile/internal/syntax: allow eliding interface in constraint literals
authorRobert Griesemer <gri@golang.org>
Wed, 29 Sep 2021 18:14:05 +0000 (11:14 -0700)
committerRobert Griesemer <gri@golang.org>
Fri, 1 Oct 2021 17:18:20 +0000 (17:18 +0000)
This CL permits an arbitrary type as well as the type sets  ~T and A|B
in constraint position, without the need of a surrrounding interface.
For instance, the type parameter list

[P interface{ ~map[K]V }, K comparable, V interface{ ~string }]

may be written as

[P ~map[K]V, K comparable, V ~string]

The feature must be enabled explicitly with the AllowTypeSets mode
and is only available if AllowGenerics is set as well.

For #48424.

Change-Id: Ic70bb97a49ff75e67e040853eac10e6aed0fef1a
Reviewed-on: https://go-review.googlesource.com/c/go/+/353133
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/syntax/error_test.go
src/cmd/compile/internal/syntax/parser.go
src/cmd/compile/internal/syntax/parser_test.go
src/cmd/compile/internal/syntax/printer_test.go
src/cmd/compile/internal/syntax/syntax.go
src/cmd/compile/internal/syntax/testdata/typeset.go2 [new file with mode: 0644]

index 0952ddc6470c3514c9177ae4572c1ed71723a965..966b36f6bcf1fe244c7d4f145bd113922c7625d3 100644 (file)
@@ -130,7 +130,7 @@ func testSyntaxErrors(t *testing.T, filename string) {
 
        var mode Mode
        if strings.HasSuffix(filename, ".go2") {
-               mode = AllowGenerics | AllowTypeLists
+               mode = AllowGenerics | AllowTypeSets | AllowTypeLists
        }
        ParseFile(filename, func(err error) {
                e, ok := err.(Error)
index 82cb06b1804337af37db1c6928191c72ea5ab093..54e77b9958f77839d19c9bdc45f42e95519f524a 100644 (file)
@@ -87,6 +87,8 @@ func (p *parser) init(file *PosBase, r io.Reader, errh ErrorHandler, pragh Pragm
        p.indent = nil
 }
 
+func (p *parser) allowGenerics() bool { return p.mode&AllowGenerics != 0 }
+
 // takePragma returns the current parsed pragmas
 // and clears them from the parser state.
 func (p *parser) takePragma() Pragma {
@@ -597,7 +599,7 @@ func (p *parser) typeDecl(group *Group) Decl {
                        p.xnest++
                        x := p.expr()
                        p.xnest--
-                       if name0, ok := x.(*Name); p.mode&AllowGenerics != 0 && ok && p.tok != _Rbrack {
+                       if name0, ok := x.(*Name); p.allowGenerics() && ok && p.tok != _Rbrack {
                                // generic type
                                d.TParamList = p.paramList(name0, _Rbrack, true)
                                pos := p.pos()
@@ -687,7 +689,7 @@ func (p *parser) funcDeclOrNil() *FuncDecl {
        }
 
        f.Name = p.name()
-       if p.mode&AllowGenerics != 0 && p.got(_Lbrack) {
+       if p.allowGenerics() && p.got(_Lbrack) {
                if p.tok == _Rbrack {
                        p.syntaxError("empty type parameter list")
                        p.next()
@@ -1425,7 +1427,7 @@ func (p *parser) interfaceType() *InterfaceType {
                switch p.tok {
                case _Name:
                        f := p.methodDecl()
-                       if f.Name == nil && p.mode&AllowGenerics != 0 {
+                       if f.Name == nil && p.allowGenerics() {
                                f = p.embeddedElem(f)
                        }
                        typ.MethodList = append(typ.MethodList, f)
@@ -1443,14 +1445,14 @@ func (p *parser) interfaceType() *InterfaceType {
                        return false
 
                case _Operator:
-                       if p.op == Tilde && p.mode&AllowGenerics != 0 {
+                       if p.op == Tilde && p.allowGenerics() {
                                typ.MethodList = append(typ.MethodList, p.embeddedElem(nil))
                                return false
                        }
 
                case _Type:
                        // TODO(gri) remove TypeList syntax if we accept #45346
-                       if p.mode&AllowGenerics != 0 && p.mode&AllowTypeLists != 0 {
+                       if p.allowGenerics() && p.mode&AllowTypeLists != 0 {
                                type_ := NewName(p.pos(), "type") // cannot have a method named "type"
                                p.next()
                                if p.tok != _Semi && p.tok != _Rbrace {
@@ -1473,7 +1475,7 @@ func (p *parser) interfaceType() *InterfaceType {
                        }
 
                default:
-                       if p.mode&AllowGenerics != 0 {
+                       if p.allowGenerics() {
                                pos := p.pos()
                                if t := p.typeOrNil(); t != nil {
                                        f := new(Field)
@@ -1485,7 +1487,7 @@ func (p *parser) interfaceType() *InterfaceType {
                        }
                }
 
-               if p.mode&AllowGenerics != 0 {
+               if p.allowGenerics() {
                        if p.mode&AllowTypeLists != 0 {
                                p.syntaxError("expecting method, type list, or embedded element")
                                p.advance(_Semi, _Rbrace, _Type)
@@ -1570,7 +1572,7 @@ func (p *parser) fieldDecl(styp *StructType) {
 
                // Careful dance: We don't know if we have an embedded instantiated
                // type T[P1, P2, ...] or a field T of array/slice type [P]E or []E.
-               if p.mode&AllowGenerics != 0 && len(names) == 1 && p.tok == _Lbrack {
+               if p.allowGenerics() && len(names) == 1 && p.tok == _Lbrack {
                        typ = p.arrayOrTArgs()
                        if typ, ok := typ.(*IndexExpr); ok {
                                // embedded type T[P1, P2, ...]
@@ -1708,7 +1710,7 @@ func (p *parser) methodDecl() *Field {
                f.Type = p.funcType()
 
        case _Lbrack:
-               if p.mode&AllowGenerics != 0 {
+               if p.allowGenerics() {
                        // Careful dance: We don't know if we have a generic method m[T C](x T)
                        // or an embedded instantiated type T[P1, P2] (we accept generic methods
                        // for generality and robustness of parsing).
@@ -1846,38 +1848,60 @@ func (p *parser) paramDeclOrNil(name *Name, follow token) *Field {
                defer p.trace("paramDecl")()
        }
 
-       f := new(Field)
+       // type set notation is ok in type parameter lists
+       typeSetsOk := p.mode&AllowTypeSets != 0 && follow == _Rbrack
+
+       pos := p.pos()
        if name != nil {
-               f.pos = name.pos
-       } else {
-               f.pos = p.pos()
+               pos = name.pos
+       } else if typeSetsOk && p.tok == _Operator && p.op == Tilde {
+               // "~" ...
+               return p.embeddedElem(nil)
        }
 
+       f := new(Field)
+       f.pos = pos
+
        if p.tok == _Name || name != nil {
+               // name
                if name == nil {
                        name = p.name()
                }
 
-               if p.mode&AllowGenerics != 0 && p.tok == _Lbrack {
+               if p.allowGenerics() && p.tok == _Lbrack {
+                       // name "[" ...
                        f.Type = p.arrayOrTArgs()
                        if typ, ok := f.Type.(*IndexExpr); ok {
+                               // name "[" ... "]"
                                typ.X = name
                        } else {
+                               // name "[" n "]" E
                                f.Name = name
                        }
                        return f
                }
 
                if p.tok == _Dot {
-                       // name_or_type
+                       // name "." ...
                        f.Type = p.qualifiedName(name)
+                       if typeSetsOk && p.tok == _Operator && p.op == Or {
+                               // name "." name "|" ...
+                               f = p.embeddedElem(f)
+                       }
                        return f
                }
 
+               if typeSetsOk && p.tok == _Operator && p.op == Or {
+                       // name "|" ...
+                       f.Type = name
+                       return p.embeddedElem(f)
+               }
+
                f.Name = name
        }
 
        if p.tok == _DotDotDot {
+               // [name] "..." ...
                t := new(DotsType)
                t.pos = p.pos()
                p.next()
@@ -1890,7 +1914,17 @@ func (p *parser) paramDeclOrNil(name *Name, follow token) *Field {
                return f
        }
 
+       if typeSetsOk && p.tok == _Operator && p.op == Tilde {
+               // [name] "~" ...
+               f.Type = p.embeddedElem(nil).Type
+               return f
+       }
+
        f.Type = p.typeOrNil()
+       if typeSetsOk && p.tok == _Operator && p.op == Or && f.Type != nil {
+               // [name] type "|"
+               f = p.embeddedElem(f)
+       }
        if f.Name != nil || f.Type != nil {
                return f
        }
@@ -1952,7 +1986,7 @@ func (p *parser) paramList(name *Name, close token, requireNames bool) (list []*
                        if par.Type != nil {
                                typ = par.Type
                                if par.Name == nil {
-                                       pos = typ.Pos()
+                                       pos = StartPos(typ)
                                        par.Name = NewName(pos, "_")
                                }
                        } else if typ != nil {
@@ -2654,7 +2688,7 @@ func (p *parser) qualifiedName(name *Name) Expr {
                x = s
        }
 
-       if p.mode&AllowGenerics != 0 && p.tok == _Lbrack {
+       if p.allowGenerics() && p.tok == _Lbrack {
                x = p.typeInstance(x)
        }
 
index 04a16e96fe7eb5a0778c3007854dd13730af601a..fb02c8b95feffe284adb6ce602f0c07aeaa901b6 100644 (file)
@@ -26,11 +26,11 @@ var (
 )
 
 func TestParse(t *testing.T) {
-       ParseFile(*src_, func(err error) { t.Error(err) }, nil, AllowGenerics)
+       ParseFile(*src_, func(err error) { t.Error(err) }, nil, AllowGenerics|AllowTypeSets)
 }
 
 func TestVerify(t *testing.T) {
-       ast, err := ParseFile(*src_, func(err error) { t.Error(err) }, nil, AllowGenerics)
+       ast, err := ParseFile(*src_, func(err error) { t.Error(err) }, nil, AllowGenerics|AllowTypeSets)
        if err != nil {
                return // error already reported
        }
@@ -46,7 +46,7 @@ func TestParseGo2(t *testing.T) {
        for _, fi := range list {
                name := fi.Name()
                if !fi.IsDir() && !strings.HasPrefix(name, ".") {
-                       ParseFile(filepath.Join(dir, name), func(err error) { t.Error(err) }, nil, AllowGenerics|AllowTypeLists)
+                       ParseFile(filepath.Join(dir, name), func(err error) { t.Error(err) }, nil, AllowGenerics|AllowTypeSets|AllowTypeLists)
                }
        }
 }
index d3469a25997f03de2035044290f715e4f3ba7a9e..ee083ad1593a267272f78539e5a97976db692fca 100644 (file)
@@ -72,6 +72,10 @@ var stringTests = []string{
        "package p; func (*R[A, B, C]) _()",
        "package p; func (_ *R[A, B, C]) _()",
 
+       // type constraint literals with elided interfaces (only if AllowTypeSets is set)
+       "package p; func _[P ~int, Q int | string]() {}",
+       "package p; func _[P struct{f int}, Q *P]() {}",
+
        // channels
        "package p; type _ chan chan int",
        "package p; type _ chan (<-chan int)",
@@ -90,7 +94,7 @@ var stringTests = []string{
 
 func TestPrintString(t *testing.T) {
        for _, want := range stringTests {
-               ast, err := Parse(nil, strings.NewReader(want), nil, nil, AllowGenerics|AllowTypeLists)
+               ast, err := Parse(nil, strings.NewReader(want), nil, nil, AllowGenerics|AllowTypeSets|AllowTypeLists)
                if err != nil {
                        t.Error(err)
                        continue
index 08f450c94f9b607736c64668fb526d54357b7ac2..8828c39ad55d01fea9e8d8374c5be1eeeef4f9e9 100644 (file)
@@ -17,6 +17,7 @@ type Mode uint
 const (
        CheckBranches Mode = 1 << iota // check correct use of labels, break, continue, and goto statements
        AllowGenerics
+       AllowTypeSets  // requires AllowGenerics; remove once #48424 is decided
        AllowTypeLists // requires AllowGenerics; remove once 1.18 is out
 )
 
diff --git a/src/cmd/compile/internal/syntax/testdata/typeset.go2 b/src/cmd/compile/internal/syntax/testdata/typeset.go2
new file mode 100644 (file)
index 0000000..a173bb1
--- /dev/null
@@ -0,0 +1,72 @@
+// 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 contains test cases for typeset-only constraint elements.
+// TODO(gri) gofmt once/if gofmt supports this notation.
+
+package p
+
+type (
+        _[_ t] t
+        _[_ ~t] t
+        _[_ t|t] t
+        _[_ ~t|t] t
+        _[_ t|~t] t
+        _[_ ~t|~t] t
+
+        _[_ t, _, _ t|t] t
+        _[_ t, _, _ ~t|t] t
+        _[_ t, _, _ t|~t] t
+        _[_ t, _, _ ~t|~t] t
+
+        _[_ t.t] t
+        _[_ ~t.t] t
+        _[_ t.t|t.t] t
+        _[_ ~t.t|t.t] t
+        _[_ t.t|~t.t] t
+        _[_ ~t.t|~t.t] t
+
+        _[_ t, _, _ t.t|t.t] t
+        _[_ t, _, _ ~t.t|t.t] t
+        _[_ t, _, _ t.t|~t.t] t
+        _[_ t, _, _ ~t.t|~t.t] t
+
+        _[_ struct{}] t
+        _[_ ~struct{}] t
+
+        _[_ struct{}|t] t
+        _[_ ~struct{}|t] t
+        _[_ struct{}|~t] t
+        _[_ ~struct{}|~t] t
+
+        _[_ t|struct{}] t
+        _[_ ~t|struct{}] t
+        _[_ t|~struct{}] t
+        _[_ ~t|~struct{}] t
+)
+
+// Single-expression type parameter lists and those that don't start
+// with a (type parameter) name are considered array sizes.
+// The term must be a valid expression (it could be a type - and then
+// a type-checker will complain - but we don't allow ~ in the expr).
+type (
+        _[t] t
+        _[/* ERROR unexpected ~ */ ~t] t
+        _[t|t] t
+        _[/* ERROR unexpected ~ */ ~t|t] t
+        _[t| /* ERROR unexpected ~ */ ~t] t
+        _[/* ERROR unexpected ~ */ ~t|~t] t
+)
+
+type (
+        _[_ t, t /* ERROR missing type constraint */ ] t
+        _[_ ~t, t /* ERROR missing type constraint */ ] t
+        _[_ t, /* ERROR type parameters must be named */ ~t] t
+        _[_ ~t, /* ERROR type parameters must be named */ ~t] t
+
+        _[_ t|t, /* ERROR type parameters must be named */ t|t] t
+        _[_ ~t|t, /* ERROR type parameters must be named */ t|t] t
+        _[_ t|t, /* ERROR type parameters must be named */ ~t|t] t
+        _[_ ~t|t, /* ERROR type parameters must be named */ ~t|t] t
+)