Skip to content

Commit 749901c

Browse files
committed
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 <[email protected]>
1 parent 27bc91e commit 749901c

File tree

2 files changed

+116
-1
lines changed

2 files changed

+116
-1
lines changed

go/types/api_test.go

Lines changed: 105 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import (
1010
"go/ast"
1111
"go/parser"
1212
"go/token"
13+
"reflect"
14+
"regexp"
1315
"runtime"
1416
"strings"
1517
"testing"
@@ -863,7 +865,7 @@ func TestIssue8518(t *testing.T) {
863865
}
864866

865867
const libSrc = `
866-
package a
868+
package a
867869
import "missing"
868870
const C1 = foo
869871
const C2 = missing.C
@@ -953,3 +955,105 @@ func sameSlice(a, b []int) bool {
953955
}
954956
return true
955957
}
958+
959+
// TestScopeLookupParent ensures that (*Scope).LookupParent returns
960+
// the correct result at various positions with the source.
961+
func TestScopeLookupParent(t *testing.T) {
962+
fset := token.NewFileSet()
963+
conf := Config{
964+
Packages: make(map[string]*Package),
965+
Import: func(imports map[string]*Package, path string) (*Package, error) {
966+
return imports[path], nil
967+
},
968+
}
969+
mustParse := func(src string) *ast.File {
970+
f, err := parser.ParseFile(fset, "dummy.go", src, parser.ParseComments)
971+
if err != nil {
972+
t.Fatal(err)
973+
}
974+
return f
975+
}
976+
var info Info
977+
makePkg := func(path string, files ...*ast.File) {
978+
conf.Packages[path], _ = conf.Check(path, fset, files, &info)
979+
}
980+
981+
makePkg("lib", mustParse("package lib; var X int"))
982+
// Each /*name=kind:line*/ comment makes the test look up the
983+
// name at that point and checks that it resolves to a decl of
984+
// the specified kind and line number. "undef" means undefined.
985+
mainSrc := `
986+
package main
987+
import "lib"
988+
var Y = lib.X
989+
func f() {
990+
print(Y) /*Y=var:4*/
991+
z /*z=undef*/ := /*z=undef*/ 1 /*z=var:7*/
992+
print(z)
993+
/*f=func:5*/ /*lib=pkgname:3*/
994+
type /*T=undef*/ T /*T=typename:10*/ *T
995+
}
996+
`
997+
info.Uses = make(map[*ast.Ident]Object)
998+
f := mustParse(mainSrc)
999+
makePkg("main", f)
1000+
mainScope := conf.Packages["main"].Scope()
1001+
rx := regexp.MustCompile(`^/\*(\w*)=([\w:]*)\*/$`)
1002+
for _, group := range f.Comments {
1003+
for _, comment := range group.List {
1004+
// Parse the assertion in the comment.
1005+
m := rx.FindStringSubmatch(comment.Text)
1006+
if m == nil {
1007+
t.Errorf("%s: bad comment: %s",
1008+
fset.Position(comment.Pos()), comment.Text)
1009+
continue
1010+
}
1011+
name, want := m[1], m[2]
1012+
1013+
// Look up the name in the innermost enclosing scope.
1014+
inner := mainScope.Innermost(comment.Pos())
1015+
if inner == nil {
1016+
t.Errorf("%s: at %s: can't find innermost scope",
1017+
fset.Position(comment.Pos()), comment.Text)
1018+
continue
1019+
}
1020+
got := "undef"
1021+
if _, obj := inner.LookupParent(name, comment.Pos()); obj != nil {
1022+
kind := strings.ToLower(strings.TrimPrefix(reflect.TypeOf(obj).String(), "*types."))
1023+
got = fmt.Sprintf("%s:%d", kind, fset.Position(obj.Pos()).Line)
1024+
}
1025+
if got != want {
1026+
t.Errorf("%s: at %s: %s resolved to %s, want %s",
1027+
fset.Position(comment.Pos()), comment.Text, name, got, want)
1028+
}
1029+
}
1030+
}
1031+
1032+
// Check that for each referring identifier,
1033+
// a lookup of its name on the innermost
1034+
// enclosing scope returns the correct object.
1035+
1036+
for id, wantObj := range info.Uses {
1037+
inner := mainScope.Innermost(id.Pos())
1038+
if inner == nil {
1039+
t.Errorf("%s: can't find innermost scope enclosing %q",
1040+
fset.Position(id.Pos()), id.Name)
1041+
continue
1042+
}
1043+
1044+
// Exclude selectors and qualified identifiers---lexical
1045+
// refs only. (Ideally, we'd see if the AST parent is a
1046+
// SelectorExpr, but that requires PathEnclosingInterval
1047+
// from golang.org/x/tools/go/ast/astutil.)
1048+
if id.Name == "X" {
1049+
continue
1050+
}
1051+
1052+
_, gotObj := inner.LookupParent(id.Name, id.Pos())
1053+
if gotObj != wantObj {
1054+
t.Errorf("%s: got %v, want %v",
1055+
fset.Position(id.Pos()), gotObj, wantObj)
1056+
continue
1057+
}
1058+
}
1059+
}

go/types/scope.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,9 +126,20 @@ func (s *Scope) Contains(pos token.Pos) bool {
126126

127127
// Innermost returns the innermost (child) scope containing
128128
// pos. If pos is not within any scope, the result is nil.
129+
// The result is also nil for the Universe scope.
129130
// The result is guaranteed to be valid only if the type-checked
130131
// AST has complete position information.
131132
func (s *Scope) Innermost(pos token.Pos) *Scope {
133+
// Package scopes do not have extents since they may be
134+
// discontiguous, so iterate over the package's files.
135+
if s.parent == Universe {
136+
for _, s := range s.children {
137+
if inner := s.Innermost(pos); inner != nil {
138+
return inner
139+
}
140+
}
141+
}
142+
132143
if s.Contains(pos) {
133144
for _, s := range s.children {
134145
if s.Contains(pos) {

0 commit comments

Comments
 (0)