From: Robert Griesemer Date: Fri, 27 Sep 2024 17:54:26 +0000 (-0700) Subject: go/types, types2: move go/types-only Scope methods into scopes2.go X-Git-Tag: go1.24rc1~807 X-Git-Url: http://www.git.cypherpunks.su/?a=commitdiff_plain;h=6ad3933e285b036137a339f598f00a21578fcbfb;p=gostls13.git go/types, types2: move go/types-only Scope methods into scopes2.go Remove them them from types2. Updates #69673. Change-Id: I7843f6da1edf3a19f85c61706104d173e04088d3 Reviewed-on: https://go-review.googlesource.com/c/go/+/616261 Reviewed-by: Alan Donovan LUCI-TryBot-Result: Go LUCI Auto-Submit: Robert Griesemer Reviewed-by: Robert Griesemer --- diff --git a/src/cmd/compile/internal/types2/api_test.go b/src/cmd/compile/internal/types2/api_test.go index e742023135..4024a3f7c8 100644 --- a/src/cmd/compile/internal/types2/api_test.go +++ b/src/cmd/compile/internal/types2/api_test.go @@ -10,8 +10,6 @@ import ( "fmt" "internal/goversion" "internal/testenv" - "reflect" - "regexp" "sort" "strings" "sync" @@ -1993,162 +1991,6 @@ func sameSlice(a, b []int) bool { return true } -// TestScopeLookupParent ensures that (*Scope).LookupParent returns -// the correct result at various positions within the source. -func TestScopeLookupParent(t *testing.T) { - imports := make(testImporter) - conf := Config{ - Importer: imports, - EnableAlias: true, // must match default Universe.Lookup behavior - } - var info Info - makePkg := func(path, src string) { - var err error - imports[path], err = conf.Check(path, []*syntax.File{mustParse(src)}, &info) - if err != nil { - t.Fatal(err) - } - } - - makePkg("lib", "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. - // Note that type switch case clauses with an empty body (but for - // comments) need the ";" to ensure that the recorded scope extends - // past the comments. - mainSrc := ` -/*lib=pkgname:5*/ /*X=var:1*/ /*Pi=const:8*/ /*T=typename:9*/ /*Y=var:10*/ /*F=func:12*/ -package main - -import "lib" -import . "lib" - -const Pi = 3.1415 -type T struct{} -var Y, _ = lib.X, X - -func F[T *U, U any](param1, param2 int) /*param1=undef*/ (res1 /*res1=undef*/, res2 int) /*param1=var:12*/ /*res1=var:12*/ /*U=typename:12*/ { - const pi, e = 3.1415, /*pi=undef*/ 2.71828 /*pi=const:13*/ /*e=const:13*/ - type /*t=undef*/ t /*t=typename:14*/ *t - print(Y) /*Y=var:10*/ - x, Y := Y, /*x=undef*/ /*Y=var:10*/ Pi /*x=var:16*/ /*Y=var:16*/ ; _ = x; _ = Y - var F = /*F=func:12*/ F[*int, int] /*F=var:17*/ ; _ = F - - var a []int - for i, x := range a /*i=undef*/ /*x=var:16*/ { _ = i; _ = x } - - var i interface{} - switch y := i.(type) { /*y=undef*/ - case /*y=undef*/ int /*y=undef*/ : /*y=var:23*/ ; - case float32, /*y=undef*/ float64 /*y=undef*/ : /*y=var:23*/ ; - default /*y=undef*/ : /*y=var:23*/ - println(y) - } - /*y=undef*/ - - switch int := i.(type) { - case /*int=typename:0*/ int /*int=typename:0*/ : /*int=var:31*/ - println(int) - default /*int=typename:0*/ : /*int=var:31*/ ; - } - - _ = param1 - _ = res1 - return -} -/*main=undef*/ -` - - info.Uses = make(map[*syntax.Name]Object) - makePkg("main", mainSrc) - mainScope := imports["main"].Scope() - - rx := regexp.MustCompile(`^/\*(\w*)=([\w:]*)\*/$`) - - base := syntax.NewFileBase("main") - syntax.CommentsDo(strings.NewReader(mainSrc), func(line, col uint, text string) { - pos := syntax.MakePos(base, line, col) - - // Syntax errors are not comments. - if text[0] != '/' { - t.Errorf("%s: %s", pos, text) - return - } - - // Parse the assertion in the comment. - m := rx.FindStringSubmatch(text) - if m == nil { - t.Errorf("%s: bad comment: %s", pos, text) - return - } - name, want := m[1], m[2] - - // Look up the name in the innermost enclosing scope. - inner := mainScope.Innermost(pos) - if inner == nil { - t.Errorf("%s: at %s: can't find innermost scope", pos, text) - return - } - got := "undef" - if _, obj := inner.LookupParent(name, pos); obj != nil { - kind := strings.ToLower(strings.TrimPrefix(reflect.TypeOf(obj).String(), "*types2.")) - got = fmt.Sprintf("%s:%d", kind, obj.Pos().Line()) - } - if got != want { - t.Errorf("%s: at %s: %s resolved to %s, want %s", pos, 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", id.Pos(), id.Value) - 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.Value == "X" { - continue - } - - _, gotObj := inner.LookupParent(id.Value, id.Pos()) - if gotObj != wantObj { - // Print the scope tree of mainScope in case of error. - var printScopeTree func(indent string, s *Scope) - printScopeTree = func(indent string, s *Scope) { - t.Logf("%sscope %s %v-%v = %v", - indent, - ScopeComment(s), - s.Pos(), - s.End(), - s.Names()) - for i := range s.NumChildren() { - printScopeTree(indent+" ", s.Child(i)) - } - } - printScopeTree("", mainScope) - - t.Errorf("%s: Scope(%s).LookupParent(%s@%v) got %v, want %v [scopePos=%v]", - id.Pos(), - ScopeComment(inner), - id.Value, - id.Pos(), - gotObj, - wantObj, - ObjectScopePos(wantObj)) - continue - } - } -} - // newDefined creates a new defined type named T with the given underlying type. func newDefined(underlying Type) *Named { tname := NewTypeName(nopos, nil, "T", nil) diff --git a/src/cmd/compile/internal/types2/scope.go b/src/cmd/compile/internal/types2/scope.go index 216c6f23b9..eefd8fac5b 100644 --- a/src/cmd/compile/internal/types2/scope.go +++ b/src/cmd/compile/internal/types2/scope.go @@ -83,25 +83,6 @@ func (s *Scope) Lookup(name string) Object { return obj } -// LookupParent follows the parent chain of scopes starting with s until -// it finds a scope where Lookup(name) returns a non-nil object, and then -// returns that scope and object. If a valid position pos is provided, -// only objects that were declared at or before pos are considered. -// If no such scope and object exists, the result is (nil, nil). -// -// Note that obj.Parent() may be different from the returned scope if the -// object was inserted into the scope and already had a parent at that -// time (see Insert). This can only happen for dot-imported objects -// whose parent is the scope of the package that exported them. -func (s *Scope) LookupParent(name string, pos syntax.Pos) (*Scope, Object) { - for ; s != nil; s = s.parent { - if obj := s.Lookup(name); obj != nil && (!pos.IsKnown() || cmpPos(obj.scopePos(), pos) <= 0) { - return s, obj - } - } - return nil, nil -} - // Insert attempts to insert an object obj into scope s. // If s already contains an alternative object alt with // the same name, Insert leaves s unchanged and returns alt. @@ -146,47 +127,6 @@ func (s *Scope) insert(name string, obj Object) { s.elems[name] = obj } -// Pos and End describe the scope's source code extent [pos, end). -// The results are guaranteed to be valid only if the type-checked -// AST has complete position information. The extent is undefined -// for Universe and package scopes. -func (s *Scope) Pos() syntax.Pos { return s.pos } -func (s *Scope) End() syntax.Pos { return s.end } - -// Contains reports whether pos is within the scope's extent. -// The result is guaranteed to be valid only if the type-checked -// AST has complete position information. -func (s *Scope) Contains(pos syntax.Pos) bool { - return cmpPos(s.pos, pos) <= 0 && cmpPos(pos, s.end) < 0 -} - -// 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 syntax.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) { - return s.Innermost(pos) - } - } - return s - } - return nil -} - // WriteTo writes a string representation of the scope to w, // with the scope elements sorted by name. // The level of indentation is controlled by n >= 0, with diff --git a/src/go/types/api_test.go b/src/go/types/api_test.go index f868338f67..ac1fc63072 100644 --- a/src/go/types/api_test.go +++ b/src/go/types/api_test.go @@ -13,8 +13,6 @@ import ( "go/token" "internal/goversion" "internal/testenv" - "reflect" - "regexp" "slices" "strings" "sync" @@ -1994,156 +1992,6 @@ 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} - var info Info - makePkg := func(path string, files ...*ast.File) { - var err error - imports[path], err = conf.Check(path, fset, files, &info) - if err != nil { - t.Fatal(err) - } - } - - makePkg("lib", mustParse(fset, "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. - // Note that type switch case clauses with an empty body (but for - // comments) need the ";" to ensure that the recorded scope extends - // past the comments. - mainSrc := ` -/*lib=pkgname:5*/ /*X=var:1*/ /*Pi=const:8*/ /*T=typename:9*/ /*Y=var:10*/ /*F=func:12*/ -package main - -import "lib" -import . "lib" - -const Pi = 3.1415 -type T struct{} -var Y, _ = lib.X, X - -func F[T *U, U any](param1, param2 int) /*param1=undef*/ (res1 /*res1=undef*/, res2 int) /*param1=var:12*/ /*res1=var:12*/ /*U=typename:12*/ { - const pi, e = 3.1415, /*pi=undef*/ 2.71828 /*pi=const:13*/ /*e=const:13*/ - type /*t=undef*/ t /*t=typename:14*/ *t - print(Y) /*Y=var:10*/ - x, Y := Y, /*x=undef*/ /*Y=var:10*/ Pi /*x=var:16*/ /*Y=var:16*/ ; _ = x; _ = Y - var F = /*F=func:12*/ F[*int, int] /*F=var:17*/ ; _ = F - - var a []int - for i, x := range a /*i=undef*/ /*x=var:16*/ { _ = i; _ = x } - - var i interface{} - switch y := i.(type) { /*y=undef*/ - case /*y=undef*/ int /*y=undef*/ : /*y=var:23*/ ; - case float32, /*y=undef*/ float64 /*y=undef*/ : /*y=var:23*/ ; - default /*y=undef*/ : /*y=var:23*/ - println(y) - } - /*y=undef*/ - - switch int := i.(type) { - case /*int=typename:0*/ int /*int=typename:0*/ : /*int=var:31*/ - println(int) - default /*int=typename:0*/ : /*int=var:31*/ ; - } - - _ = param1 - _ = res1 - return -} -/*main=undef*/ -` - - info.Uses = make(map[*ast.Ident]Object) - f := mustParse(fset, 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 { - // Print the scope tree of mainScope in case of error. - var printScopeTree func(indent string, s *Scope) - printScopeTree = func(indent string, s *Scope) { - t.Logf("%sscope %s %v-%v = %v", - indent, - ScopeComment(s), - s.Pos(), - s.End(), - s.Names()) - for i := range s.NumChildren() { - printScopeTree(indent+" ", s.Child(i)) - } - } - printScopeTree("", mainScope) - - t.Errorf("%s: Scope(%s).LookupParent(%s@%v) got %v, want %v [scopePos=%v]", - fset.Position(id.Pos()), - ScopeComment(inner), - id.Name, - id.Pos(), - gotObj, - wantObj, - ObjectScopePos(wantObj)) - continue - } - } -} - // newDefined creates a new defined type named T with the given underlying type. // Helper function for use with TestIncompleteInterfaces only. func newDefined(underlying Type) *Named { diff --git a/src/go/types/scope.go b/src/go/types/scope.go index fd2dc6f40f..6d748009f8 100644 --- a/src/go/types/scope.go +++ b/src/go/types/scope.go @@ -86,25 +86,6 @@ func (s *Scope) Lookup(name string) Object { return obj } -// LookupParent follows the parent chain of scopes starting with s until -// it finds a scope where Lookup(name) returns a non-nil object, and then -// returns that scope and object. If a valid position pos is provided, -// only objects that were declared at or before pos are considered. -// If no such scope and object exists, the result is (nil, nil). -// -// Note that obj.Parent() may be different from the returned scope if the -// object was inserted into the scope and already had a parent at that -// time (see Insert). This can only happen for dot-imported objects -// whose parent is the scope of the package that exported them. -func (s *Scope) LookupParent(name string, pos token.Pos) (*Scope, Object) { - for ; s != nil; s = s.parent { - if obj := s.Lookup(name); obj != nil && (!pos.IsValid() || cmpPos(obj.scopePos(), pos) <= 0) { - return s, obj - } - } - return nil, nil -} - // Insert attempts to insert an object obj into scope s. // If s already contains an alternative object alt with // the same name, Insert leaves s unchanged and returns alt. @@ -149,47 +130,6 @@ func (s *Scope) insert(name string, obj Object) { s.elems[name] = obj } -// Pos and End describe the scope's source code extent [pos, end). -// The results are guaranteed to be valid only if the type-checked -// AST has complete position information. The extent is undefined -// for Universe and package scopes. -func (s *Scope) Pos() token.Pos { return s.pos } -func (s *Scope) End() token.Pos { return s.end } - -// Contains reports whether pos is within the scope's extent. -// The result is guaranteed to be valid only if the type-checked -// AST has complete position information. -func (s *Scope) Contains(pos token.Pos) bool { - return cmpPos(s.pos, pos) <= 0 && cmpPos(pos, s.end) < 0 -} - -// 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) { - return s.Innermost(pos) - } - } - return s - } - return nil -} - // WriteTo writes a string representation of the scope to w, // with the scope elements sorted by name. // The level of indentation is controlled by n >= 0, with diff --git a/src/go/types/scope2.go b/src/go/types/scope2.go new file mode 100644 index 0000000000..de7c503ea8 --- /dev/null +++ b/src/go/types/scope2.go @@ -0,0 +1,72 @@ +// Copyright 2024 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. + +// This file implements go/types-specific scope methods. +// These methods do not exist in types2. + +package types + +import "go/token" + +// LookupParent follows the parent chain of scopes starting with s until +// it finds a scope where Lookup(name) returns a non-nil object, and then +// returns that scope and object. If a valid position pos is provided, +// only objects that were declared at or before pos are considered. +// If no such scope and object exists, the result is (nil, nil). +// The results are guaranteed to be valid only if the type-checked +// AST has complete position information. +// +// Note that obj.Parent() may be different from the returned scope if the +// object was inserted into the scope and already had a parent at that +// time (see Insert). This can only happen for dot-imported objects +// whose parent is the scope of the package that exported them. +func (s *Scope) LookupParent(name string, pos token.Pos) (*Scope, Object) { + for ; s != nil; s = s.parent { + if obj := s.Lookup(name); obj != nil && (!pos.IsValid() || cmpPos(obj.scopePos(), pos) <= 0) { + return s, obj + } + } + return nil, nil +} + +// Pos and End describe the scope's source code extent [pos, end). +// The results are guaranteed to be valid only if the type-checked +// AST has complete position information. The extent is undefined +// for Universe and package scopes. +func (s *Scope) Pos() token.Pos { return s.pos } +func (s *Scope) End() token.Pos { return s.end } + +// Contains reports whether pos is within the scope's extent. +// The result is guaranteed to be valid only if the type-checked +// AST has complete position information. +func (s *Scope) Contains(pos token.Pos) bool { + return cmpPos(s.pos, pos) <= 0 && cmpPos(pos, s.end) < 0 +} + +// 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) { + return s.Innermost(pos) + } + } + return s + } + return nil +} diff --git a/src/go/types/scope2_test.go b/src/go/types/scope2_test.go new file mode 100644 index 0000000000..df016984b3 --- /dev/null +++ b/src/go/types/scope2_test.go @@ -0,0 +1,167 @@ +// Copyright 2024 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 types_test + +import ( + "fmt" + "go/ast" + "go/token" + "reflect" + "regexp" + "strings" + "testing" + + . "go/types" +) + +// 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} + var info Info + makePkg := func(path string, files ...*ast.File) { + var err error + imports[path], err = conf.Check(path, fset, files, &info) + if err != nil { + t.Fatal(err) + } + } + + makePkg("lib", mustParse(fset, "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. + // Note that type switch case clauses with an empty body (but for + // comments) need the ";" to ensure that the recorded scope extends + // past the comments. + mainSrc := ` +/*lib=pkgname:5*/ /*X=var:1*/ /*Pi=const:8*/ /*T=typename:9*/ /*Y=var:10*/ /*F=func:12*/ +package main + +import "lib" +import . "lib" + +const Pi = 3.1415 +type T struct{} +var Y, _ = lib.X, X + +func F[T *U, U any](param1, param2 int) /*param1=undef*/ (res1 /*res1=undef*/, res2 int) /*param1=var:12*/ /*res1=var:12*/ /*U=typename:12*/ { + const pi, e = 3.1415, /*pi=undef*/ 2.71828 /*pi=const:13*/ /*e=const:13*/ + type /*t=undef*/ t /*t=typename:14*/ *t + print(Y) /*Y=var:10*/ + x, Y := Y, /*x=undef*/ /*Y=var:10*/ Pi /*x=var:16*/ /*Y=var:16*/ ; _ = x; _ = Y + var F = /*F=func:12*/ F[*int, int] /*F=var:17*/ ; _ = F + + var a []int + for i, x := range a /*i=undef*/ /*x=var:16*/ { _ = i; _ = x } + + var i interface{} + switch y := i.(type) { /*y=undef*/ + case /*y=undef*/ int /*y=undef*/ : /*y=var:23*/ ; + case float32, /*y=undef*/ float64 /*y=undef*/ : /*y=var:23*/ ; + default /*y=undef*/ : /*y=var:23*/ + println(y) + } + /*y=undef*/ + + switch int := i.(type) { + case /*int=typename:0*/ int /*int=typename:0*/ : /*int=var:31*/ + println(int) + default /*int=typename:0*/ : /*int=var:31*/ ; + } + + _ = param1 + _ = res1 + return +} +/*main=undef*/ +` + + info.Uses = make(map[*ast.Ident]Object) + f := mustParse(fset, 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 { + // Print the scope tree of mainScope in case of error. + var printScopeTree func(indent string, s *Scope) + printScopeTree = func(indent string, s *Scope) { + t.Logf("%sscope %s %v-%v = %v", + indent, + ScopeComment(s), + s.Pos(), + s.End(), + s.Names()) + for i := range s.NumChildren() { + printScopeTree(indent+" ", s.Child(i)) + } + } + printScopeTree("", mainScope) + + t.Errorf("%s: Scope(%s).LookupParent(%s@%v) got %v, want %v [scopePos=%v]", + fset.Position(id.Pos()), + ScopeComment(inner), + id.Name, + id.Pos(), + gotObj, + wantObj, + ObjectScopePos(wantObj)) + continue + } + } +}