mirror of https://github.com/golang/go.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 <adonovan@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Auto-Submit: Robert Griesemer <gri@google.com> Reviewed-by: Robert Griesemer <gri@google.com>
This commit is contained in:
parent
62452bed48
commit
6ad3933e28
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue