1
0
mirror of https://github.com/golang/go synced 2024-11-18 09:04:49 -07:00

refactor/rename: eliminate dependency on refactor/lexical package

so that we can delete that package.

lexicalLookup reconstructs the lexical scope from the existing tree of
types.Scope blocks, using source position information to determine
which prefix of declarations are visible.  (Inspired by Russ's
lookupAtPos in github.com/rsc/grind/grinder.)

forEachLexicalRef implements the part of the recursion from
refactor/lexical that enumerates the ast.Idents that use lexical
lookup.  (I would still like to eliminate this redundant logic by
having go/types record environments, as in CL 9493.)

Change-Id: I040ab33b508aad2dc68fd48850fe92ec072045d1
Reviewed-on: https://go-review.googlesource.com/9544
Reviewed-by: Sameer Ajmani <sameer@golang.org>
This commit is contained in:
Alan Donovan 2015-04-30 11:39:50 -04:00
parent b9f1f6a3c1
commit 03e05ec5a5

View File

@ -13,7 +13,6 @@ import (
"golang.org/x/tools/go/loader"
"golang.org/x/tools/go/types"
"golang.org/x/tools/refactor/lexical"
"golang.org/x/tools/refactor/satisfy"
)
@ -101,18 +100,20 @@ func (r *renamer) checkInPackageBlock(from types.Object) {
}
info := r.packages[from.Pkg()]
lexinfo := lexical.Structure(r.iprog.Fset, from.Pkg(), &info.Info, info.Files)
// Check that in the package block, "init" is a function, and never referenced.
if r.to == "init" {
kind := objectKind(from)
if kind == "func" {
// Reject if intra-package references to it exist.
if refs := lexinfo.Refs[from]; len(refs) > 0 {
r.errorf(from.Pos(),
"renaming this func %q to %q would make it a package initializer",
from.Name(), r.to)
r.errorf(refs[0].Id.Pos(), "\tbut references to it exist")
for id, obj := range info.Uses {
if obj == from {
r.errorf(from.Pos(),
"renaming this func %q to %q would make it a package initializer",
from.Name(), r.to)
r.errorf(id.Pos(), "\tbut references to it exist")
break
}
}
} else {
r.errorf(from.Pos(), "you cannot have a %s at package level named %q",
@ -122,7 +123,9 @@ func (r *renamer) checkInPackageBlock(from types.Object) {
// Check for conflicts between package block and all file blocks.
for _, f := range info.Files {
if prev, b := lexinfo.Blocks[f].Lookup(r.to); b == lexinfo.Blocks[f] {
fileScope := info.Info.Scopes[f]
b, prev := fileScope.LookupParent(r.to)
if b == fileScope {
r.errorf(from.Pos(), "renaming this %s %q to %q would conflict",
objectKind(from), from.Name(), r.to)
r.errorf(prev.Pos(), "\twith this %s",
@ -205,11 +208,9 @@ func (r *renamer) checkInLocalScope(from types.Object) {
// requires no checks.
//
func (r *renamer) checkInLexicalScope(from types.Object, info *loader.PackageInfo) {
lexinfo := lexical.Structure(r.iprog.Fset, info.Pkg, &info.Info, info.Files)
b := lexinfo.Defs[from] // the block defining the 'from' object
b := from.Parent() // the block defining the 'from' object
if b != nil {
to, toBlock := b.Lookup(r.to)
toBlock, to := b.LookupParent(r.to)
if toBlock == b {
// same-block conflict
r.errorf(from.Pos(), "renaming this %s %q to %q",
@ -221,42 +222,45 @@ func (r *renamer) checkInLexicalScope(from types.Object, info *loader.PackageInf
// Check for super-block conflict.
// The name r.to is defined in a superblock.
// Is that name referenced from within this block?
for _, ref := range lexinfo.Refs[to] {
if obj, _ := ref.Env.Lookup(from.Name()); obj == from {
forEachLexicalRef(info, to, func(id *ast.Ident, block *types.Scope) bool {
_, obj := lexicalLookup(block, from.Name(), id.Pos())
if obj == from {
// super-block conflict
r.errorf(from.Pos(), "renaming this %s %q to %q",
objectKind(from), from.Name(), r.to)
r.errorf(ref.Id.Pos(), "\twould shadow this reference")
r.errorf(id.Pos(), "\twould shadow this reference")
r.errorf(to.Pos(), "\tto the %s declared here",
objectKind(to))
return
return false // stop
}
}
return true
})
}
}
// Check for sub-block conflict.
// Is there an intervening definition of r.to between
// the block defining 'from' and some reference to it?
for _, ref := range lexinfo.Refs[from] {
// TODO(adonovan): think about dot imports.
// (Is b == fromBlock an invariant?)
_, fromBlock := ref.Env.Lookup(from.Name())
fromDepth := fromBlock.Depth()
forEachLexicalRef(info, from, func(id *ast.Ident, block *types.Scope) bool {
// Find the block that defines the found reference.
// It may be an ancestor.
fromBlock, _ := lexicalLookup(block, from.Name(), id.Pos())
to, toBlock := ref.Env.Lookup(r.to)
// See what r.to would resolve to in the same scope.
toBlock, to := lexicalLookup(block, r.to, id.Pos())
if to != nil {
// sub-block conflict
if toBlock.Depth() > fromDepth {
if deeper(toBlock, fromBlock) {
r.errorf(from.Pos(), "renaming this %s %q to %q",
objectKind(from), from.Name(), r.to)
r.errorf(ref.Id.Pos(), "\twould cause this reference to become shadowed")
r.errorf(id.Pos(), "\twould cause this reference to become shadowed")
r.errorf(to.Pos(), "\tby this intervening %s definition",
objectKind(to))
return
return false // stop
}
}
}
return true
})
// Renaming a type that is used as an embedded field
// requires renaming the field too. e.g.
@ -274,6 +278,123 @@ func (r *renamer) checkInLexicalScope(from types.Object, info *loader.PackageInf
}
}
// lexicalLookup is like (*types.Scope).LookupParent but respects the
// environment visible at pos. It assumes the relative position
// information is correct with each file.
func lexicalLookup(block *types.Scope, name string, pos token.Pos) (*types.Scope, types.Object) {
for b := block; b != nil; b = b.Parent() {
obj := b.Lookup(name)
// The scope of a package-level object is the entire package,
// so ignore pos in that case.
// No analogous clause is needed for file-level objects
// since no reference can appear before an import decl.
if obj != nil && (b == obj.Pkg().Scope() || obj.Pos() < pos) {
return b, obj
}
}
return nil, nil
}
// deeper reports whether block x is lexically deeper than y.
func deeper(x, y *types.Scope) bool {
if x == y || x == nil {
return false
} else if y == nil {
return true
} else {
return deeper(x.Parent(), y.Parent())
}
}
// forEachLexicalRef calls fn(id, block) for each identifier id in package
// info that is a reference to obj in lexical scope. block is the
// lexical block enclosing the reference. If fn returns false the
// iteration is terminated and findLexicalRefs returns false.
func forEachLexicalRef(info *loader.PackageInfo, obj types.Object, fn func(id *ast.Ident, block *types.Scope) bool) bool {
ok := true
var stack []ast.Node
var visit func(n ast.Node) bool
visit = func(n ast.Node) bool {
if n == nil {
stack = stack[:len(stack)-1] // pop
return false
}
if !ok {
return false // bail out
}
stack = append(stack, n) // push
switch n := n.(type) {
case *ast.Ident:
if info.Uses[n] == obj {
block := enclosingBlock(&info.Info, stack)
if !fn(n, block) {
ok = false
}
}
return visit(nil) // pop stack
case *ast.SelectorExpr:
// don't visit n.Sel
ast.Inspect(n.X, visit)
return visit(nil) // pop stack, don't descend
case *ast.CompositeLit:
// Handle recursion ourselves for struct literals
// so we don't visit field identifiers.
tv := info.Types[n]
if _, ok := deref(tv.Type).Underlying().(*types.Struct); ok {
if n.Type != nil {
ast.Inspect(n.Type, visit)
}
for _, elt := range n.Elts {
if kv, ok := elt.(*ast.KeyValueExpr); ok {
ast.Inspect(kv.Value, visit)
} else {
ast.Inspect(elt, visit)
}
}
return visit(nil) // pop stack, don't descend
}
}
return true
}
for _, f := range info.Files {
ast.Inspect(f, visit)
if len(stack) != 0 {
panic(stack)
}
if !ok {
break
}
}
return ok
}
// enclosingBlock returns the innermost block enclosing the specified
// AST node, specified in the form of a path from the root of the file,
// [file...n].
func enclosingBlock(info *types.Info, stack []ast.Node) *types.Scope {
for i := range stack {
n := stack[len(stack)-1-i]
// For some reason, go/types always associates a
// function's scope with its FuncType.
// TODO(adonovan): feature or a bug?
switch f := n.(type) {
case *ast.FuncDecl:
n = f.Type
case *ast.FuncLit:
n = f.Type
}
if b := info.Scopes[n]; b != nil {
return b
}
}
panic("no Scope for *ast.File")
}
func (r *renamer) checkLabel(label *types.Label) {
// Check there are no identical labels in the function's label block.
// (Label blocks don't nest, so this is easy.)