diff --git a/ssa/source_ast.go b/importer/source.go
similarity index 94%
rename from ssa/source_ast.go
rename to importer/source.go
index 0d946f1e..326d5d25 100644
--- a/ssa/source_ast.go
+++ b/importer/source.go
@@ -1,11 +1,7 @@
-package ssa
+package importer
// This file defines utilities for working with source positions.
-// It has no dependencies on ssa or go/types.
-// TODO(adonovan): move it somewhere more general,
-// e.g. go.tools/importer?
-
import (
"fmt"
"go/ast"
@@ -623,3 +619,32 @@ 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.Packages {
+ 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/importer/source_test.go b/importer/source_test.go
new file mode 100644
index 00000000..8493425d
--- /dev/null
+++ b/importer/source_test.go
@@ -0,0 +1,278 @@
+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/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.
+//
+func findInterval(t *testing.T, fset *token.FileSet, input, substr string) (f *ast.File, start, end token.Pos) {
+ f, err := parser.ParseFile(fset, "", input, parser.DeclarationErrors)
+ 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 := 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 -----------------------------------------
+
+func TestEnclosingFunction(t *testing.T) {
+ tests := []struct {
+ input string // the input file
+ substr string // first occurrence of this string denotes interval
+ fn string // name of expected containing function
+ }{
+ // We use distinctive numbers as syntactic landmarks.
+
+ // Ordinary function:
+ {`package main
+ func f() { println(1003) }`,
+ "100", "main.f"},
+ // Methods:
+ {`package main
+ type T int
+ func (t T) f() { println(200) }`,
+ "200", "(main.T).f"},
+ // Function literal:
+ {`package main
+ func f() { println(func() { print(300) }) }`,
+ "300", "func@2.24"},
+ // Doubly nested
+ {`package main
+ func f() { println(func() { print(func() { print(350) })})}`,
+ "350", "func@2.39"},
+ // Implicit init for package-level var initializer.
+ {"package main; var a = 400", "400", "main.init"},
+ // No code for constants:
+ {"package main; const a = 500", "500", "(none)"},
+ // Explicit init()
+ {"package main; func init() { println(600) }", "600", "main.init"},
+ // Multiple explicit init functions:
+ {`package main
+ func init() { println("foo") }
+ func init() { println(800) }`,
+ "800", "main.init"},
+ // init() containing FuncLit.
+ {`package main
+ func init() { println(func(){print(900)}) }`,
+ "900", "func@2.27"},
+ }
+ for _, test := range tests {
+ imp := importer.New(new(importer.Context)) // (NB: no Loader)
+ f, start, end := findInterval(t, imp.Fset, test.input, test.substr)
+ if f == nil {
+ continue
+ }
+ path, exact := importer.PathEnclosingInterval(f, start, end)
+ if !exact {
+ t.Errorf("EnclosingFunction(%q) not exact", test.substr)
+ continue
+ }
+ info, err := imp.CreateSourcePackage("main", []*ast.File{f})
+ if err != nil {
+ t.Error(err.Error())
+ continue
+ }
+
+ prog := ssa.NewProgram(imp.Fset, 0)
+ prog.CreatePackages(imp)
+ pkg := prog.Package(info.Pkg)
+ pkg.Build()
+
+ name := "(none)"
+ fn := ssa.EnclosingFunction(pkg, path)
+ if fn != nil {
+ name = fn.String()
+ }
+
+ if name != test.fn {
+ t.Errorf("EnclosingFunction(%q in %q) got %s, want %s",
+ test.substr, test.input, name, test.fn)
+ continue
+ }
+
+ // While we're here: test HasEnclosingFunction.
+ if has := ssa.HasEnclosingFunction(pkg, path); has != (fn != nil) {
+ t.Errorf("HasEnclosingFunction(%q in %q) got %v, want %v",
+ test.substr, test.input, has, fn != nil)
+ continue
+ }
+ }
+}
diff --git a/ssa/promote.go b/ssa/promote.go
index df382edf..f7b563a5 100644
--- a/ssa/promote.go
+++ b/ssa/promote.go
@@ -9,7 +9,7 @@ package ssa
// - bound method wrappers, for uncalled obj.Method closures.
// - indirection wrappers, for calls to T-methods on a *T receiver.
-// TODO(adonovan): rename to methods.go.
+// TODO(adonovan): rename to wrappers.go when promotion logic has evaporated.
import (
"code.google.com/p/go.tools/go/types"
@@ -127,6 +127,8 @@ func (p *Program) MethodSet(typ types.Type) MethodSet {
// buildMethodSet computes the concrete method set for type typ.
// It is the implementation of Program.MethodSet.
//
+// TODO(adonovan): use go/types.MethodSet(typ) when it's ready.
+//
// EXCLUSIVE_LOCKS_REQUIRED(meth.Prog.methodsMu)
//
func buildMethodSet(prog *Program, typ types.Type) MethodSet {
diff --git a/ssa/source.go b/ssa/source.go
index d97ecd58..ddbe378c 100644
--- a/ssa/source.go
+++ b/ssa/source.go
@@ -7,46 +7,8 @@ import (
"go/token"
"code.google.com/p/go.tools/go/types"
- "code.google.com/p/go.tools/importer"
)
-// 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 Package and ast.Node that
-// contain source interval [start, end), and all the node's ancestors
-// up to the AST root. It searches all files of all packages in the
-// program prog. exact is defined as for standalone
-// PathEnclosingInterval.
-//
-// imp provides ASTs for the program's packages.
-//
-// pkg may be nil if no SSA package has yet been created for the found
-// package. Call prog.CreatePackages(imp) to avoid this.
-//
-// The result is (nil, nil, false) if not found.
-//
-func (prog *Program) PathEnclosingInterval(imp *importer.Importer, start, end token.Pos) (pkg *Package, path []ast.Node, exact bool) {
- for importPath, info := range imp.Packages {
- for _, f := range info.Files {
- if !tokenFileContainsPos(imp.Fset.File(f.Package), start) {
- continue
- }
- if path, exact := PathEnclosingInterval(f, start, end); path != nil {
- // TODO(adonovan): return info in lieu
- // of pkg; remove Prog as a parameter;
- // move to importer.
- return prog.PackagesByPath[importPath], path, exact
- }
- }
- }
- return nil, nil, false
-}
-
// EnclosingFunction returns the function that contains the syntax
// node denoted by path.
//
diff --git a/ssa/source_test.go b/ssa/source_test.go
index 552bad31..8ed82b20 100644
--- a/ssa/source_test.go
+++ b/ssa/source_test.go
@@ -1,18 +1,13 @@
package ssa_test
-// This file defines tests of the source and source_ast utilities.
-
-// TODO(adonovan): exhaustive tests that run over the whole input
-// tree, not just handcrafted examples.
+// This file defines tests of source-level debugging utilities.
import (
- "bytes"
"fmt"
"go/ast"
"go/parser"
"go/token"
"regexp"
- "strings"
"testing"
"code.google.com/p/go.tools/go/exact"
@@ -21,267 +16,6 @@ import (
"code.google.com/p/go.tools/ssa"
)
-// -------- Tests of source_ast.go -------------------------------------
-
-// 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, parser.DeclarationErrors)
- 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 := ssa.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 := ssa.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 -----------------------------------------
-
-func TestEnclosingFunction(t *testing.T) {
- tests := []struct {
- input string // the input file
- substr string // first occurrence of this string denotes interval
- fn string // name of expected containing function
- }{
- // We use distinctive numbers as syntactic landmarks.
-
- // Ordinary function:
- {`package main
- func f() { println(1003) }`,
- "100", "main.f"},
- // Methods:
- {`package main
- type T int
- func (t T) f() { println(200) }`,
- "200", "(main.T).f"},
- // Function literal:
- {`package main
- func f() { println(func() { print(300) }) }`,
- "300", "func@2.24"},
- // Doubly nested
- {`package main
- func f() { println(func() { print(func() { print(350) })})}`,
- "350", "func@2.39"},
- // Implicit init for package-level var initializer.
- {"package main; var a = 400", "400", "main.init"},
- // No code for constants:
- {"package main; const a = 500", "500", "(none)"},
- // Explicit init()
- {"package main; func init() { println(600) }", "600", "main.init"},
- // Multiple explicit init functions:
- {`package main
- func init() { println("foo") }
- func init() { println(800) }`,
- "800", "main.init"},
- // init() containing FuncLit.
- {`package main
- func init() { println(func(){print(900)}) }`,
- "900", "func@2.27"},
- }
- for _, test := range tests {
- imp := importer.New(new(importer.Context)) // (NB: no Loader)
- f, start, end := findInterval(t, imp.Fset, test.input, test.substr)
- if f == nil {
- continue
- }
- path, exact := ssa.PathEnclosingInterval(f, start, end)
- if !exact {
- t.Errorf("EnclosingFunction(%q) not exact", test.substr)
- continue
- }
- info, err := imp.CreateSourcePackage("main", []*ast.File{f})
- if err != nil {
- t.Error(err.Error())
- continue
- }
-
- prog := ssa.NewProgram(imp.Fset, 0)
- prog.CreatePackages(imp)
- pkg := prog.Package(info.Pkg)
- pkg.Build()
-
- name := "(none)"
- fn := ssa.EnclosingFunction(pkg, path)
- if fn != nil {
- name = fn.String()
- }
-
- if name != test.fn {
- t.Errorf("EnclosingFunction(%q in %q) got %s, want %s",
- test.substr, test.input, name, test.fn)
- continue
- }
-
- // While we're here: test HasEnclosingFunction.
- if has := ssa.HasEnclosingFunction(pkg, path); has != (fn != nil) {
- t.Errorf("HasEnclosingFunction(%q in %q) got %v, want %v",
- test.substr, test.input, has, fn != nil)
- continue
- }
- }
-}
-
func TestObjValueLookup(t *testing.T) {
imp := importer.New(new(importer.Context)) // (uses GCImporter)
f, err := parser.ParseFile(imp.Fset, "testdata/objlookup.go", nil, parser.DeclarationErrors|parser.ParseComments)
@@ -348,7 +82,7 @@ func TestObjValueLookup(t *testing.T) {
// The result varies based on the specific Ident.
for _, id := range ids {
if obj, ok := info.ObjectOf(id).(*types.Var); ok {
- ref, _ := ssa.PathEnclosingInterval(f, id.Pos(), id.Pos())
+ ref, _ := importer.PathEnclosingInterval(f, id.Pos(), id.Pos())
pos := imp.Fset.Position(id.Pos())
exp := expectations[fmt.Sprintf("%s:%d", id.Name, pos.Line)]
if exp == "" {