aboutsummaryrefslogtreecommitdiff
path: root/src/cmd/compile/internal/syntax/nodes_test.go
diff options
context:
space:
mode:
authorRobert Griesemer <gri@golang.org>2017-02-13 16:00:53 -0800
committerRobert Griesemer <gri@golang.org>2017-02-15 01:33:03 +0000
commit5267ac2732edd1ba4a13773987dff08e8b4a2dde (patch)
treee75fba029f50ffce20fa05525d2349cf7813bf74 /src/cmd/compile/internal/syntax/nodes_test.go
parent6910756f9b8c7a97b1435ec40b8ebff9655611d7 (diff)
downloadgo-5267ac2732edd1ba4a13773987dff08e8b4a2dde.tar.gz
go-5267ac2732edd1ba4a13773987dff08e8b4a2dde.zip
cmd/compile/internal/syntax: establish principled position information
Until now, the parser set the position for each Node to the position of the first token belonging to that node. For compatibility with the now defunct gc parser, in many places that position information was modified when the gcCompat flag was set (which it was, by default). Furthermore, in some places, position information was not set at all. This change removes the gcCompat flag and all associated code, and sets position information for all nodes in a more principled way, as proposed by mdempsky (see #16943 for details). Specifically, the position of a node may not be at the very beginning of the respective production. For instance for an Operation `a + b`, the position associated with the node is the position of the `+`. Thus, for `a + b + c` we now get different positions for the two additions. This change does not pass toolstash -cmp because position information recorded in export data and pcline tables is different. There are no other functional changes. Added test suite testing the position of all nodes. Fixes #16943. Change-Id: I3fc02bf096bc3b3d7d2fa655dfd4714a1a0eb90c Reviewed-on: https://go-review.googlesource.com/37017 Run-TryBot: Robert Griesemer <gri@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Matthew Dempsky <mdempsky@google.com>
Diffstat (limited to 'src/cmd/compile/internal/syntax/nodes_test.go')
-rw-r--r--src/cmd/compile/internal/syntax/nodes_test.go321
1 files changed, 321 insertions, 0 deletions
diff --git a/src/cmd/compile/internal/syntax/nodes_test.go b/src/cmd/compile/internal/syntax/nodes_test.go
new file mode 100644
index 0000000000..6b4119c70b
--- /dev/null
+++ b/src/cmd/compile/internal/syntax/nodes_test.go
@@ -0,0 +1,321 @@
+// Copyright 2017 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 syntax
+
+import (
+ "fmt"
+ "strings"
+ "testing"
+)
+
+// A test is a source code snippet of a particular node type.
+// In the snippet, a '@' indicates the position recorded by
+// the parser when creating the respective node.
+type test struct {
+ nodetyp string
+ snippet string
+}
+
+var decls = []test{
+ // The position of declarations is always the
+ // position of the first token of an individual
+ // declaration, independent of grouping.
+ {"ImportDecl", `import @"math"`},
+ {"ImportDecl", `import @mymath "math"`},
+ {"ImportDecl", `import @. "math"`},
+ {"ImportDecl", `import (@"math")`},
+ {"ImportDecl", `import (@mymath "math")`},
+ {"ImportDecl", `import (@. "math")`},
+
+ {"ConstDecl", `const @x`},
+ {"ConstDecl", `const @x = 0`},
+ {"ConstDecl", `const @x, y, z = 0, 1, 2`},
+ {"ConstDecl", `const (@x)`},
+ {"ConstDecl", `const (@x = 0)`},
+ {"ConstDecl", `const (@x, y, z = 0, 1, 2)`},
+
+ {"TypeDecl", `type @T int`},
+ {"TypeDecl", `type @T = int`},
+ {"TypeDecl", `type (@T int)`},
+ {"TypeDecl", `type (@T = int)`},
+
+ {"VarDecl", `var @x int`},
+ {"VarDecl", `var @x, y, z int`},
+ {"VarDecl", `var @x int = 0`},
+ {"VarDecl", `var @x, y, z int = 1, 2, 3`},
+ {"VarDecl", `var @x = 0`},
+ {"VarDecl", `var @x, y, z = 1, 2, 3`},
+ {"VarDecl", `var (@x int)`},
+ {"VarDecl", `var (@x, y, z int)`},
+ {"VarDecl", `var (@x int = 0)`},
+ {"VarDecl", `var (@x, y, z int = 1, 2, 3)`},
+ {"VarDecl", `var (@x = 0)`},
+ {"VarDecl", `var (@x, y, z = 1, 2, 3)`},
+
+ {"FuncDecl", `func @f() {}`},
+ {"FuncDecl", `func @(T) f() {}`},
+ {"FuncDecl", `func @(x T) f() {}`},
+}
+
+var exprs = []test{
+ // The position of an expression is the position
+ // of the left-most token that identifies the
+ // kind of expression.
+ {"Name", `@x`},
+
+ {"BasicLit", `@0`},
+ {"BasicLit", `@0x123`},
+ {"BasicLit", `@3.1415`},
+ {"BasicLit", `@.2718`},
+ {"BasicLit", `@1i`},
+ {"BasicLit", `@'a'`},
+ {"BasicLit", `@"abc"`},
+ {"BasicLit", "@`abc`"},
+
+ {"CompositeLit", `@{}`},
+ {"CompositeLit", `T@{}`},
+ {"CompositeLit", `struct{x, y int}@{}`},
+
+ {"KeyValueExpr", `"foo"@: true`},
+ {"KeyValueExpr", `"a"@: b`},
+
+ {"FuncLit", `@func (){}`},
+ {"ParenExpr", `@(x)`},
+ {"SelectorExpr", `a@.b`},
+ {"IndexExpr", `a@[i]`},
+
+ {"SliceExpr", `a@[:]`},
+ {"SliceExpr", `a@[i:]`},
+ {"SliceExpr", `a@[:j]`},
+ {"SliceExpr", `a@[i:j]`},
+ {"SliceExpr", `a@[i:j:k]`},
+
+ {"AssertExpr", `x@.(T)`},
+
+ {"Operation", `@*b`},
+ {"Operation", `@+b`},
+ {"Operation", `@-b`},
+ {"Operation", `@!b`},
+ {"Operation", `@^b`},
+ {"Operation", `@&b`},
+ {"Operation", `@<-b`},
+
+ {"Operation", `a @|| b`},
+ {"Operation", `a @&& b`},
+ {"Operation", `a @== b`},
+ {"Operation", `a @+ b`},
+ {"Operation", `a @* b`},
+
+ {"CallExpr", `f@()`},
+ {"CallExpr", `f@(x, y, z)`},
+ {"CallExpr", `obj.f@(1, 2, 3)`},
+ {"CallExpr", `func(x int) int { return x + 1 }@(y)`},
+
+ // ListExpr: tested via multi-value const/var declarations
+}
+
+var types = []test{
+ {"Operation", `@*T`},
+ {"Operation", `@*struct{}`},
+
+ {"ArrayType", `@[10]T`},
+ {"ArrayType", `@[...]T`},
+
+ {"SliceType", `@[]T`},
+ {"DotsType", `@...T`},
+ {"StructType", `@struct{}`},
+ {"InterfaceType", `@interface{}`},
+ {"FuncType", `func@()`},
+ {"MapType", `@map[T]T`},
+
+ {"ChanType", `@chan T`},
+ {"ChanType", `@chan<- T`},
+ {"ChanType", `@<-chan T`},
+}
+
+var fields = []test{
+ {"Field", `@T`},
+ {"Field", `@(T)`},
+ {"Field", `@x T`},
+ {"Field", `@x *(T)`},
+ {"Field", `@x, y, z T`},
+ {"Field", `@x, y, z (*T)`},
+}
+
+var stmts = []test{
+ {"EmptyStmt", `@;`},
+
+ {"LabeledStmt", `L@:`},
+ {"LabeledStmt", `L@: ;`},
+ {"LabeledStmt", `L@: f()`},
+
+ {"BlockStmt", `@{}`},
+
+ // The position of an ExprStmt is the position of the expression.
+ {"ExprStmt", `@<-ch`},
+ {"ExprStmt", `f@()`},
+ {"ExprStmt", `append@(s, 1, 2, 3)`},
+
+ {"SendStmt", `ch @<- x`},
+
+ {"DeclStmt", `@const x = 0`},
+ {"DeclStmt", `@const (x = 0)`},
+ {"DeclStmt", `@type T int`},
+ {"DeclStmt", `@type T = int`},
+ {"DeclStmt", `@type (T1 = int; T2 = float32)`},
+ {"DeclStmt", `@var x = 0`},
+ {"DeclStmt", `@var x, y, z int`},
+ {"DeclStmt", `@var (a, b = 1, 2)`},
+
+ {"AssignStmt", `x @= y`},
+ {"AssignStmt", `a, b, x @= 1, 2, 3`},
+ {"AssignStmt", `x @+= y`},
+ {"AssignStmt", `x @:= y`},
+ {"AssignStmt", `x, ok @:= f()`},
+ {"AssignStmt", `x@++`},
+ {"AssignStmt", `a[i]@--`},
+
+ {"BranchStmt", `@break`},
+ {"BranchStmt", `@break L`},
+ {"BranchStmt", `@continue`},
+ {"BranchStmt", `@continue L`},
+ {"BranchStmt", `@fallthrough`},
+ {"BranchStmt", `@goto L`},
+
+ {"CallStmt", `@defer f()`},
+ {"CallStmt", `@go f()`},
+
+ {"ReturnStmt", `@return`},
+ {"ReturnStmt", `@return x`},
+ {"ReturnStmt", `@return a, b, c`},
+
+ {"IfStmt", `@if cond {}`},
+ {"ForStmt", `@for {}`},
+ {"SwitchStmt", `@switch {}`},
+ {"SelectStmt", `@select {}`},
+}
+
+var ranges = []test{
+ {"RangeClause", `for @range s {}`},
+ {"RangeClause", `for _, i = @range s {}`},
+ {"RangeClause", `for x, i = @range s {}`},
+ {"RangeClause", `for _, i := @range s {}`},
+ {"RangeClause", `for x, i := @range s {}`},
+}
+
+var guards = []test{
+ {"TypeSwitchGuard", `switch x@.(type) {}`},
+ {"TypeSwitchGuard", `switch x := x@.(type) {}`},
+ {"TypeSwitchGuard", `switch a = b; x@.(type) {}`},
+ {"TypeSwitchGuard", `switch a := b; x := x@.(type) {}`},
+}
+
+var cases = []test{
+ {"CaseClause", ` switch { @case x: }`},
+ {"CaseClause", ` switch { @case x, y, z: }`},
+ {"CaseClause", ` switch { @case x == 1, y == 2: }`},
+ {"CaseClause", ` switch { @default: }`},
+}
+
+var comms = []test{
+ {"CommClause", `select { @case <-ch: }`},
+ {"CommClause", `select { @case x <- ch: }`},
+ {"CommClause", `select { @case x = <-ch: }`},
+ {"CommClause", `select { @case x := <-ch: }`},
+ {"CommClause", `select { @case x, ok = <-ch: }`},
+ {"CommClause", `select { @case x, ok := <-ch: }`},
+ {"CommClause", `select { @default: }`},
+}
+
+func TestPos(t *testing.T) {
+ // TODO(gri) Once we have a general tree walker, we can use that to find
+ // the first occurence of the respective node and we don't need to hand-
+ // extract the node for each specific kind of construct.
+
+ testPos(t, decls, "package p; ", "",
+ func(f *File) Node { return f.DeclList[0] },
+ )
+
+ // embed expressions in a composite literal so we can test key:value and naked composite literals
+ testPos(t, exprs, "package p; var _ = T{ ", " }",
+ func(f *File) Node { return f.DeclList[0].(*VarDecl).Values.(*CompositeLit).ElemList[0] },
+ )
+
+ // embed types in a function signature so we can test ... types
+ testPos(t, types, "package p; func f(", ")",
+ func(f *File) Node { return f.DeclList[0].(*FuncDecl).Type.ParamList[0].Type },
+ )
+
+ testPos(t, fields, "package p; func f(", ")",
+ func(f *File) Node { return f.DeclList[0].(*FuncDecl).Type.ParamList[0] },
+ )
+
+ testPos(t, stmts, "package p; func _() { ", " } ",
+ func(f *File) Node { return f.DeclList[0].(*FuncDecl).Body[0] },
+ )
+
+ testPos(t, ranges, "package p; func _() { ", " } ",
+ func(f *File) Node { return f.DeclList[0].(*FuncDecl).Body[0].(*ForStmt).Init.(*RangeClause) },
+ )
+
+ testPos(t, guards, "package p; func _() { ", " } ",
+ func(f *File) Node { return f.DeclList[0].(*FuncDecl).Body[0].(*SwitchStmt).Tag.(*TypeSwitchGuard) },
+ )
+
+ testPos(t, cases, "package p; func _() { ", " } ",
+ func(f *File) Node { return f.DeclList[0].(*FuncDecl).Body[0].(*SwitchStmt).Body[0] },
+ )
+
+ testPos(t, comms, "package p; func _() { ", " } ",
+ func(f *File) Node { return f.DeclList[0].(*FuncDecl).Body[0].(*SelectStmt).Body[0] },
+ )
+}
+
+func testPos(t *testing.T, list []test, prefix, suffix string, extract func(*File) Node) {
+ for _, test := range list {
+ // complete source, compute @ position, and strip @ from source
+ src, index := stripAt(prefix + test.snippet + suffix)
+ if index < 0 {
+ t.Errorf("missing @: %s", src)
+ continue
+ }
+
+ // build syntaxt tree
+ file, err := ParseBytes(nil, []byte(src), nil, nil, 0)
+ if err != nil {
+ t.Errorf("parse error: %s: %v", src, err)
+ continue
+ }
+
+ // extract desired node
+ node := extract(file)
+ if typ := typeOf(node); typ != test.nodetyp {
+ t.Errorf("type error: %s: type = %s, want %s", src, typ, test.nodetyp)
+ continue
+ }
+
+ // verify node position with expected position as indicated by @
+ if col := int(node.Pos().Col()); col != index {
+ t.Errorf("pos error: %s: col = %d, want %d", src, col, index)
+ continue
+ }
+ }
+}
+
+func stripAt(s string) (string, int) {
+ if i := strings.Index(s, "@"); i >= 0 {
+ return s[:i] + s[i+1:], i
+ }
+ return s, -1
+}
+
+func typeOf(n Node) string {
+ const prefix = "*syntax."
+ k := fmt.Sprintf("%T", n)
+ if strings.HasPrefix(k, prefix) {
+ return k[len(prefix):]
+ }
+ return k
+}