aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlan Donovan <adonovan@google.com>2015-06-29 16:13:09 -0400
committerAlan Donovan <adonovan@google.com>2015-06-29 20:16:18 +0000
commitc77809e9032f83fdf6ddfda5b5a19f6771962488 (patch)
treee1c39d0c22fd786418717c21d7bdbb71cdc5abb3
parent34846aef78e2e27da8eaadff2d9ea78cd99d491d (diff)
downloadgo-c77809e9032f83fdf6ddfda5b5a19f6771962488.tar.gz
go-c77809e9032f83fdf6ddfda5b5a19f6771962488.zip
go/types: go/types: add an API test of the Scope type
Also: make (*Scope).Innermost work for Package scopes. This change is identical to http://go-review.googlesource.com/#/c/11691/, except for minor changes required by the use of testImporter. Change-Id: Id07e66f78987f7242c2e642dfd6ee613676e10e5 Reviewed-on: https://go-review.googlesource.com/11714 Reviewed-by: Robert Griesemer <gri@golang.org>
-rw-r--r--src/go/types/api_test.go102
-rw-r--r--src/go/types/scope.go11
2 files changed, 112 insertions, 1 deletions
diff --git a/src/go/types/api_test.go b/src/go/types/api_test.go
index bdf47e77f1..eeda0d847c 100644
--- a/src/go/types/api_test.go
+++ b/src/go/types/api_test.go
@@ -12,6 +12,8 @@ import (
"go/parser"
"go/token"
"internal/testenv"
+ "reflect"
+ "regexp"
"strings"
"testing"
@@ -852,7 +854,7 @@ func TestIssue8518(t *testing.T) {
}
const libSrc = `
-package a
+package a
import "missing"
const C1 = foo
const C2 = missing.C
@@ -942,3 +944,101 @@ func sameSlice(a, b []int) bool {
}
return true
}
+
+// TestScopeLookupParent ensures that (*Scope).LookupParent returns
+// the correct result at various positions with the source.
+func TestScopeLookupParent(t *testing.T) {
+ fset := token.NewFileSet()
+ imports := make(testImporter)
+ conf := Config{Importer: imports}
+ mustParse := func(src string) *ast.File {
+ f, err := parser.ParseFile(fset, "dummy.go", src, parser.ParseComments)
+ if err != nil {
+ t.Fatal(err)
+ }
+ return f
+ }
+ var info Info
+ makePkg := func(path string, files ...*ast.File) {
+ imports[path], _ = conf.Check(path, fset, files, &info)
+ }
+
+ makePkg("lib", mustParse("package lib; var X int"))
+ // Each /*name=kind:line*/ comment makes the test look up the
+ // name at that point and checks that it resolves to a decl of
+ // the specified kind and line number. "undef" means undefined.
+ mainSrc := `
+package main
+import "lib"
+var Y = lib.X
+func f() {
+ print(Y) /*Y=var:4*/
+ z /*z=undef*/ := /*z=undef*/ 1 /*z=var:7*/
+ print(z)
+ /*f=func:5*/ /*lib=pkgname:3*/
+ type /*T=undef*/ T /*T=typename:10*/ *T
+}
+`
+ info.Uses = make(map[*ast.Ident]Object)
+ f := mustParse(mainSrc)
+ makePkg("main", f)
+ mainScope := imports["main"].Scope()
+ rx := regexp.MustCompile(`^/\*(\w*)=([\w:]*)\*/$`)
+ for _, group := range f.Comments {
+ for _, comment := range group.List {
+ // Parse the assertion in the comment.
+ m := rx.FindStringSubmatch(comment.Text)
+ if m == nil {
+ t.Errorf("%s: bad comment: %s",
+ fset.Position(comment.Pos()), comment.Text)
+ continue
+ }
+ name, want := m[1], m[2]
+
+ // Look up the name in the innermost enclosing scope.
+ inner := mainScope.Innermost(comment.Pos())
+ if inner == nil {
+ t.Errorf("%s: at %s: can't find innermost scope",
+ fset.Position(comment.Pos()), comment.Text)
+ continue
+ }
+ got := "undef"
+ if _, obj := inner.LookupParent(name, comment.Pos()); obj != nil {
+ kind := strings.ToLower(strings.TrimPrefix(reflect.TypeOf(obj).String(), "*types."))
+ got = fmt.Sprintf("%s:%d", kind, fset.Position(obj.Pos()).Line)
+ }
+ if got != want {
+ t.Errorf("%s: at %s: %s resolved to %s, want %s",
+ fset.Position(comment.Pos()), comment.Text, name, got, want)
+ }
+ }
+ }
+
+ // Check that for each referring identifier,
+ // a lookup of its name on the innermost
+ // enclosing scope returns the correct object.
+
+ for id, wantObj := range info.Uses {
+ inner := mainScope.Innermost(id.Pos())
+ if inner == nil {
+ t.Errorf("%s: can't find innermost scope enclosing %q",
+ fset.Position(id.Pos()), id.Name)
+ continue
+ }
+
+ // Exclude selectors and qualified identifiers---lexical
+ // refs only. (Ideally, we'd see if the AST parent is a
+ // SelectorExpr, but that requires PathEnclosingInterval
+ // from golang.org/x/tools/go/ast/astutil.)
+ if id.Name == "X" {
+ continue
+ }
+
+ _, gotObj := inner.LookupParent(id.Name, id.Pos())
+ if gotObj != wantObj {
+ t.Errorf("%s: got %v, want %v",
+ fset.Position(id.Pos()), gotObj, wantObj)
+ continue
+ }
+ }
+}
diff --git a/src/go/types/scope.go b/src/go/types/scope.go
index dae5deff8a..3502840225 100644
--- a/src/go/types/scope.go
+++ b/src/go/types/scope.go
@@ -126,9 +126,20 @@ func (s *Scope) Contains(pos token.Pos) bool {
// Innermost returns the innermost (child) scope containing
// pos. If pos is not within any scope, the result is nil.
+// The result is also nil for the Universe scope.
// The result is guaranteed to be valid only if the type-checked
// AST has complete position information.
func (s *Scope) Innermost(pos token.Pos) *Scope {
+ // Package scopes do not have extents since they may be
+ // discontiguous, so iterate over the package's files.
+ if s.parent == Universe {
+ for _, s := range s.children {
+ if inner := s.Innermost(pos); inner != nil {
+ return inner
+ }
+ }
+ }
+
if s.Contains(pos) {
for _, s := range s.children {
if s.Contains(pos) {