go.tools/importer: move PathEnclosingInterval to package astutil.

R=crawshaw
CC=golang-dev
https://golang.org/cl/38650044
This commit is contained in:
Alan Donovan 2013-12-09 09:36:29 -05:00
parent 55fb2e8e28
commit fb3d862cae
8 changed files with 241 additions and 201 deletions

View File

@ -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
}

195
astutil/enclosing_test.go Normal file
View File

@ -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>", 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
}
}
}

View File

@ -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()
}

View File

@ -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

View File

@ -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
}

View File

@ -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
}

View File

@ -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 {

View File

@ -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