1
0
mirror of https://github.com/golang/go synced 2024-11-19 05:34:40 -07:00
go/refactor/lexical/lexical.go
Alan Donovan 997b3545fd go/types: change {Type,Object,Selection}String to accept a Qualifier function
The optional Qualifier function determines what prefix to attach to
package-level names, enabling clients to qualify packages in different
ways, for example, using only the package name instead of its complete
path, or using the locally appropriate name for package given a set of
(possibly renaming) imports.

Prior to this change, clients wanting this behavior had to copy
hundreds of lines of complex printing logic.

Fun fact: (*types.Package).Path and (*types.Package).Name are valid
Qualifier functions.

We provide the RelativeTo helper function to create Qualifiers so that
the old behavior remains a one-liner.

Fixes golang/go#11133

Change-Id: Ibd63f639c7b3aa1738826d6165f2d810efeb8293
Reviewed-on: https://go-review.googlesource.com/11692
Reviewed-by: Robert Griesemer <gri@golang.org>
2015-06-30 19:00:00 +00:00

764 lines
18 KiB
Go

// Package lexical computes the structure of the lexical environment,
// including the definition of and references to all universal,
// package-level, file-level and function-local entities. It does not
// record qualified identifiers, labels, struct fields, or methods.
//
// It is intended for renaming and refactoring tools, which need a more
// precise understanding of identifier resolution than is available from
// the output of the type-checker alone.
//
// THIS INTERFACE IS EXPERIMENTAL AND MAY CHANGE OR BE REMOVED IN FUTURE.
//
package lexical // import "golang.org/x/tools/refactor/lexical"
// OVERVIEW
//
// As we traverse the AST, we build a "spaghetti stack" of Blocks,
// i.e. a tree with parent edges pointing to the root. Each time we
// visit an identifier that's a reference into the lexical environment,
// we create and save an Environment, which captures the current mapping
// state of the Block; these are saved for the client.
//
// We don't bother recording non-lexical references.
// TODO(adonovan):
// - make it robust against syntax errors. Audit all type assertions, etc.
// - better still, after the Go 1.4 thaw, move this into go/types.
// I don't think it need be a big change since the visitor is already there;
// we just need to records Environments. lexical.Block is analogous
// to types.Scope.
import (
"fmt"
"go/ast"
"go/token"
"os"
"strconv"
"golang.org/x/tools/go/types"
)
const trace = false
var logf = func(format string, args ...interface{}) {
fmt.Fprintf(os.Stderr, format, args...)
}
// A Block is a level of the lexical environment, a tree of blocks.
// It maps names to objects.
//
type Block struct {
kind string // one of universe package file func block if switch typeswitch case for range
syntax ast.Node // syntax declaring the block (nil for universe and package) [needed?]
parent Environment
bindings []types.Object // bindings in lexical order
index map[string]int // maps a name to the index of its binding, for fast lookup
}
// An Environment is a snapshot of a Block taken at a certain lexical
// position. It may contain bindings for fewer names than the
// (completed) block, or different bindings for names that are
// re-defined later in the block.
//
// For example, the lexical Block for the function f below contains a
// binding for the local var x, but the Environments captured by at the
// two print(x) calls differ: the first contains this binding, the
// second does not. The first Environment contains a different binding
// for x: the string var defined in the package block, an ancestor.
//
// var x string
// func f() {
// print(x)
// x := 1
// print(x)
// }
//
type Environment struct {
block *Block
nbindings int // length of prefix of block.bindings that's visible
}
// Depth returns the depth of this block in the block tree.
// The universal block has depth 1, a package block 2, a file block 3, etc.
func (b *Block) Depth() int {
if b == nil {
return 0
}
return 1 + b.parent.block.Depth()
}
// env returns an Environment that is a snapshot of b's current state.
func (b *Block) env() Environment {
return Environment{b, len(b.bindings)}
}
// Lookup returns the definition of name in the environment specified by
// env, and the Block that defines it, which may be an ancestor.
func (env Environment) Lookup(name string) (types.Object, *Block) {
if env.block == nil {
return nil, nil
}
return lookup(env.block, name, env.nbindings)
}
// nbindings specifies what prefix of b.bindings should be considered visible.
func lookup(b *Block, name string, nbindings int) (types.Object, *Block) {
if b == nil {
return nil, nil
}
if i, ok := b.index[name]; ok && i < nbindings {
return b.bindings[i], b
}
parent := b.parent
if parent.block == nil {
return nil, nil
}
return lookup(parent.block, name, parent.nbindings)
}
// Lookup returns the definition of name in the environment specified by
// b, and the Block that defines it, which may be an ancestor.
func (b *Block) Lookup(name string) (types.Object, *Block) {
return b.env().Lookup(name)
}
// Block returns the block of which this environment is a partial view.
func (env Environment) Block() *Block {
return env.block
}
func (env Environment) String() string {
return fmt.Sprintf("%s:%d", env.block, env.nbindings)
}
func (b *Block) String() string {
var s string
if b.parent.block != nil {
s = b.parent.block.String()
s += "."
}
return s + b.kind
}
var universe = &Block{kind: "universe", index: make(map[string]int)}
func init() {
for i, name := range types.Universe.Names() {
obj := types.Universe.Lookup(name)
universe.bindings = append(universe.bindings, obj)
universe.index[name] = i
}
}
// -- resolver ---------------------------------------------------------
// A Reference provides the lexical environment for a given reference to
// an object in lexical scope.
type Reference struct {
Id *ast.Ident
Env Environment
}
// resolver holds the state of the identifier resolution visitation:
// the package information, the result, and the current block.
type resolver struct {
fset *token.FileSet
imports map[string]*types.Package
pkg *types.Package
info *types.Info
// visitor state
block *Block
result *Info
}
func (r *resolver) setBlock(kind string, syntax ast.Node) *Block {
b := &Block{
kind: kind,
syntax: syntax,
parent: r.block.env(),
index: make(map[string]int),
}
if syntax != nil {
r.result.Blocks[syntax] = b
}
r.block = b
return b
}
func (r *resolver) qualifier(pkg *types.Package) string {
if pkg == r.pkg {
return "" // unqualified intra-package reference
}
return pkg.Path()
}
func (r *resolver) use(id *ast.Ident, env Environment) {
if id.Name == "_" {
return // an error
}
obj, _ := env.Lookup(id.Name)
if obj == nil {
logf("%s: lookup of %s failed\n", r.fset.Position(id.Pos()), id.Name)
} else if want := r.info.Uses[id]; obj != want {
// sanity check against go/types resolver
logf("%s: internal error: lookup of %s yielded wrong object: got %v (%s), want %v\n",
r.fset.Position(id.Pos()), id.Name, types.ObjectString(obj, r.qualifier),
r.fset.Position(obj.Pos()),
want)
}
if trace {
logf("use %s = %v in %s\n", id.Name, types.ObjectString(obj, r.qualifier), env)
}
r.result.Refs[obj] = append(r.result.Refs[obj], Reference{id, env})
}
func (r *resolver) define(b *Block, id *ast.Ident) {
obj := r.info.Defs[id]
if obj == nil {
logf("%s: internal error: not a defining ident: %s\n",
r.fset.Position(id.Pos()), id.Name)
panic(id)
}
r.defineObject(b, id.Name, obj)
// Objects (other than PkgName) defined at file scope
// are also defined in the enclosing package scope.
if _, ok := b.syntax.(*ast.File); ok {
switch obj.(type) {
default:
r.defineObject(b.parent.block, id.Name, obj)
case nil, *types.PkgName:
}
}
}
// Used for implicit objects created by some ImportSpecs and CaseClauses.
func (r *resolver) defineImplicit(b *Block, n ast.Node, name string) {
obj := r.info.Implicits[n]
if obj == nil {
logf("%s: internal error: not an implicit definition: %T\n",
r.fset.Position(n.Pos()), n)
}
r.defineObject(b, name, obj)
}
func (r *resolver) defineObject(b *Block, name string, obj types.Object) {
if obj.Name() == "_" {
return
}
i := len(b.bindings)
b.bindings = append(b.bindings, obj)
b.index[name] = i
if trace {
logf("def %s = %s in %s\n", name, types.ObjectString(obj, r.qualifier), b)
}
r.result.Defs[obj] = b
}
func (r *resolver) function(recv *ast.FieldList, typ *ast.FuncType, body *ast.BlockStmt, syntax ast.Node) {
// Use all signature types in enclosing block.
r.expr(typ)
r.fieldList(recv, false)
savedBlock := r.block // save
r.setBlock("func", syntax)
// Define all parameters/results, and visit the body, within the func block.
r.fieldList(typ.Params, true)
r.fieldList(typ.Results, true)
r.fieldList(recv, true)
if body != nil {
r.stmtList(body.List)
}
r.block = savedBlock // restore
}
func (r *resolver) fieldList(list *ast.FieldList, def bool) {
if list != nil {
for _, f := range list.List {
if def {
for _, id := range f.Names {
r.define(r.block, id)
}
} else {
r.expr(f.Type)
}
}
}
}
func (r *resolver) exprList(list []ast.Expr) {
for _, x := range list {
r.expr(x)
}
}
func (r *resolver) expr(n ast.Expr) {
switch n := n.(type) {
case *ast.BadExpr:
case *ast.BasicLit:
// no-op
case *ast.Ident:
r.use(n, r.block.env())
case *ast.Ellipsis:
if n.Elt != nil {
r.expr(n.Elt)
}
case *ast.FuncLit:
r.function(nil, n.Type, n.Body, n)
case *ast.CompositeLit:
if n.Type != nil {
r.expr(n.Type)
}
tv := r.info.Types[n]
if _, ok := deref(tv.Type).Underlying().(*types.Struct); ok {
for _, elt := range n.Elts {
if kv, ok := elt.(*ast.KeyValueExpr); ok {
r.expr(kv.Value)
// Also uses field kv.Key (non-lexical)
// id := kv.Key.(*ast.Ident)
// obj := r.info.Uses[id]
// logf("use %s = %v (field)\n",
// id.Name, types.ObjectString(obj, r.qualifier))
// TODO make a fake FieldVal selection?
} else {
r.expr(elt)
}
}
} else {
r.exprList(n.Elts)
}
case *ast.ParenExpr:
r.expr(n.X)
case *ast.SelectorExpr:
r.expr(n.X)
// Non-lexical reference to field/method, or qualified identifier.
// if sel, ok := r.info.Selections[n]; ok { // selection
// switch sel.Kind() {
// case types.FieldVal:
// logf("use %s = %v (field)\n",
// n.Sel.Name, types.ObjectString(sel.Obj(), r.qualifier))
// case types.MethodExpr, types.MethodVal:
// logf("use %s = %v (method)\n",
// n.Sel.Name, types.ObjectString(sel.Obj(), r.qualifier))
// }
// } else { // qualified identifier
// obj := r.info.Uses[n.Sel]
// logf("use %s = %v (qualified)\n", n.Sel.Name, obj)
// }
case *ast.IndexExpr:
r.expr(n.X)
r.expr(n.Index)
case *ast.SliceExpr:
r.expr(n.X)
if n.Low != nil {
r.expr(n.Low)
}
if n.High != nil {
r.expr(n.High)
}
if n.Max != nil {
r.expr(n.Max)
}
case *ast.TypeAssertExpr:
r.expr(n.X)
if n.Type != nil {
r.expr(n.Type)
}
case *ast.CallExpr:
r.expr(n.Fun)
r.exprList(n.Args)
case *ast.StarExpr:
r.expr(n.X)
case *ast.UnaryExpr:
r.expr(n.X)
case *ast.BinaryExpr:
r.expr(n.X)
r.expr(n.Y)
case *ast.KeyValueExpr:
r.expr(n.Key)
r.expr(n.Value)
case *ast.ArrayType:
if n.Len != nil {
r.expr(n.Len)
}
r.expr(n.Elt)
case *ast.StructType:
// Use all the type names, but don't define any fields.
r.fieldList(n.Fields, false)
case *ast.FuncType:
// Use all the type names, but don't define any vars.
r.fieldList(n.Params, false)
r.fieldList(n.Results, false)
case *ast.InterfaceType:
// Use all the type names, but don't define any methods.
r.fieldList(n.Methods, false)
case *ast.MapType:
r.expr(n.Key)
r.expr(n.Value)
case *ast.ChanType:
r.expr(n.Value)
default:
panic(n)
}
}
func (r *resolver) stmtList(list []ast.Stmt) {
for _, s := range list {
r.stmt(s)
}
}
func (r *resolver) stmt(n ast.Stmt) {
switch n := n.(type) {
case *ast.BadStmt:
case *ast.EmptyStmt:
// nothing to do
case *ast.DeclStmt:
decl := n.Decl.(*ast.GenDecl)
for _, spec := range decl.Specs {
switch spec := spec.(type) {
case *ast.ValueSpec: // const or var
if spec.Type != nil {
r.expr(spec.Type)
}
r.exprList(spec.Values)
for _, name := range spec.Names {
r.define(r.block, name)
}
case *ast.TypeSpec:
r.define(r.block, spec.Name)
r.expr(spec.Type)
}
}
case *ast.LabeledStmt:
// Also defines label n.Label (non-lexical)
r.stmt(n.Stmt)
case *ast.ExprStmt:
r.expr(n.X)
case *ast.SendStmt:
r.expr(n.Chan)
r.expr(n.Value)
case *ast.IncDecStmt:
r.expr(n.X)
case *ast.AssignStmt:
if n.Tok == token.DEFINE {
r.exprList(n.Rhs)
for _, lhs := range n.Lhs {
id := lhs.(*ast.Ident)
if _, ok := r.info.Defs[id]; ok {
r.define(r.block, id)
} else {
r.use(id, r.block.env())
}
}
} else { // ASSIGN
r.exprList(n.Lhs)
r.exprList(n.Rhs)
}
case *ast.GoStmt:
r.expr(n.Call)
case *ast.DeferStmt:
r.expr(n.Call)
case *ast.ReturnStmt:
r.exprList(n.Results)
case *ast.BranchStmt:
if n.Label != nil {
// Also uses label n.Label (non-lexical)
}
case *ast.SelectStmt:
r.stmtList(n.Body.List)
case *ast.BlockStmt: // (explicit blocks only)
savedBlock := r.block // save
r.setBlock("block", n)
r.stmtList(n.List)
r.block = savedBlock // restore
case *ast.IfStmt:
savedBlock := r.block // save
r.setBlock("if", n)
if n.Init != nil {
r.stmt(n.Init)
}
r.expr(n.Cond)
r.stmt(n.Body) // new block
if n.Else != nil {
r.stmt(n.Else)
}
r.block = savedBlock // restore
case *ast.CaseClause:
savedBlock := r.block // save
r.setBlock("case", n)
if obj, ok := r.info.Implicits[n]; ok {
// e.g.
// switch y := x.(type) {
// case T: // we declare an implicit 'var y T' in this block
// }
r.defineImplicit(r.block, n, obj.Name())
}
r.exprList(n.List)
r.stmtList(n.Body)
r.block = savedBlock // restore
case *ast.SwitchStmt:
savedBlock := r.block // save
r.setBlock("switch", n)
if n.Init != nil {
r.stmt(n.Init)
}
if n.Tag != nil {
r.expr(n.Tag)
}
r.stmtList(n.Body.List)
r.block = savedBlock // restore
case *ast.TypeSwitchStmt:
savedBlock := r.block // save
r.setBlock("typeswitch", n)
if n.Init != nil {
r.stmt(n.Init)
}
if assign, ok := n.Assign.(*ast.AssignStmt); ok { // y := x.(type)
r.expr(assign.Rhs[0]) // skip y: not a defining ident
} else {
r.stmt(n.Assign)
}
r.stmtList(n.Body.List)
r.block = savedBlock // restore
case *ast.CommClause:
savedBlock := r.block // save
r.setBlock("case", n)
if n.Comm != nil {
r.stmt(n.Comm)
}
r.stmtList(n.Body)
r.block = savedBlock // restore
case *ast.ForStmt:
savedBlock := r.block // save
r.setBlock("for", n)
if n.Init != nil {
r.stmt(n.Init)
}
if n.Cond != nil {
r.expr(n.Cond)
}
if n.Post != nil {
r.stmt(n.Post)
}
r.stmt(n.Body)
r.block = savedBlock // restore
case *ast.RangeStmt:
r.expr(n.X)
savedBlock := r.block // save
r.setBlock("range", n)
if n.Tok == token.DEFINE {
if n.Key != nil {
r.define(r.block, n.Key.(*ast.Ident))
}
if n.Value != nil {
r.define(r.block, n.Value.(*ast.Ident))
}
} else {
if n.Key != nil {
r.expr(n.Key)
}
if n.Value != nil {
r.expr(n.Value)
}
}
r.stmt(n.Body)
r.block = savedBlock // restore
default:
panic(n)
}
}
func (r *resolver) doImport(s *ast.ImportSpec, fileBlock *Block) {
path, _ := strconv.Unquote(s.Path.Value)
pkg := r.imports[path]
if s.Name == nil { // normal
r.defineImplicit(fileBlock, s, pkg.Name())
} else if s.Name.Name == "." { // dot import
for _, name := range pkg.Scope().Names() {
if ast.IsExported(name) {
obj := pkg.Scope().Lookup(name)
r.defineObject(fileBlock, name, obj)
}
}
} else { // renaming import
r.define(fileBlock, s.Name)
}
}
func (r *resolver) doPackage(pkg *types.Package, files []*ast.File) {
r.block = universe
r.result.Blocks[nil] = universe
r.result.PackageBlock = r.setBlock("package", nil)
var fileBlocks []*Block
// 1. Insert all package-level objects into file and package blocks.
// (PkgName objects are only inserted into file blocks.)
for _, f := range files {
r.block = r.result.PackageBlock
fileBlock := r.setBlock("file", f) // package is not yet visible to file
fileBlocks = append(fileBlocks, fileBlock)
for _, d := range f.Decls {
switch d := d.(type) {
case *ast.GenDecl:
for _, s := range d.Specs {
switch s := s.(type) {
case *ast.ImportSpec:
r.doImport(s, fileBlock)
case *ast.ValueSpec: // const or var
for _, name := range s.Names {
r.define(r.result.PackageBlock, name)
}
case *ast.TypeSpec:
r.define(r.result.PackageBlock, s.Name)
}
}
case *ast.FuncDecl:
if d.Recv == nil { // function
if d.Name.Name != "init" {
r.define(r.result.PackageBlock, d.Name)
}
}
}
}
}
// 2. Now resolve bodies of GenDecls and FuncDecls.
for i, f := range files {
fileBlock := fileBlocks[i]
fileBlock.parent = r.result.PackageBlock.env() // make entire package visible to this file
for _, d := range f.Decls {
r.block = fileBlock
switch d := d.(type) {
case *ast.GenDecl:
for _, s := range d.Specs {
switch s := s.(type) {
case *ast.ValueSpec: // const or var
if s.Type != nil {
r.expr(s.Type)
}
r.exprList(s.Values)
case *ast.TypeSpec:
r.expr(s.Type)
}
}
case *ast.FuncDecl:
r.function(d.Recv, d.Type, d.Body, d)
}
}
}
r.block = nil
}
// An Info contains the lexical reference structure of a package.
type Info struct {
Defs map[types.Object]*Block // maps each object to its defining lexical block
Refs map[types.Object][]Reference // maps each object to the set of references to it
Blocks map[ast.Node]*Block // maps declaring syntax to block; nil => universe
PackageBlock *Block // the package-level lexical block
}
// Structure computes the structure of the lexical environment of the
// package specified by (pkg, info, files).
//
// The info.{Types,Defs,Uses,Implicits} maps must have been populated
// by the type-checker
//
// fset is used for logging.
//
func Structure(fset *token.FileSet, pkg *types.Package, info *types.Info, files []*ast.File) *Info {
r := resolver{
fset: fset,
imports: make(map[string]*types.Package),
result: &Info{
Defs: make(map[types.Object]*Block),
Refs: make(map[types.Object][]Reference),
Blocks: make(map[ast.Node]*Block),
},
pkg: pkg,
info: info,
}
// Build import map for just this package.
r.imports["unsafe"] = types.Unsafe
for _, imp := range pkg.Imports() {
r.imports[imp.Path()] = imp
}
r.doPackage(pkg, files)
return r.result
}
// -- Plundered from golang.org/x/tools/go/ssa -----------------
// deref returns a pointer's element type; otherwise it returns typ.
func deref(typ types.Type) types.Type {
if p, ok := typ.Underlying().(*types.Pointer); ok {
return p.Elem()
}
return typ
}