From fb3d862caeee3404de8ce066ee4eeb4b7ec2955b Mon Sep 17 00:00:00 2001 From: Alan Donovan Date: Mon, 9 Dec 2013 09:36:29 -0500 Subject: [PATCH] go.tools/importer: move PathEnclosingInterval to package astutil. R=crawshaw CC=golang-dev https://golang.org/cl/38650044 --- importer/source.go => astutil/enclosing.go | 33 +--- astutil/enclosing_test.go | 195 +++++++++++++++++++++ importer/importer.go | 29 +++ importer/source_test.go | 162 +---------------- oracle/describe.go | 13 +- oracle/oracle.go | 3 +- ssa/dom.go | 2 +- ssa/source_test.go | 5 +- 8 files changed, 241 insertions(+), 201 deletions(-) rename importer/source.go => astutil/enclosing.go (93%) create mode 100644 astutil/enclosing_test.go diff --git a/importer/source.go b/astutil/enclosing.go similarity index 93% rename from importer/source.go rename to astutil/enclosing.go index 3279bff9..f5b17876 100644 --- a/importer/source.go +++ b/astutil/enclosing.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -package importer +package astutil // This file defines utilities for working with source positions. @@ -54,7 +54,7 @@ import ( // // Precondition: [start, end) both lie within the same file as root. // TODO(adonovan): return (nil, false) in this case and remove precond. -// Requires FileSet; see tokenFileContainsPos. +// Requires FileSet; see importer.tokenFileContainsPos. // // Postcondition: path is never nil; it always contains at least 'root'. // @@ -623,32 +623,3 @@ func NodeDescription(n ast.Node) string { } panic(fmt.Sprintf("unexpected node type: %T", n)) } - -// TODO(adonovan): make this a method: func (*token.File) Contains(token.Pos) -func tokenFileContainsPos(f *token.File, pos token.Pos) bool { - p := int(pos) - base := f.Base() - return base <= p && p < base+f.Size() -} - -// PathEnclosingInterval returns the PackageInfo and ast.Node that -// contain source interval [start, end), and all the node's ancestors -// up to the AST root. It searches all ast.Files of all packages in the -// Importer imp. exact is defined as for standalone -// PathEnclosingInterval. -// -// The result is (nil, nil, false) if not found. -// -func (imp *Importer) PathEnclosingInterval(start, end token.Pos) (pkg *PackageInfo, path []ast.Node, exact bool) { - for _, info := range imp.allPackages { - for _, f := range info.Files { - if !tokenFileContainsPos(imp.Fset.File(f.Package), start) { - continue - } - if path, exact := PathEnclosingInterval(f, start, end); path != nil { - return info, path, exact - } - } - } - return nil, nil, false -} diff --git a/astutil/enclosing_test.go b/astutil/enclosing_test.go new file mode 100644 index 00000000..18b911d9 --- /dev/null +++ b/astutil/enclosing_test.go @@ -0,0 +1,195 @@ +// Copyright 2013 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 astutil_test + +// This file defines tests of PathEnclosingInterval. + +// TODO(adonovan): exhaustive tests that run over the whole input +// tree, not just handcrafted examples. + +import ( + "bytes" + "fmt" + "go/ast" + "go/parser" + "go/token" + "strings" + "testing" + + "code.google.com/p/go.tools/astutil" +) + +// pathToString returns a string containing the concrete types of the +// nodes in path. +func pathToString(path []ast.Node) string { + var buf bytes.Buffer + fmt.Fprint(&buf, "[") + for i, n := range path { + if i > 0 { + fmt.Fprint(&buf, " ") + } + fmt.Fprint(&buf, strings.TrimPrefix(fmt.Sprintf("%T", n), "*ast.")) + } + fmt.Fprint(&buf, "]") + return buf.String() +} + +// findInterval parses input and returns the [start, end) positions of +// the first occurrence of substr in input. f==nil indicates failure; +// an error has already been reported in that case. +// +func findInterval(t *testing.T, fset *token.FileSet, input, substr string) (f *ast.File, start, end token.Pos) { + f, err := parser.ParseFile(fset, "", input, 0) + if err != nil { + t.Errorf("parse error: %s", err) + return + } + + i := strings.Index(input, substr) + if i < 0 { + t.Errorf("%q is not a substring of input", substr) + f = nil + return + } + + filePos := fset.File(f.Package) + return f, filePos.Pos(i), filePos.Pos(i + len(substr)) +} + +// Common input for following tests. +const input = ` +// Hello. +package main +import "fmt" +func f() {} +func main() { + z := (x + y) // add them + f() // NB: ExprStmt and its CallExpr have same Pos/End +} +` + +func TestPathEnclosingInterval_Exact(t *testing.T) { + // For the exact tests, we check that a substring is mapped to + // the canonical string for the node it denotes. + tests := []struct { + substr string // first occurrence of this string indicates interval + node string // complete text of expected containing node + }{ + {"package", + input[11 : len(input)-1]}, + {"\npack", + input[11 : len(input)-1]}, + {"main", + "main"}, + {"import", + "import \"fmt\""}, + {"\"fmt\"", + "\"fmt\""}, + {"\nfunc f() {}\n", + "func f() {}"}, + {"x ", + "x"}, + {" y", + "y"}, + {"z", + "z"}, + {" + ", + "x + y"}, + {" :=", + "z := (x + y)"}, + {"x + y", + "x + y"}, + {"(x + y)", + "(x + y)"}, + {" (x + y) ", + "(x + y)"}, + {" (x + y) // add", + "(x + y)"}, + {"func", + "func f() {}"}, + {"func f() {}", + "func f() {}"}, + {"\nfun", + "func f() {}"}, + {" f", + "f"}, + } + for _, test := range tests { + f, start, end := findInterval(t, new(token.FileSet), input, test.substr) + if f == nil { + continue + } + + path, exact := astutil.PathEnclosingInterval(f, start, end) + if !exact { + t.Errorf("PathEnclosingInterval(%q) not exact", test.substr) + continue + } + + if len(path) == 0 { + if test.node != "" { + t.Errorf("PathEnclosingInterval(%q).path: got [], want %q", + test.substr, test.node) + } + continue + } + + if got := input[path[0].Pos():path[0].End()]; got != test.node { + t.Errorf("PathEnclosingInterval(%q): got %q, want %q (path was %s)", + test.substr, got, test.node, pathToString(path)) + continue + } + } +} + +func TestPathEnclosingInterval_Paths(t *testing.T) { + // For these tests, we check only the path of the enclosing + // node, but not its complete text because it's often quite + // large when !exact. + tests := []struct { + substr string // first occurrence of this string indicates interval + path string // the pathToString(),exact of the expected path + }{ + {"// add", + "[BlockStmt FuncDecl File],false"}, + {"(x + y", + "[ParenExpr AssignStmt BlockStmt FuncDecl File],false"}, + {"x +", + "[BinaryExpr ParenExpr AssignStmt BlockStmt FuncDecl File],false"}, + {"z := (x", + "[AssignStmt BlockStmt FuncDecl File],false"}, + {"func f", + "[FuncDecl File],false"}, + {"func f()", + "[FuncDecl File],false"}, + {" f()", + "[FuncDecl File],false"}, + {"() {}", + "[FuncDecl File],false"}, + {"// Hello", + "[File],false"}, + {" f", + "[Ident FuncDecl File],true"}, + {"func ", + "[FuncDecl File],true"}, + {"mai", + "[Ident File],true"}, + {"f() // NB", + "[CallExpr ExprStmt BlockStmt FuncDecl File],true"}, + } + for _, test := range tests { + f, start, end := findInterval(t, new(token.FileSet), input, test.substr) + if f == nil { + continue + } + + path, exact := astutil.PathEnclosingInterval(f, start, end) + if got := fmt.Sprintf("%s,%v", pathToString(path), exact); got != test.path { + t.Errorf("PathEnclosingInterval(%q): got %q, want %q", + test.substr, got, test.path) + continue + } + } +} diff --git a/importer/importer.go b/importer/importer.go index c0ec0ecc..38fb0c30 100644 --- a/importer/importer.go +++ b/importer/importer.go @@ -52,6 +52,7 @@ import ( "strings" "sync" + "code.google.com/p/go.tools/astutil" "code.google.com/p/go.tools/go/exact" "code.google.com/p/go.tools/go/gcimporter" "code.google.com/p/go.tools/go/types" @@ -531,3 +532,31 @@ func initialPackageFromFiles(fset *token.FileSet, arg string) (*initialPkg, erro files: files, }, nil } + +// PathEnclosingInterval returns the PackageInfo and ast.Node that +// contain source interval [start, end), and all the node's ancestors +// up to the AST root. It searches all ast.Files of all packages in the +// Importer imp. exact is defined as for astutil.PathEnclosingInterval. +// +// The result is (nil, nil, false) if not found. +// +func (imp *Importer) PathEnclosingInterval(start, end token.Pos) (pkg *PackageInfo, path []ast.Node, exact bool) { + for _, info := range imp.allPackages { + for _, f := range info.Files { + if !tokenFileContainsPos(imp.Fset.File(f.Package), start) { + continue + } + if path, exact := astutil.PathEnclosingInterval(f, start, end); path != nil { + return info, path, exact + } + } + } + return nil, nil, false +} + +// TODO(adonovan): make this a method: func (*token.File) Contains(token.Pos) +func tokenFileContainsPos(f *token.File, pos token.Pos) bool { + p := int(pos) + base := f.Base() + return base <= p && p < base+f.Size() +} diff --git a/importer/source_test.go b/importer/source_test.go index 791b05bf..c761409a 100644 --- a/importer/source_test.go +++ b/importer/source_test.go @@ -6,37 +6,18 @@ package importer_test // This file defines tests of source utilities. -// TODO(adonovan): exhaustive tests that run over the whole input -// tree, not just handcrafted examples. - import ( - "bytes" - "fmt" "go/ast" "go/parser" "go/token" "strings" "testing" + "code.google.com/p/go.tools/astutil" "code.google.com/p/go.tools/importer" "code.google.com/p/go.tools/ssa" ) -// pathToString returns a string containing the concrete types of the -// nodes in path. -func pathToString(path []ast.Node) string { - var buf bytes.Buffer - fmt.Fprint(&buf, "[") - for i, n := range path { - if i > 0 { - fmt.Fprint(&buf, " ") - } - fmt.Fprint(&buf, strings.TrimPrefix(fmt.Sprintf("%T", n), "*ast.")) - } - fmt.Fprint(&buf, "]") - return buf.String() -} - // findInterval parses input and returns the [start, end) positions of // the first occurrence of substr in input. f==nil indicates failure; // an error has already been reported in that case. @@ -59,145 +40,6 @@ func findInterval(t *testing.T, fset *token.FileSet, input, substr string) (f *a return f, filePos.Pos(i), filePos.Pos(i + len(substr)) } -// Common input for following tests. -const input = ` -// Hello. -package main -import "fmt" -func f() {} -func main() { - z := (x + y) // add them - f() // NB: ExprStmt and its CallExpr have same Pos/End -} -` - -func TestPathEnclosingInterval_Exact(t *testing.T) { - // For the exact tests, we check that a substring is mapped to - // the canonical string for the node it denotes. - tests := []struct { - substr string // first occurrence of this string indicates interval - node string // complete text of expected containing node - }{ - {"package", - input[11 : len(input)-1]}, - {"\npack", - input[11 : len(input)-1]}, - {"main", - "main"}, - {"import", - "import \"fmt\""}, - {"\"fmt\"", - "\"fmt\""}, - {"\nfunc f() {}\n", - "func f() {}"}, - {"x ", - "x"}, - {" y", - "y"}, - {"z", - "z"}, - {" + ", - "x + y"}, - {" :=", - "z := (x + y)"}, - {"x + y", - "x + y"}, - {"(x + y)", - "(x + y)"}, - {" (x + y) ", - "(x + y)"}, - {" (x + y) // add", - "(x + y)"}, - {"func", - "func f() {}"}, - {"func f() {}", - "func f() {}"}, - {"\nfun", - "func f() {}"}, - {" f", - "f"}, - } - for _, test := range tests { - f, start, end := findInterval(t, new(token.FileSet), input, test.substr) - if f == nil { - continue - } - - path, exact := importer.PathEnclosingInterval(f, start, end) - if !exact { - t.Errorf("PathEnclosingInterval(%q) not exact", test.substr) - continue - } - - if len(path) == 0 { - if test.node != "" { - t.Errorf("PathEnclosingInterval(%q).path: got [], want %q", - test.substr, test.node) - } - continue - } - - if got := input[path[0].Pos():path[0].End()]; got != test.node { - t.Errorf("PathEnclosingInterval(%q): got %q, want %q (path was %s)", - test.substr, got, test.node, pathToString(path)) - continue - } - } -} - -func TestPathEnclosingInterval_Paths(t *testing.T) { - // For these tests, we check only the path of the enclosing - // node, but not its complete text because it's often quite - // large when !exact. - tests := []struct { - substr string // first occurrence of this string indicates interval - path string // the pathToString(),exact of the expected path - }{ - {"// add", - "[BlockStmt FuncDecl File],false"}, - {"(x + y", - "[ParenExpr AssignStmt BlockStmt FuncDecl File],false"}, - {"x +", - "[BinaryExpr ParenExpr AssignStmt BlockStmt FuncDecl File],false"}, - {"z := (x", - "[AssignStmt BlockStmt FuncDecl File],false"}, - {"func f", - "[FuncDecl File],false"}, - {"func f()", - "[FuncDecl File],false"}, - {" f()", - "[FuncDecl File],false"}, - {"() {}", - "[FuncDecl File],false"}, - {"// Hello", - "[File],false"}, - {" f", - "[Ident FuncDecl File],true"}, - {"func ", - "[FuncDecl File],true"}, - {"mai", - "[Ident File],true"}, - {"f() // NB", - "[CallExpr ExprStmt BlockStmt FuncDecl File],true"}, - } - for _, test := range tests { - f, start, end := findInterval(t, new(token.FileSet), input, test.substr) - if f == nil { - continue - } - - path, exact := importer.PathEnclosingInterval(f, start, end) - if got := fmt.Sprintf("%s,%v", pathToString(path), exact); got != test.path { - t.Errorf("PathEnclosingInterval(%q): got %q, want %q", - test.substr, got, test.path) - continue - } - } -} - -// -------- Tests of source.go ----------------------------------------- - -// TODO(adonovan): move this test into ssa. func TestEnclosingFunction(t *testing.T) { tests := []struct { input string // the input file @@ -245,7 +87,7 @@ func TestEnclosingFunction(t *testing.T) { if f == nil { continue } - path, exact := importer.PathEnclosingInterval(f, start, end) + path, exact := astutil.PathEnclosingInterval(f, start, end) if !exact { t.Errorf("EnclosingFunction(%q) not exact", test.substr) continue diff --git a/oracle/describe.go b/oracle/describe.go index c355a766..1c11f283 100644 --- a/oracle/describe.go +++ b/oracle/describe.go @@ -14,6 +14,7 @@ import ( "strconv" "strings" + "code.google.com/p/go.tools/astutil" "code.google.com/p/go.tools/go/exact" "code.google.com/p/go.tools/go/types" "code.google.com/p/go.tools/importer" @@ -36,7 +37,7 @@ import ( func describe(o *Oracle, qpos *QueryPos) (queryResult, error) { if false { // debugging o.fprintf(os.Stderr, qpos.path[0], "you selected: %s %s", - importer.NodeDescription(qpos.path[0]), pathToString2(qpos.path)) + astutil.NodeDescription(qpos.path[0]), pathToString2(qpos.path)) } path, action := findInterestingNode(qpos.info, qpos.path) @@ -67,12 +68,12 @@ type describeUnknownResult struct { func (r *describeUnknownResult) display(printf printfFunc) { // Nothing much to say about misc syntax. - printf(r.node, "%s", importer.NodeDescription(r.node)) + printf(r.node, "%s", astutil.NodeDescription(r.node)) } func (r *describeUnknownResult) toSerial(res *serial.Result, fset *token.FileSet) { res.Describe = &serial.Describe{ - Desc: importer.NodeDescription(r.node), + Desc: astutil.NodeDescription(r.node), Pos: fset.Position(r.node.Pos()).String(), } } @@ -496,7 +497,7 @@ func (r *describeValueResult) display(printf printfFunc) { } } } else { - desc := importer.NodeDescription(r.expr) + desc := astutil.NodeDescription(r.expr) if suffix != "" { // constant expression printf(r.expr, "%s%s", desc, suffix) @@ -582,7 +583,7 @@ func (r *describeValueResult) toSerial(res *serial.Result, fset *token.FileSet) } res.Describe = &serial.Describe{ - Desc: importer.NodeDescription(r.expr), + Desc: astutil.NodeDescription(r.expr), Pos: fset.Position(r.expr.Pos()).String(), Detail: "value", Value: &serial.DescribeValue{ @@ -901,7 +902,7 @@ func describeStmt(o *Oracle, qpos *QueryPos, path []ast.Node) (*describeStmtResu default: // Nothing much to say about statements. - description = importer.NodeDescription(n) + description = astutil.NodeDescription(n) } return &describeStmtResult{o.prog.Fset, path[0], description}, nil } diff --git a/oracle/oracle.go b/oracle/oracle.go index aa85fe34..459435d1 100644 --- a/oracle/oracle.go +++ b/oracle/oracle.go @@ -30,6 +30,7 @@ import ( "strings" "time" + "code.google.com/p/go.tools/astutil" "code.google.com/p/go.tools/go/types" "code.google.com/p/go.tools/importer" "code.google.com/p/go.tools/oracle/serial" @@ -339,7 +340,7 @@ func ParseQueryPos(imp *importer.Importer, pos string, needExact bool) (*QueryPo return nil, fmt.Errorf("no syntax here") } if needExact && !exact { - return nil, fmt.Errorf("ambiguous selection within %s", importer.NodeDescription(path[0])) + return nil, fmt.Errorf("ambiguous selection within %s", astutil.NodeDescription(path[0])) } return &QueryPos{start, end, info, path}, nil } diff --git a/ssa/dom.go b/ssa/dom.go index 138b8d6b..33d1cfae 100644 --- a/ssa/dom.go +++ b/ssa/dom.go @@ -113,7 +113,7 @@ func (lt *ltState) link(v, w *BasicBlock) { // func buildDomTree(f *Function) { // The step numbers refer to the original LT paper; the - // reodering is due to Georgiadis. + // reordering is due to Georgiadis. // Clear any previous domInfo. for _, b := range f.Blocks { diff --git a/ssa/source_test.go b/ssa/source_test.go index 268463ae..0e7118f3 100644 --- a/ssa/source_test.go +++ b/ssa/source_test.go @@ -16,6 +16,7 @@ import ( "strings" "testing" + "code.google.com/p/go.tools/astutil" "code.google.com/p/go.tools/go/exact" "code.google.com/p/go.tools/go/types" "code.google.com/p/go.tools/importer" @@ -88,7 +89,7 @@ func TestObjValueLookup(t *testing.T) { continue } if obj, ok := mainInfo.ObjectOf(id).(*types.Var); ok { - ref, _ := importer.PathEnclosingInterval(f, id.Pos(), id.Pos()) + ref, _ := astutil.PathEnclosingInterval(f, id.Pos(), id.Pos()) pos := imp.Fset.Position(id.Pos()) exp := expectations[fmt.Sprintf("%s:%d", id.Name, pos.Line)] if exp == "" { @@ -240,7 +241,7 @@ func TestValueForExpr(t *testing.T) { e = target.X } - path, _ := importer.PathEnclosingInterval(f, pos, pos) + path, _ := astutil.PathEnclosingInterval(f, pos, pos) if path == nil { t.Errorf("%s: can't find AST path from root to comment: %s", position, text) continue