From 5279e534b5ced1eaea19e8b75968498287ff6228 Mon Sep 17 00:00:00 2001 From: Robert Griesemer Date: Wed, 29 Sep 2021 11:14:05 -0700 Subject: [PATCH] cmd/compile/internal/syntax: allow eliding interface in constraint literals 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 Run-TryBot: Robert Griesemer TryBot-Result: Go Bot Reviewed-by: Robert Findley --- src/cmd/compile/internal/syntax/error_test.go | 2 +- src/cmd/compile/internal/syntax/parser.go | 68 +++++++++++++----- .../compile/internal/syntax/parser_test.go | 6 +- .../compile/internal/syntax/printer_test.go | 6 +- src/cmd/compile/internal/syntax/syntax.go | 1 + .../internal/syntax/testdata/typeset.go2 | 72 +++++++++++++++++++ 6 files changed, 133 insertions(+), 22 deletions(-) create mode 100644 src/cmd/compile/internal/syntax/testdata/typeset.go2 diff --git a/src/cmd/compile/internal/syntax/error_test.go b/src/cmd/compile/internal/syntax/error_test.go index 0952ddc6470c3..966b36f6bcf1f 100644 --- a/src/cmd/compile/internal/syntax/error_test.go +++ b/src/cmd/compile/internal/syntax/error_test.go @@ -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) diff --git a/src/cmd/compile/internal/syntax/parser.go b/src/cmd/compile/internal/syntax/parser.go index 82cb06b180433..54e77b9958f77 100644 --- a/src/cmd/compile/internal/syntax/parser.go +++ b/src/cmd/compile/internal/syntax/parser.go @@ -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) } diff --git a/src/cmd/compile/internal/syntax/parser_test.go b/src/cmd/compile/internal/syntax/parser_test.go index 04a16e96fe7eb..fb02c8b95feff 100644 --- a/src/cmd/compile/internal/syntax/parser_test.go +++ b/src/cmd/compile/internal/syntax/parser_test.go @@ -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) } } } diff --git a/src/cmd/compile/internal/syntax/printer_test.go b/src/cmd/compile/internal/syntax/printer_test.go index d3469a25997f0..ee083ad1593a2 100644 --- a/src/cmd/compile/internal/syntax/printer_test.go +++ b/src/cmd/compile/internal/syntax/printer_test.go @@ -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 diff --git a/src/cmd/compile/internal/syntax/syntax.go b/src/cmd/compile/internal/syntax/syntax.go index 08f450c94f9b6..8828c39ad55d0 100644 --- a/src/cmd/compile/internal/syntax/syntax.go +++ b/src/cmd/compile/internal/syntax/syntax.go @@ -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 index 0000000000000..a173bb1d4ff07 --- /dev/null +++ b/src/cmd/compile/internal/syntax/testdata/typeset.go2 @@ -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 +)