]> Cypherpunks repositories - gostls13.git/commitdiff
go/parser: fix parsing of array or slice constraint types
authorRobert Findley <rfindley@google.com>
Wed, 27 Oct 2021 13:19:50 +0000 (09:19 -0400)
committerRobert Findley <rfindley@google.com>
Thu, 28 Oct 2021 15:32:59 +0000 (15:32 +0000)
Now that we allow eliding 'interface' from constraint types, we need to
be a bit more careful about not consuming a '[' when parsing the next
expression after "type T [". We want to check if the next expression is
an identifier not followed by ']', in which case we're in a generic
type, but need to avoid parsing index or slice expressions. Such
expressions aren't valid array lengths because these expressions are
never constant, so when encountering a following '[' we can instead
assume that this is a type parameter field with array or slice type
constraint.

Test cases are added for the related issues #49174 and #49175, along
with a flag to enable tracing error tests.

For #49174
For #49175

Change-Id: I0476ef20c4c134ac537118272f20caaf123ee70e
Reviewed-on: https://go-review.googlesource.com/c/go/+/359134
Trust: Robert Findley <rfindley@google.com>
Run-TryBot: Robert Findley <rfindley@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Robert Griesemer <gri@golang.org>
src/go/parser/error_test.go
src/go/parser/parser.go
src/go/parser/testdata/issue49174.go2 [new file with mode: 0644]
src/go/parser/testdata/issue49175.go2 [new file with mode: 0644]

index f35ba0b50173c9b8fcd8f2d8f7c52f20c503510e..a45c897da364045d57ca5741e5f3bf9031b4b609 100644 (file)
@@ -23,6 +23,7 @@
 package parser
 
 import (
+       "flag"
        "go/internal/typeparams"
        "go/scanner"
        "go/token"
@@ -33,6 +34,8 @@ import (
        "testing"
 )
 
+var traceErrs = flag.Bool("trace_errs", false, "whether to enable tracing for error tests")
+
 const testdata = "testdata"
 
 // getFile assumes that each filename occurs at most once
@@ -192,6 +195,9 @@ func TestErrors(t *testing.T) {
                                if !strings.HasSuffix(name, ".go2") {
                                        mode |= typeparams.DisallowParsing
                                }
+                               if *traceErrs {
+                                       mode |= Trace
+                               }
                                checkErrors(t, filepath.Join(testdata, name), nil, mode, true)
                        }
                })
index 4f7a4987805fb7ec0dc2a7b4070b861b7b204df7..999663b98c74550cf588712e25f233c4fa25135e 100644 (file)
@@ -455,10 +455,10 @@ func (p *parser) parseExprList() (list []ast.Expr) {
                defer un(trace(p, "ExpressionList"))
        }
 
-       list = append(list, p.checkExpr(p.parseExpr()))
+       list = append(list, p.checkExpr(p.parseExpr(nil)))
        for p.tok == token.COMMA {
                p.next()
-               list = append(list, p.checkExpr(p.parseExpr()))
+               list = append(list, p.checkExpr(p.parseExpr(nil)))
        }
 
        return
@@ -996,7 +996,7 @@ func (p *parser) parseMethodSpec() *ast.Field {
                        lbrack := p.pos
                        p.next()
                        p.exprLev++
-                       x := p.parseExpr()
+                       x := p.parseExpr(nil)
                        p.exprLev--
                        if name0, _ := x.(*ast.Ident); name0 != nil && p.tok != token.COMMA && p.tok != token.RBRACK {
                                // generic method m[T any]
@@ -1538,7 +1538,7 @@ func (p *parser) parseValue() ast.Expr {
                return p.parseLiteralValue(nil)
        }
 
-       x := p.checkExpr(p.parseExpr())
+       x := p.checkExpr(p.parseExpr(nil))
 
        return x
 }
@@ -1648,12 +1648,14 @@ func (p *parser) checkExprOrType(x ast.Expr) ast.Expr {
        return x
 }
 
-func (p *parser) parsePrimaryExpr() (x ast.Expr) {
+func (p *parser) parsePrimaryExpr(x ast.Expr) ast.Expr {
        if p.trace {
                defer un(trace(p, "PrimaryExpr"))
        }
 
-       x = p.parseOperand()
+       if x == nil {
+               x = p.parseOperand()
+       }
        for {
                switch p.tok {
                case token.PERIOD:
@@ -1689,18 +1691,18 @@ func (p *parser) parsePrimaryExpr() (x ast.Expr) {
                        switch t.(type) {
                        case *ast.BadExpr, *ast.Ident, *ast.SelectorExpr:
                                if p.exprLev < 0 {
-                                       return
+                                       return x
                                }
                                // x is possibly a composite literal type
                        case *ast.IndexExpr, *ast.IndexListExpr:
                                if p.exprLev < 0 {
-                                       return
+                                       return x
                                }
                                // x is possibly a composite literal type
                        case *ast.ArrayType, *ast.StructType, *ast.MapType:
                                // x is a composite literal type
                        default:
-                               return
+                               return x
                        }
                        if t != x {
                                p.error(t.Pos(), "cannot parenthesize type in composite literal")
@@ -1708,7 +1710,7 @@ func (p *parser) parsePrimaryExpr() (x ast.Expr) {
                        }
                        x = p.parseLiteralValue(x)
                default:
-                       return
+                       return x
                }
        }
 }
@@ -1779,7 +1781,7 @@ func (p *parser) parseUnaryExpr() ast.Expr {
                return &ast.StarExpr{Star: pos, X: p.checkExprOrType(x)}
        }
 
-       return p.parsePrimaryExpr()
+       return p.parsePrimaryExpr(nil)
 }
 
 func (p *parser) tokPrec() (token.Token, int) {
@@ -1790,19 +1792,21 @@ func (p *parser) tokPrec() (token.Token, int) {
        return tok, tok.Precedence()
 }
 
-func (p *parser) parseBinaryExpr(prec1 int) ast.Expr {
+func (p *parser) parseBinaryExpr(x ast.Expr, prec1 int) ast.Expr {
        if p.trace {
                defer un(trace(p, "BinaryExpr"))
        }
 
-       x := p.parseUnaryExpr()
+       if x == nil {
+               x = p.parseUnaryExpr()
+       }
        for {
                op, oprec := p.tokPrec()
                if oprec < prec1 {
                        return x
                }
                pos := p.expect(op)
-               y := p.parseBinaryExpr(oprec + 1)
+               y := p.parseBinaryExpr(nil, oprec+1)
                x = &ast.BinaryExpr{X: p.checkExpr(x), OpPos: pos, Op: op, Y: p.checkExpr(y)}
        }
 }
@@ -1810,18 +1814,18 @@ func (p *parser) parseBinaryExpr(prec1 int) ast.Expr {
 // The result may be a type or even a raw type ([...]int). Callers must
 // check the result (using checkExpr or checkExprOrType), depending on
 // context.
-func (p *parser) parseExpr() ast.Expr {
+func (p *parser) parseExpr(lhs ast.Expr) ast.Expr {
        if p.trace {
                defer un(trace(p, "Expression"))
        }
 
-       return p.parseBinaryExpr(token.LowestPrec + 1)
+       return p.parseBinaryExpr(lhs, token.LowestPrec+1)
 }
 
 func (p *parser) parseRhs() ast.Expr {
        old := p.inRhs
        p.inRhs = true
-       x := p.checkExpr(p.parseExpr())
+       x := p.checkExpr(p.parseExpr(nil))
        p.inRhs = old
        return x
 }
@@ -1829,7 +1833,7 @@ func (p *parser) parseRhs() ast.Expr {
 func (p *parser) parseRhsOrType() ast.Expr {
        old := p.inRhs
        p.inRhs = true
-       x := p.checkExprOrType(p.parseExpr())
+       x := p.checkExprOrType(p.parseExpr(nil))
        p.inRhs = old
        return x
 }
@@ -2539,6 +2543,10 @@ func (p *parser) parseValueSpec(doc *ast.CommentGroup, _ token.Pos, keyword toke
 }
 
 func (p *parser) parseGenericType(spec *ast.TypeSpec, openPos token.Pos, name0 *ast.Ident) {
+       if p.trace {
+               defer un(trace(p, "parseGenericType"))
+       }
+
        list := p.parseParameterList(name0, token.RBRACK)
        closePos := p.expect(token.RBRACK)
        spec.TypeParams = &ast.FieldList{Opening: openPos, List: list, Closing: closePos}
@@ -2563,19 +2571,34 @@ func (p *parser) parseTypeSpec(doc *ast.CommentGroup, _ token.Pos, _ token.Token
                lbrack := p.pos
                p.next()
                if p.tok == token.IDENT {
-                       // array type or generic type [T any]
-                       p.exprLev++
-                       x := p.parseExpr()
-                       p.exprLev--
-                       if name0, _ := x.(*ast.Ident); p.allowGenerics() && name0 != nil && p.tok != token.RBRACK {
-                               // generic type [T any];
+                       // array type or generic type: [name0...
+                       name0 := p.parseIdent()
+
+                       if p.allowGenerics() && p.tok == token.LBRACK {
+                               // Index or slice expressions are not valid array lengths, so we can
+                               // parse as though we are in a generic type with array or slice
+                               // constraint: [T [...
                                p.parseGenericType(spec, lbrack, name0)
+                               break
                        } else {
-                               // array type
-                               // TODO(rfindley) should resolve all identifiers in x.
-                               p.expect(token.RBRACK)
-                               elt := p.parseType()
-                               spec.Type = &ast.ArrayType{Lbrack: lbrack, Len: x, Elt: elt}
+
+                               // We may still have either an array type or generic type -- check if
+                               // name0 is the entire expr.
+                               p.exprLev++
+                               lhs := p.parsePrimaryExpr(name0)
+                               x := p.parseExpr(lhs)
+                               p.exprLev--
+
+                               if name1, _ := x.(*ast.Ident); p.allowGenerics() && name1 != nil && p.tok != token.RBRACK {
+                                       // generic type [T any];
+                                       p.parseGenericType(spec, lbrack, name1)
+                               } else {
+                                       // array type
+                                       // TODO(rfindley) should resolve all identifiers in x.
+                                       p.expect(token.RBRACK)
+                                       elt := p.parseType()
+                                       spec.Type = &ast.ArrayType{Lbrack: lbrack, Len: x, Elt: elt}
+                               }
                        }
                } else {
                        // array type
diff --git a/src/go/parser/testdata/issue49174.go2 b/src/go/parser/testdata/issue49174.go2
new file mode 100644 (file)
index 0000000..77c1950
--- /dev/null
@@ -0,0 +1,8 @@
+// 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.
+
+package p
+
+func _[_ []int | int]() {}
+func _[_ int | []int]() {}
diff --git a/src/go/parser/testdata/issue49175.go2 b/src/go/parser/testdata/issue49175.go2
new file mode 100644 (file)
index 0000000..a5ad30f
--- /dev/null
@@ -0,0 +1,13 @@
+// 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.
+
+package p
+
+type _[_ []t]t
+type _[_ [1]t]t
+
+func _[_ []t]() {}
+func _[_ [1]t]() {}
+
+type t [t /* ERROR "all type parameters must be named" */ [0]]t