go/types: add an API test of the Scope type
Also: make (*Scope).Innermost work for Package scopes. Change-Id: I9836676e94f95df897101606bed6f29ba46e0f9d Reviewed-on: https://go-review.googlesource.com/11691 Reviewed-by: Robert Griesemer <gri@golang.org>
This commit is contained in:
parent
27bc91e0ba
commit
749901c676
|
@ -10,6 +10,8 @@ import (
|
||||||
"go/ast"
|
"go/ast"
|
||||||
"go/parser"
|
"go/parser"
|
||||||
"go/token"
|
"go/token"
|
||||||
|
"reflect"
|
||||||
|
"regexp"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
@ -863,7 +865,7 @@ func TestIssue8518(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const libSrc = `
|
const libSrc = `
|
||||||
package a
|
package a
|
||||||
import "missing"
|
import "missing"
|
||||||
const C1 = foo
|
const C1 = foo
|
||||||
const C2 = missing.C
|
const C2 = missing.C
|
||||||
|
@ -953,3 +955,105 @@ func sameSlice(a, b []int) bool {
|
||||||
}
|
}
|
||||||
return true
|
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()
|
||||||
|
conf := Config{
|
||||||
|
Packages: make(map[string]*Package),
|
||||||
|
Import: func(imports map[string]*Package, path string) (*Package, error) {
|
||||||
|
return imports[path], nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
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) {
|
||||||
|
conf.Packages[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 := conf.Packages["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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -126,9 +126,20 @@ func (s *Scope) Contains(pos token.Pos) bool {
|
||||||
|
|
||||||
// Innermost returns the innermost (child) scope containing
|
// Innermost returns the innermost (child) scope containing
|
||||||
// pos. If pos is not within any scope, the result is nil.
|
// 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
|
// The result is guaranteed to be valid only if the type-checked
|
||||||
// AST has complete position information.
|
// AST has complete position information.
|
||||||
func (s *Scope) Innermost(pos token.Pos) *Scope {
|
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) {
|
if s.Contains(pos) {
|
||||||
for _, s := range s.children {
|
for _, s := range s.children {
|
||||||
if s.Contains(pos) {
|
if s.Contains(pos) {
|
||||||
|
|
Loading…
Reference in New Issue