refactor/...: remove go1.8-tagged files

Change-Id: I7df485b71ba87670d9e70e5b44e2110fd8f6a815
Reviewed-on: https://go-review.googlesource.com/32815
Reviewed-by: Robert Griesemer <gri@golang.org>
This commit is contained in:
Alan Donovan 2016-11-04 17:20:28 -04:00
parent fe1fa38300
commit be0bbf2399
10 changed files with 0 additions and 2281 deletions

View File

@ -2,8 +2,6 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build !go1.8
// Package eg implements the example-based refactoring tool whose
// command-line is defined in golang.org/x/tools/cmd/eg.
package eg // import "golang.org/x/tools/refactor/eg"

View File

@ -1,346 +0,0 @@
// Copyright 2014 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.
// +build go1.8
// Package eg implements the example-based refactoring tool whose
// command-line is defined in golang.org/x/tools/cmd/eg.
package eg // import "golang.org/x/tools/refactor/eg"
import (
"bytes"
"fmt"
"go/ast"
"go/format"
"go/printer"
"go/token"
"go/types"
"os"
)
const Help = `
This tool implements example-based refactoring of expressions.
The transformation is specified as a Go file defining two functions,
'before' and 'after', of identical types. Each function body consists
of a single statement: either a return statement with a single
(possibly multi-valued) expression, or an expression statement. The
'before' expression specifies a pattern and the 'after' expression its
replacement.
package P
import ( "errors"; "fmt" )
func before(s string) error { return fmt.Errorf("%s", s) }
func after(s string) error { return errors.New(s) }
The expression statement form is useful when the expression has no
result, for example:
func before(msg string) { log.Fatalf("%s", msg) }
func after(msg string) { log.Fatal(msg) }
The parameters of both functions are wildcards that may match any
expression assignable to that type. If the pattern contains multiple
occurrences of the same parameter, each must match the same expression
in the input for the pattern to match. If the replacement contains
multiple occurrences of the same parameter, the expression will be
duplicated, possibly changing the side-effects.
The tool analyses all Go code in the packages specified by the
arguments, replacing all occurrences of the pattern with the
substitution.
So, the transform above would change this input:
err := fmt.Errorf("%s", "error: " + msg)
to this output:
err := errors.New("error: " + msg)
Identifiers, including qualified identifiers (p.X) are considered to
match only if they denote the same object. This allows correct
matching even in the presence of dot imports, named imports and
locally shadowed package names in the input program.
Matching of type syntax is semantic, not syntactic: type syntax in the
pattern matches type syntax in the input if the types are identical.
Thus, func(x int) matches func(y int).
This tool was inspired by other example-based refactoring tools,
'gofmt -r' for Go and Refaster for Java.
LIMITATIONS
===========
EXPRESSIVENESS
Only refactorings that replace one expression with another, regardless
of the expression's context, may be expressed. Refactoring arbitrary
statements (or sequences of statements) is a less well-defined problem
and is less amenable to this approach.
A pattern that contains a function literal (and hence statements)
never matches.
There is no way to generalize over related types, e.g. to express that
a wildcard may have any integer type, for example.
It is not possible to replace an expression by one of a different
type, even in contexts where this is legal, such as x in fmt.Print(x).
The struct literals T{x} and T{K: x} cannot both be matched by a single
template.
SAFETY
Verifying that a transformation does not introduce type errors is very
complex in the general case. An innocuous-looking replacement of one
constant by another (e.g. 1 to 2) may cause type errors relating to
array types and indices, for example. The tool performs only very
superficial checks of type preservation.
IMPORTS
Although the matching algorithm is fully aware of scoping rules, the
replacement algorithm is not, so the replacement code may contain
incorrect identifier syntax for imported objects if there are dot
imports, named imports or locally shadowed package names in the input
program.
Imports are added as needed, but they are not removed as needed.
Run 'goimports' on the modified file for now.
Dot imports are forbidden in the template.
TIPS
====
Sometimes a little creativity is required to implement the desired
migration. This section lists a few tips and tricks.
To remove the final parameter from a function, temporarily change the
function signature so that the final parameter is variadic, as this
allows legal calls both with and without the argument. Then use eg to
remove the final argument from all callers, and remove the variadic
parameter by hand. The reverse process can be used to add a final
parameter.
To add or remove parameters other than the final one, you must do it in
stages: (1) declare a variant function f' with a different name and the
desired parameters; (2) use eg to transform calls to f into calls to f',
changing the arguments as needed; (3) change the declaration of f to
match f'; (4) use eg to rename f' to f in all calls; (5) delete f'.
`
// TODO(adonovan): expand upon the above documentation as an HTML page.
// A Transformer represents a single example-based transformation.
type Transformer struct {
fset *token.FileSet
verbose bool
info *types.Info // combined type info for template/input/output ASTs
seenInfos map[*types.Info]bool
wildcards map[*types.Var]bool // set of parameters in func before()
env map[string]ast.Expr // maps parameter name to wildcard binding
importedObjs map[types.Object]*ast.SelectorExpr // objects imported by after().
before, after ast.Expr
allowWildcards bool
// Working state of Transform():
nsubsts int // number of substitutions made
currentPkg *types.Package // package of current call
}
// NewTransformer returns a transformer based on the specified template,
// a single-file package containing "before" and "after" functions as
// described in the package documentation.
// tmplInfo is the type information for tmplFile.
//
func NewTransformer(fset *token.FileSet, tmplPkg *types.Package, tmplFile *ast.File, tmplInfo *types.Info, verbose bool) (*Transformer, error) {
// Check the template.
beforeSig := funcSig(tmplPkg, "before")
if beforeSig == nil {
return nil, fmt.Errorf("no 'before' func found in template")
}
afterSig := funcSig(tmplPkg, "after")
if afterSig == nil {
return nil, fmt.Errorf("no 'after' func found in template")
}
// TODO(adonovan): should we also check the names of the params match?
if !types.Identical(afterSig, beforeSig) {
return nil, fmt.Errorf("before %s and after %s functions have different signatures",
beforeSig, afterSig)
}
for _, imp := range tmplFile.Imports {
if imp.Name != nil && imp.Name.Name == "." {
// Dot imports are currently forbidden. We
// make the simplifying assumption that all
// imports are regular, without local renames.
// TODO(adonovan): document
return nil, fmt.Errorf("dot-import (of %s) in template", imp.Path.Value)
}
}
var beforeDecl, afterDecl *ast.FuncDecl
for _, decl := range tmplFile.Decls {
if decl, ok := decl.(*ast.FuncDecl); ok {
switch decl.Name.Name {
case "before":
beforeDecl = decl
case "after":
afterDecl = decl
}
}
}
before, err := soleExpr(beforeDecl)
if err != nil {
return nil, fmt.Errorf("before: %s", err)
}
after, err := soleExpr(afterDecl)
if err != nil {
return nil, fmt.Errorf("after: %s", err)
}
wildcards := make(map[*types.Var]bool)
for i := 0; i < beforeSig.Params().Len(); i++ {
wildcards[beforeSig.Params().At(i)] = true
}
// checkExprTypes returns an error if Tb (type of before()) is not
// safe to replace with Ta (type of after()).
//
// Only superficial checks are performed, and they may result in both
// false positives and negatives.
//
// Ideally, we would only require that the replacement be assignable
// to the context of a specific pattern occurrence, but the type
// checker doesn't record that information and it's complex to deduce.
// A Go type cannot capture all the constraints of a given expression
// context, which may include the size, constness, signedness,
// namedness or constructor of its type, and even the specific value
// of the replacement. (Consider the rule that array literal keys
// must be unique.) So we cannot hope to prove the safety of a
// transformation in general.
Tb := tmplInfo.TypeOf(before)
Ta := tmplInfo.TypeOf(after)
if types.AssignableTo(Tb, Ta) {
// safe: replacement is assignable to pattern.
} else if tuple, ok := Tb.(*types.Tuple); ok && tuple.Len() == 0 {
// safe: pattern has void type (must appear in an ExprStmt).
} else {
return nil, fmt.Errorf("%s is not a safe replacement for %s", Ta, Tb)
}
tr := &Transformer{
fset: fset,
verbose: verbose,
wildcards: wildcards,
allowWildcards: true,
seenInfos: make(map[*types.Info]bool),
importedObjs: make(map[types.Object]*ast.SelectorExpr),
before: before,
after: after,
}
// Combine type info from the template and input packages, and
// type info for the synthesized ASTs too. This saves us
// having to book-keep where each ast.Node originated as we
// construct the resulting hybrid AST.
tr.info = &types.Info{
Types: make(map[ast.Expr]types.TypeAndValue),
Defs: make(map[*ast.Ident]types.Object),
Uses: make(map[*ast.Ident]types.Object),
Selections: make(map[*ast.SelectorExpr]*types.Selection),
}
mergeTypeInfo(tr.info, tmplInfo)
// Compute set of imported objects required by after().
// TODO(adonovan): reject dot-imports in pattern
ast.Inspect(after, func(n ast.Node) bool {
if n, ok := n.(*ast.SelectorExpr); ok {
if _, ok := tr.info.Selections[n]; !ok {
// qualified ident
obj := tr.info.Uses[n.Sel]
tr.importedObjs[obj] = n
return false // prune
}
}
return true // recur
})
return tr, nil
}
// WriteAST is a convenience function that writes AST f to the specified file.
func WriteAST(fset *token.FileSet, filename string, f *ast.File) (err error) {
fh, err := os.Create(filename)
if err != nil {
return err
}
defer func() {
if err2 := fh.Close(); err != nil {
err = err2 // prefer earlier error
}
}()
return format.Node(fh, fset, f)
}
// -- utilities --------------------------------------------------------
// funcSig returns the signature of the specified package-level function.
func funcSig(pkg *types.Package, name string) *types.Signature {
if f, ok := pkg.Scope().Lookup(name).(*types.Func); ok {
return f.Type().(*types.Signature)
}
return nil
}
// soleExpr returns the sole expression in the before/after template function.
func soleExpr(fn *ast.FuncDecl) (ast.Expr, error) {
if fn.Body == nil {
return nil, fmt.Errorf("no body")
}
if len(fn.Body.List) != 1 {
return nil, fmt.Errorf("must contain a single statement")
}
switch stmt := fn.Body.List[0].(type) {
case *ast.ReturnStmt:
if len(stmt.Results) != 1 {
return nil, fmt.Errorf("return statement must have a single operand")
}
return stmt.Results[0], nil
case *ast.ExprStmt:
return stmt.X, nil
}
return nil, fmt.Errorf("must contain a single return or expression statement")
}
// mergeTypeInfo adds type info from src to dst.
func mergeTypeInfo(dst, src *types.Info) {
for k, v := range src.Types {
dst.Types[k] = v
}
for k, v := range src.Defs {
dst.Defs[k] = v
}
for k, v := range src.Uses {
dst.Uses[k] = v
}
for k, v := range src.Selections {
dst.Selections[k] = v
}
}
// (debugging only)
func astString(fset *token.FileSet, n ast.Node) string {
var buf bytes.Buffer
printer.Fprint(&buf, fset, n)
return buf.String()
}

View File

@ -2,8 +2,6 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build !go1.8
package eg
import (

View File

@ -1,251 +0,0 @@
// Copyright 2014 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.
// +build go1.8
package eg
import (
"fmt"
"go/ast"
exact "go/constant"
"go/token"
"go/types"
"log"
"os"
"reflect"
"golang.org/x/tools/go/ast/astutil"
)
// matchExpr reports whether pattern x matches y.
//
// If tr.allowWildcards, Idents in x that refer to parameters are
// treated as wildcards, and match any y that is assignable to the
// parameter type; matchExpr records this correspondence in tr.env.
// Otherwise, matchExpr simply reports whether the two trees are
// equivalent.
//
// A wildcard appearing more than once in the pattern must
// consistently match the same tree.
//
func (tr *Transformer) matchExpr(x, y ast.Expr) bool {
if x == nil && y == nil {
return true
}
if x == nil || y == nil {
return false
}
x = unparen(x)
y = unparen(y)
// Is x a wildcard? (a reference to a 'before' parameter)
if xobj, ok := tr.wildcardObj(x); ok {
return tr.matchWildcard(xobj, y)
}
// Object identifiers (including pkg-qualified ones)
// are handled semantically, not syntactically.
xobj := isRef(x, tr.info)
yobj := isRef(y, tr.info)
if xobj != nil {
return xobj == yobj
}
if yobj != nil {
return false
}
// TODO(adonovan): audit: we cannot assume these ast.Exprs
// contain non-nil pointers. e.g. ImportSpec.Name may be a
// nil *ast.Ident.
if reflect.TypeOf(x) != reflect.TypeOf(y) {
return false
}
switch x := x.(type) {
case *ast.Ident:
log.Fatalf("unexpected Ident: %s", astString(tr.fset, x))
case *ast.BasicLit:
y := y.(*ast.BasicLit)
xval := exact.MakeFromLiteral(x.Value, x.Kind, 0)
yval := exact.MakeFromLiteral(y.Value, y.Kind, 0)
return exact.Compare(xval, token.EQL, yval)
case *ast.FuncLit:
// func literals (and thus statement syntax) never match.
return false
case *ast.CompositeLit:
y := y.(*ast.CompositeLit)
return (x.Type == nil) == (y.Type == nil) &&
(x.Type == nil || tr.matchType(x.Type, y.Type)) &&
tr.matchExprs(x.Elts, y.Elts)
case *ast.SelectorExpr:
y := y.(*ast.SelectorExpr)
return tr.matchSelectorExpr(x, y) &&
tr.info.Selections[x].Obj() == tr.info.Selections[y].Obj()
case *ast.IndexExpr:
y := y.(*ast.IndexExpr)
return tr.matchExpr(x.X, y.X) &&
tr.matchExpr(x.Index, y.Index)
case *ast.SliceExpr:
y := y.(*ast.SliceExpr)
return tr.matchExpr(x.X, y.X) &&
tr.matchExpr(x.Low, y.Low) &&
tr.matchExpr(x.High, y.High) &&
tr.matchExpr(x.Max, y.Max) &&
x.Slice3 == y.Slice3
case *ast.TypeAssertExpr:
y := y.(*ast.TypeAssertExpr)
return tr.matchExpr(x.X, y.X) &&
tr.matchType(x.Type, y.Type)
case *ast.CallExpr:
y := y.(*ast.CallExpr)
match := tr.matchExpr // function call
if tr.info.Types[x.Fun].IsType() {
match = tr.matchType // type conversion
}
return x.Ellipsis.IsValid() == y.Ellipsis.IsValid() &&
match(x.Fun, y.Fun) &&
tr.matchExprs(x.Args, y.Args)
case *ast.StarExpr:
y := y.(*ast.StarExpr)
return tr.matchExpr(x.X, y.X)
case *ast.UnaryExpr:
y := y.(*ast.UnaryExpr)
return x.Op == y.Op &&
tr.matchExpr(x.X, y.X)
case *ast.BinaryExpr:
y := y.(*ast.BinaryExpr)
return x.Op == y.Op &&
tr.matchExpr(x.X, y.X) &&
tr.matchExpr(x.Y, y.Y)
case *ast.KeyValueExpr:
y := y.(*ast.KeyValueExpr)
return tr.matchExpr(x.Key, y.Key) &&
tr.matchExpr(x.Value, y.Value)
}
panic(fmt.Sprintf("unhandled AST node type: %T", x))
}
func (tr *Transformer) matchExprs(xx, yy []ast.Expr) bool {
if len(xx) != len(yy) {
return false
}
for i := range xx {
if !tr.matchExpr(xx[i], yy[i]) {
return false
}
}
return true
}
// matchType reports whether the two type ASTs denote identical types.
func (tr *Transformer) matchType(x, y ast.Expr) bool {
tx := tr.info.Types[x].Type
ty := tr.info.Types[y].Type
return types.Identical(tx, ty)
}
func (tr *Transformer) wildcardObj(x ast.Expr) (*types.Var, bool) {
if x, ok := x.(*ast.Ident); ok && x != nil && tr.allowWildcards {
if xobj, ok := tr.info.Uses[x].(*types.Var); ok && tr.wildcards[xobj] {
return xobj, true
}
}
return nil, false
}
func (tr *Transformer) matchSelectorExpr(x, y *ast.SelectorExpr) bool {
if xobj, ok := tr.wildcardObj(x.X); ok {
field := x.Sel.Name
yt := tr.info.TypeOf(y.X)
o, _, _ := types.LookupFieldOrMethod(yt, true, tr.currentPkg, field)
if o != nil {
tr.env[xobj.Name()] = y.X // record binding
return true
}
}
return tr.matchExpr(x.X, y.X)
}
func (tr *Transformer) matchWildcard(xobj *types.Var, y ast.Expr) bool {
name := xobj.Name()
if tr.verbose {
fmt.Fprintf(os.Stderr, "%s: wildcard %s -> %s?: ",
tr.fset.Position(y.Pos()), name, astString(tr.fset, y))
}
// Check that y is assignable to the declared type of the param.
yt := tr.info.TypeOf(y)
if yt == nil {
// y has no type.
// Perhaps it is an *ast.Ellipsis in [...]T{}, or
// an *ast.KeyValueExpr in T{k: v}.
// Clearly these pseudo-expressions cannot match a
// wildcard, but it would nice if we had a way to ignore
// the difference between T{v} and T{k:v} for structs.
return false
}
if !types.AssignableTo(yt, xobj.Type()) {
if tr.verbose {
fmt.Fprintf(os.Stderr, "%s not assignable to %s\n", yt, xobj.Type())
}
return false
}
// A wildcard matches any expression.
// If it appears multiple times in the pattern, it must match
// the same expression each time.
if old, ok := tr.env[name]; ok {
// found existing binding
tr.allowWildcards = false
r := tr.matchExpr(old, y)
if tr.verbose {
fmt.Fprintf(os.Stderr, "%t secondary match, primary was %s\n",
r, astString(tr.fset, old))
}
tr.allowWildcards = true
return r
}
if tr.verbose {
fmt.Fprintf(os.Stderr, "primary match\n")
}
tr.env[name] = y // record binding
return true
}
// -- utilities --------------------------------------------------------
func unparen(e ast.Expr) ast.Expr { return astutil.Unparen(e) }
// isRef returns the object referred to by this (possibly qualified)
// identifier, or nil if the node is not a referring identifier.
func isRef(n ast.Node, info *types.Info) types.Object {
switch n := n.(type) {
case *ast.Ident:
return info.Uses[n]
case *ast.SelectorExpr:
if _, ok := info.Selections[n]; !ok {
// qualified ident
return info.Uses[n.Sel]
}
}
return nil
}

View File

@ -2,8 +2,6 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build !go1.8
package rename
// This file defines the safety checks for each kind of renaming.

View File

@ -1,860 +0,0 @@
// Copyright 2014 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.
// +build go1.8
package rename
// This file defines the safety checks for each kind of renaming.
import (
"fmt"
"go/ast"
"go/token"
"go/types"
"golang.org/x/tools/go/loader"
"golang.org/x/tools/refactor/satisfy"
)
// errorf reports an error (e.g. conflict) and prevents file modification.
func (r *renamer) errorf(pos token.Pos, format string, args ...interface{}) {
r.hadConflicts = true
reportError(r.iprog.Fset.Position(pos), fmt.Sprintf(format, args...))
}
// check performs safety checks of the renaming of the 'from' object to r.to.
func (r *renamer) check(from types.Object) {
if r.objsToUpdate[from] {
return
}
r.objsToUpdate[from] = true
// NB: order of conditions is important.
if from_, ok := from.(*types.PkgName); ok {
r.checkInFileBlock(from_)
} else if from_, ok := from.(*types.Label); ok {
r.checkLabel(from_)
} else if isPackageLevel(from) {
r.checkInPackageBlock(from)
} else if v, ok := from.(*types.Var); ok && v.IsField() {
r.checkStructField(v)
} else if f, ok := from.(*types.Func); ok && recv(f) != nil {
r.checkMethod(f)
} else if isLocal(from) {
r.checkInLocalScope(from)
} else {
r.errorf(from.Pos(), "unexpected %s object %q (please report a bug)\n",
objectKind(from), from)
}
}
// checkInFileBlock performs safety checks for renames of objects in the file block,
// i.e. imported package names.
func (r *renamer) checkInFileBlock(from *types.PkgName) {
// Check import name is not "init".
if r.to == "init" {
r.errorf(from.Pos(), "%q is not a valid imported package name", r.to)
}
// Check for conflicts between file and package block.
if prev := from.Pkg().Scope().Lookup(r.to); prev != nil {
r.errorf(from.Pos(), "renaming this %s %q to %q would conflict",
objectKind(from), from.Name(), r.to)
r.errorf(prev.Pos(), "\twith this package member %s",
objectKind(prev))
return // since checkInPackageBlock would report redundant errors
}
// Check for conflicts in lexical scope.
r.checkInLexicalScope(from, r.packages[from.Pkg()])
// Finally, modify ImportSpec syntax to add or remove the Name as needed.
info, path, _ := r.iprog.PathEnclosingInterval(from.Pos(), from.Pos())
if from.Imported().Name() == r.to {
// ImportSpec.Name not needed
path[1].(*ast.ImportSpec).Name = nil
} else {
// ImportSpec.Name needed
if spec := path[1].(*ast.ImportSpec); spec.Name == nil {
spec.Name = &ast.Ident{NamePos: spec.Path.Pos(), Name: r.to}
info.Defs[spec.Name] = from
}
}
}
// checkInPackageBlock performs safety checks for renames of
// func/var/const/type objects in the package block.
func (r *renamer) checkInPackageBlock(from types.Object) {
// Check that there are no references to the name from another
// package if the renaming would make it unexported.
if ast.IsExported(from.Name()) && !ast.IsExported(r.to) {
for pkg, info := range r.packages {
if pkg == from.Pkg() {
continue
}
if id := someUse(info, from); id != nil &&
!r.checkExport(id, pkg, from) {
break
}
}
}
info := r.packages[from.Pkg()]
// 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.
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",
kind, r.to)
}
}
// Check for conflicts between package block and all file blocks.
for _, f := range info.Files {
fileScope := info.Info.Scopes[f]
b, prev := fileScope.LookupParent(r.to, token.NoPos)
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",
objectKind(prev))
return // since checkInPackageBlock would report redundant errors
}
}
// Check for conflicts in lexical scope.
if from.Exported() {
for _, info := range r.packages {
r.checkInLexicalScope(from, info)
}
} else {
r.checkInLexicalScope(from, info)
}
}
func (r *renamer) checkInLocalScope(from types.Object) {
info := r.packages[from.Pkg()]
// Is this object an implicit local var for a type switch?
// Each case has its own var, whose position is the decl of y,
// but Ident in that decl does not appear in the Uses map.
//
// switch y := x.(type) { // Defs[Ident(y)] is undefined
// case int: print(y) // Implicits[CaseClause(int)] = Var(y_int)
// case string: print(y) // Implicits[CaseClause(string)] = Var(y_string)
// }
//
var isCaseVar bool
for syntax, obj := range info.Implicits {
if _, ok := syntax.(*ast.CaseClause); ok && obj.Pos() == from.Pos() {
isCaseVar = true
r.check(obj)
}
}
r.checkInLexicalScope(from, info)
// Finally, if this was a type switch, change the variable y.
if isCaseVar {
_, path, _ := r.iprog.PathEnclosingInterval(from.Pos(), from.Pos())
path[0].(*ast.Ident).Name = r.to // path is [Ident AssignStmt TypeSwitchStmt...]
}
}
// checkInLexicalScope performs safety checks that a renaming does not
// change the lexical reference structure of the specified package.
//
// For objects in lexical scope, there are three kinds of conflicts:
// same-, sub-, and super-block conflicts. We will illustrate all three
// using this example:
//
// var x int
// var z int
//
// func f(y int) {
// print(x)
// print(y)
// }
//
// Renaming x to z encounters a SAME-BLOCK CONFLICT, because an object
// with the new name already exists, defined in the same lexical block
// as the old object.
//
// Renaming x to y encounters a SUB-BLOCK CONFLICT, because there exists
// a reference to x from within (what would become) a hole in its scope.
// The definition of y in an (inner) sub-block would cast a shadow in
// the scope of the renamed variable.
//
// Renaming y to x encounters a SUPER-BLOCK CONFLICT. This is the
// converse situation: there is an existing definition of the new name
// (x) in an (enclosing) super-block, and the renaming would create a
// hole in its scope, within which there exist references to it. The
// new name casts a shadow in scope of the existing definition of x in
// the super-block.
//
// Removing the old name (and all references to it) is always safe, and
// requires no checks.
//
func (r *renamer) checkInLexicalScope(from types.Object, info *loader.PackageInfo) {
b := from.Parent() // the block defining the 'from' object
if b != nil {
toBlock, to := b.LookupParent(r.to, from.Parent().End())
if toBlock == b {
// same-block conflict
r.errorf(from.Pos(), "renaming this %s %q to %q",
objectKind(from), from.Name(), r.to)
r.errorf(to.Pos(), "\tconflicts with %s in same block",
objectKind(to))
return
} else if toBlock != nil {
// Check for super-block conflict.
// The name r.to is defined in a superblock.
// Is that name referenced from within this block?
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(id.Pos(), "\twould shadow this reference")
r.errorf(to.Pos(), "\tto the %s declared here",
objectKind(to))
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?
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())
// 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 deeper(toBlock, fromBlock) {
r.errorf(from.Pos(), "renaming this %s %q to %q",
objectKind(from), from.Name(), r.to)
r.errorf(id.Pos(), "\twould cause this reference to become shadowed")
r.errorf(to.Pos(), "\tby this intervening %s definition",
objectKind(to))
return false // stop
}
}
return true
})
// Renaming a type that is used as an embedded field
// requires renaming the field too. e.g.
// type T int // if we rename this to U..
// var s struct {T}
// print(s.T) // ...this must change too
if _, ok := from.(*types.TypeName); ok {
for id, obj := range info.Uses {
if obj == from {
if field := info.Defs[id]; field != nil {
r.check(field)
}
}
}
}
}
// 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.)
if prev := label.Parent().Lookup(r.to); prev != nil {
r.errorf(label.Pos(), "renaming this label %q to %q", label.Name(), prev.Name())
r.errorf(prev.Pos(), "\twould conflict with this one")
}
}
// checkStructField checks that the field renaming will not cause
// conflicts at its declaration, or ambiguity or changes to any selection.
func (r *renamer) checkStructField(from *types.Var) {
// Check that the struct declaration is free of field conflicts,
// and field/method conflicts.
// go/types offers no easy way to get from a field (or interface
// method) to its declaring struct (or interface), so we must
// ascend the AST.
info, path, _ := r.iprog.PathEnclosingInterval(from.Pos(), from.Pos())
// path matches this pattern:
// [Ident SelectorExpr? StarExpr? Field FieldList StructType ParenExpr* ... File]
// Ascend to FieldList.
var i int
for {
if _, ok := path[i].(*ast.FieldList); ok {
break
}
i++
}
i++
tStruct := path[i].(*ast.StructType)
i++
// Ascend past parens (unlikely).
for {
_, ok := path[i].(*ast.ParenExpr)
if !ok {
break
}
i++
}
if spec, ok := path[i].(*ast.TypeSpec); ok {
// This struct is also a named type.
// We must check for direct (non-promoted) field/field
// and method/field conflicts.
named := info.Defs[spec.Name].Type()
prev, indices, _ := types.LookupFieldOrMethod(named, true, info.Pkg, r.to)
if len(indices) == 1 {
r.errorf(from.Pos(), "renaming this field %q to %q",
from.Name(), r.to)
r.errorf(prev.Pos(), "\twould conflict with this %s",
objectKind(prev))
return // skip checkSelections to avoid redundant errors
}
} else {
// This struct is not a named type.
// We need only check for direct (non-promoted) field/field conflicts.
T := info.Types[tStruct].Type.Underlying().(*types.Struct)
for i := 0; i < T.NumFields(); i++ {
if prev := T.Field(i); prev.Name() == r.to {
r.errorf(from.Pos(), "renaming this field %q to %q",
from.Name(), r.to)
r.errorf(prev.Pos(), "\twould conflict with this field")
return // skip checkSelections to avoid redundant errors
}
}
}
// Renaming an anonymous field requires renaming the type too. e.g.
// print(s.T) // if we rename T to U,
// type T int // this and
// var s struct {T} // this must change too.
if from.Anonymous() {
if named, ok := from.Type().(*types.Named); ok {
r.check(named.Obj())
} else if named, ok := deref(from.Type()).(*types.Named); ok {
r.check(named.Obj())
}
}
// Check integrity of existing (field and method) selections.
r.checkSelections(from)
}
// checkSelection checks that all uses and selections that resolve to
// the specified object would continue to do so after the renaming.
func (r *renamer) checkSelections(from types.Object) {
for pkg, info := range r.packages {
if id := someUse(info, from); id != nil {
if !r.checkExport(id, pkg, from) {
return
}
}
for syntax, sel := range info.Selections {
// There may be extant selections of only the old
// name or only the new name, so we must check both.
// (If neither, the renaming is sound.)
//
// In both cases, we wish to compare the lengths
// of the implicit field path (Selection.Index)
// to see if the renaming would change it.
//
// If a selection that resolves to 'from', when renamed,
// would yield a path of the same or shorter length,
// this indicates ambiguity or a changed referent,
// analogous to same- or sub-block lexical conflict.
//
// If a selection using the name 'to' would
// yield a path of the same or shorter length,
// this indicates ambiguity or shadowing,
// analogous to same- or super-block lexical conflict.
// TODO(adonovan): fix: derive from Types[syntax.X].Mode
// TODO(adonovan): test with pointer, value, addressable value.
isAddressable := true
if sel.Obj() == from {
if obj, indices, _ := types.LookupFieldOrMethod(sel.Recv(), isAddressable, from.Pkg(), r.to); obj != nil {
// Renaming this existing selection of
// 'from' may block access to an existing
// type member named 'to'.
delta := len(indices) - len(sel.Index())
if delta > 0 {
continue // no ambiguity
}
r.selectionConflict(from, delta, syntax, obj)
return
}
} else if sel.Obj().Name() == r.to {
if obj, indices, _ := types.LookupFieldOrMethod(sel.Recv(), isAddressable, from.Pkg(), from.Name()); obj == from {
// Renaming 'from' may cause this existing
// selection of the name 'to' to change
// its meaning.
delta := len(indices) - len(sel.Index())
if delta > 0 {
continue // no ambiguity
}
r.selectionConflict(from, -delta, syntax, sel.Obj())
return
}
}
}
}
}
func (r *renamer) selectionConflict(from types.Object, delta int, syntax *ast.SelectorExpr, obj types.Object) {
r.errorf(from.Pos(), "renaming this %s %q to %q",
objectKind(from), from.Name(), r.to)
switch {
case delta < 0:
// analogous to sub-block conflict
r.errorf(syntax.Sel.Pos(),
"\twould change the referent of this selection")
r.errorf(obj.Pos(), "\tof this %s", objectKind(obj))
case delta == 0:
// analogous to same-block conflict
r.errorf(syntax.Sel.Pos(),
"\twould make this reference ambiguous")
r.errorf(obj.Pos(), "\twith this %s", objectKind(obj))
case delta > 0:
// analogous to super-block conflict
r.errorf(syntax.Sel.Pos(),
"\twould shadow this selection")
r.errorf(obj.Pos(), "\tof the %s declared here",
objectKind(obj))
}
}
// checkMethod performs safety checks for renaming a method.
// There are three hazards:
// - declaration conflicts
// - selection ambiguity/changes
// - entailed renamings of assignable concrete/interface types.
// We reject renamings initiated at concrete methods if it would
// change the assignability relation. For renamings of abstract
// methods, we rename all methods transitively coupled to it via
// assignability.
func (r *renamer) checkMethod(from *types.Func) {
// e.g. error.Error
if from.Pkg() == nil {
r.errorf(from.Pos(), "you cannot rename built-in method %s", from)
return
}
// ASSIGNABILITY: We reject renamings of concrete methods that
// would break a 'satisfy' constraint; but renamings of abstract
// methods are allowed to proceed, and we rename affected
// concrete and abstract methods as necessary. It is the
// initial method that determines the policy.
// Check for conflict at point of declaration.
// Check to ensure preservation of assignability requirements.
R := recv(from).Type()
if isInterface(R) {
// Abstract method
// declaration
prev, _, _ := types.LookupFieldOrMethod(R, false, from.Pkg(), r.to)
if prev != nil {
r.errorf(from.Pos(), "renaming this interface method %q to %q",
from.Name(), r.to)
r.errorf(prev.Pos(), "\twould conflict with this method")
return
}
// Check all interfaces that embed this one for
// declaration conflicts too.
for _, info := range r.packages {
// Start with named interface types (better errors)
for _, obj := range info.Defs {
if obj, ok := obj.(*types.TypeName); ok && isInterface(obj.Type()) {
f, _, _ := types.LookupFieldOrMethod(
obj.Type(), false, from.Pkg(), from.Name())
if f == nil {
continue
}
t, _, _ := types.LookupFieldOrMethod(
obj.Type(), false, from.Pkg(), r.to)
if t == nil {
continue
}
r.errorf(from.Pos(), "renaming this interface method %q to %q",
from.Name(), r.to)
r.errorf(t.Pos(), "\twould conflict with this method")
r.errorf(obj.Pos(), "\tin named interface type %q", obj.Name())
}
}
// Now look at all literal interface types (includes named ones again).
for e, tv := range info.Types {
if e, ok := e.(*ast.InterfaceType); ok {
_ = e
_ = tv.Type.(*types.Interface)
// TODO(adonovan): implement same check as above.
}
}
}
// assignability
//
// Find the set of concrete or abstract methods directly
// coupled to abstract method 'from' by some
// satisfy.Constraint, and rename them too.
for key := range r.satisfy() {
// key = (lhs, rhs) where lhs is always an interface.
lsel := r.msets.MethodSet(key.LHS).Lookup(from.Pkg(), from.Name())
if lsel == nil {
continue
}
rmethods := r.msets.MethodSet(key.RHS)
rsel := rmethods.Lookup(from.Pkg(), from.Name())
if rsel == nil {
continue
}
// If both sides have a method of this name,
// and one of them is m, the other must be coupled.
var coupled *types.Func
switch from {
case lsel.Obj():
coupled = rsel.Obj().(*types.Func)
case rsel.Obj():
coupled = lsel.Obj().(*types.Func)
default:
continue
}
// We must treat concrete-to-interface
// constraints like an implicit selection C.f of
// each interface method I.f, and check that the
// renaming leaves the selection unchanged and
// unambiguous.
//
// Fun fact: the implicit selection of C.f
// type I interface{f()}
// type C struct{I}
// func (C) g()
// var _ I = C{} // here
// yields abstract method I.f. This can make error
// messages less than obvious.
//
if !isInterface(key.RHS) {
// The logic below was derived from checkSelections.
rtosel := rmethods.Lookup(from.Pkg(), r.to)
if rtosel != nil {
rto := rtosel.Obj().(*types.Func)
delta := len(rsel.Index()) - len(rtosel.Index())
if delta < 0 {
continue // no ambiguity
}
// TODO(adonovan): record the constraint's position.
keyPos := token.NoPos
r.errorf(from.Pos(), "renaming this method %q to %q",
from.Name(), r.to)
if delta == 0 {
// analogous to same-block conflict
r.errorf(keyPos, "\twould make the %s method of %s invoked via interface %s ambiguous",
r.to, key.RHS, key.LHS)
r.errorf(rto.Pos(), "\twith (%s).%s",
recv(rto).Type(), r.to)
} else {
// analogous to super-block conflict
r.errorf(keyPos, "\twould change the %s method of %s invoked via interface %s",
r.to, key.RHS, key.LHS)
r.errorf(coupled.Pos(), "\tfrom (%s).%s",
recv(coupled).Type(), r.to)
r.errorf(rto.Pos(), "\tto (%s).%s",
recv(rto).Type(), r.to)
}
return // one error is enough
}
}
if !r.changeMethods {
// This should be unreachable.
r.errorf(from.Pos(), "internal error: during renaming of abstract method %s", from)
r.errorf(coupled.Pos(), "\tchangedMethods=false, coupled method=%s", coupled)
r.errorf(from.Pos(), "\tPlease file a bug report")
return
}
// Rename the coupled method to preserve assignability.
r.check(coupled)
}
} else {
// Concrete method
// declaration
prev, indices, _ := types.LookupFieldOrMethod(R, true, from.Pkg(), r.to)
if prev != nil && len(indices) == 1 {
r.errorf(from.Pos(), "renaming this method %q to %q",
from.Name(), r.to)
r.errorf(prev.Pos(), "\twould conflict with this %s",
objectKind(prev))
return
}
// assignability
//
// Find the set of abstract methods coupled to concrete
// method 'from' by some satisfy.Constraint, and rename
// them too.
//
// Coupling may be indirect, e.g. I.f <-> C.f via type D.
//
// type I interface {f()}
// type C int
// type (C) f()
// type D struct{C}
// var _ I = D{}
//
for key := range r.satisfy() {
// key = (lhs, rhs) where lhs is always an interface.
if isInterface(key.RHS) {
continue
}
rsel := r.msets.MethodSet(key.RHS).Lookup(from.Pkg(), from.Name())
if rsel == nil || rsel.Obj() != from {
continue // rhs does not have the method
}
lsel := r.msets.MethodSet(key.LHS).Lookup(from.Pkg(), from.Name())
if lsel == nil {
continue
}
imeth := lsel.Obj().(*types.Func)
// imeth is the abstract method (e.g. I.f)
// and key.RHS is the concrete coupling type (e.g. D).
if !r.changeMethods {
r.errorf(from.Pos(), "renaming this method %q to %q",
from.Name(), r.to)
var pos token.Pos
var iface string
I := recv(imeth).Type()
if named, ok := I.(*types.Named); ok {
pos = named.Obj().Pos()
iface = "interface " + named.Obj().Name()
} else {
pos = from.Pos()
iface = I.String()
}
r.errorf(pos, "\twould make %s no longer assignable to %s",
key.RHS, iface)
r.errorf(imeth.Pos(), "\t(rename %s.%s if you intend to change both types)",
I, from.Name())
return // one error is enough
}
// Rename the coupled interface method to preserve assignability.
r.check(imeth)
}
}
// Check integrity of existing (field and method) selections.
// We skip this if there were errors above, to avoid redundant errors.
r.checkSelections(from)
}
func (r *renamer) checkExport(id *ast.Ident, pkg *types.Package, from types.Object) bool {
// Reject cross-package references if r.to is unexported.
// (Such references may be qualified identifiers or field/method
// selections.)
if !ast.IsExported(r.to) && pkg != from.Pkg() {
r.errorf(from.Pos(),
"renaming this %s %q to %q would make it unexported",
objectKind(from), from.Name(), r.to)
r.errorf(id.Pos(), "\tbreaking references from packages such as %q",
pkg.Path())
return false
}
return true
}
// satisfy returns the set of interface satisfaction constraints.
func (r *renamer) satisfy() map[satisfy.Constraint]bool {
if r.satisfyConstraints == nil {
// Compute on demand: it's expensive.
var f satisfy.Finder
for _, info := range r.packages {
f.Find(&info.Info, info.Files)
}
r.satisfyConstraints = f.Result
}
return r.satisfyConstraints
}
// -- helpers ----------------------------------------------------------
// recv returns the method's receiver.
func recv(meth *types.Func) *types.Var {
return meth.Type().(*types.Signature).Recv()
}
// someUse returns an arbitrary use of obj within info.
func someUse(info *loader.PackageInfo, obj types.Object) *ast.Ident {
for id, o := range info.Uses {
if o == obj {
return id
}
}
return nil
}
// -- Plundered from golang.org/x/tools/go/ssa -----------------
func isInterface(T types.Type) bool { return types.IsInterface(T) }
func deref(typ types.Type) types.Type {
if p, _ := typ.(*types.Pointer); p != nil {
return p.Elem()
}
return typ
}

View File

@ -2,8 +2,6 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build !go1.8
package rename
import (

View File

@ -1,107 +0,0 @@
// Copyright 2014 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.
// +build go1.8
package rename
import (
"go/ast"
"go/token"
"go/types"
"os"
"path/filepath"
"reflect"
"runtime"
"strings"
"unicode"
"golang.org/x/tools/go/ast/astutil"
)
func objectKind(obj types.Object) string {
switch obj := obj.(type) {
case *types.PkgName:
return "imported package name"
case *types.TypeName:
return "type"
case *types.Var:
if obj.IsField() {
return "field"
}
case *types.Func:
if obj.Type().(*types.Signature).Recv() != nil {
return "method"
}
}
// label, func, var, const
return strings.ToLower(strings.TrimPrefix(reflect.TypeOf(obj).String(), "*types."))
}
func typeKind(T types.Type) string {
return strings.ToLower(strings.TrimPrefix(reflect.TypeOf(T.Underlying()).String(), "*types."))
}
// NB: for renamings, blank is not considered valid.
func isValidIdentifier(id string) bool {
if id == "" || id == "_" {
return false
}
for i, r := range id {
if !isLetter(r) && (i == 0 || !isDigit(r)) {
return false
}
}
return token.Lookup(id) == token.IDENT
}
// isLocal reports whether obj is local to some function.
// Precondition: not a struct field or interface method.
func isLocal(obj types.Object) bool {
// [... 5=stmt 4=func 3=file 2=pkg 1=universe]
var depth int
for scope := obj.Parent(); scope != nil; scope = scope.Parent() {
depth++
}
return depth >= 4
}
func isPackageLevel(obj types.Object) bool {
return obj.Pkg().Scope().Lookup(obj.Name()) == obj
}
// -- Plundered from go/scanner: ---------------------------------------
func isLetter(ch rune) bool {
return 'a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || ch == '_' || ch >= 0x80 && unicode.IsLetter(ch)
}
func isDigit(ch rune) bool {
return '0' <= ch && ch <= '9' || ch >= 0x80 && unicode.IsDigit(ch)
}
// -- Plundered from golang.org/x/tools/cmd/guru -----------------
// sameFile returns true if x and y have the same basename and denote
// the same file.
//
func sameFile(x, y string) bool {
if runtime.GOOS == "windows" {
x = filepath.ToSlash(x)
y = filepath.ToSlash(y)
}
if x == y {
return true
}
if filepath.Base(x) == filepath.Base(y) { // (optimisation)
if xi, err := os.Stat(x); err == nil {
if yi, err := os.Stat(y); err == nil {
return os.SameFile(xi, yi)
}
}
}
return false
}
func unparen(e ast.Expr) ast.Expr { return astutil.Unparen(e) }

View File

@ -2,8 +2,6 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build !go1.8
// Package satisfy inspects the type-checked ASTs of Go packages and
// reports the set of discovered type constraints of the form (lhs, rhs
// Type) where lhs is a non-trivial interface, rhs satisfies this

View File

@ -1,707 +0,0 @@
// Copyright 2014 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.
// +build go1.8
// Package satisfy inspects the type-checked ASTs of Go packages and
// reports the set of discovered type constraints of the form (lhs, rhs
// Type) where lhs is a non-trivial interface, rhs satisfies this
// interface, and this fact is necessary for the package to be
// well-typed.
//
// THIS PACKAGE IS EXPERIMENTAL AND MAY CHANGE AT ANY TIME.
//
// It is provided only for the gorename tool. Ideally this
// functionality will become part of the type-checker in due course,
// since it is computing it anyway, and it is robust for ill-typed
// inputs, which this package is not.
//
package satisfy // import "golang.org/x/tools/refactor/satisfy"
// NOTES:
//
// We don't care about numeric conversions, so we don't descend into
// types or constant expressions. This is unsound because
// constant expressions can contain arbitrary statements, e.g.
// const x = len([1]func(){func() {
// ...
// }})
//
// TODO(adonovan): make this robust against ill-typed input.
// Or move it into the type-checker.
//
// Assignability conversions are possible in the following places:
// - in assignments y = x, y := x, var y = x.
// - from call argument types to formal parameter types
// - in append and delete calls
// - from return operands to result parameter types
// - in composite literal T{k:v}, from k and v to T's field/element/key type
// - in map[key] from key to the map's key type
// - in comparisons x==y and switch x { case y: }.
// - in explicit conversions T(x)
// - in sends ch <- x, from x to the channel element type
// - in type assertions x.(T) and switch x.(type) { case T: }
//
// The results of this pass provide information equivalent to the
// ssa.MakeInterface and ssa.ChangeInterface instructions.
import (
"fmt"
"go/ast"
"go/token"
"go/types"
"golang.org/x/tools/go/ast/astutil"
"golang.org/x/tools/go/types/typeutil"
)
// A Constraint records the fact that the RHS type does and must
// satisify the LHS type, which is an interface.
// The names are suggestive of an assignment statement LHS = RHS.
type Constraint struct {
LHS, RHS types.Type
}
// A Finder inspects the type-checked ASTs of Go packages and
// accumulates the set of type constraints (x, y) such that x is
// assignable to y, y is an interface, and both x and y have methods.
//
// In other words, it returns the subset of the "implements" relation
// that is checked during compilation of a package. Refactoring tools
// will need to preserve at least this part of the relation to ensure
// continued compilation.
//
type Finder struct {
Result map[Constraint]bool
msetcache typeutil.MethodSetCache
// per-Find state
info *types.Info
sig *types.Signature
}
// Find inspects a single package, populating Result with its pairs of
// constrained types.
//
// The result is non-canonical and thus may contain duplicates (but this
// tends to preserves names of interface types better).
//
// The package must be free of type errors, and
// info.{Defs,Uses,Selections,Types} must have been populated by the
// type-checker.
//
func (f *Finder) Find(info *types.Info, files []*ast.File) {
if f.Result == nil {
f.Result = make(map[Constraint]bool)
}
f.info = info
for _, file := range files {
for _, d := range file.Decls {
switch d := d.(type) {
case *ast.GenDecl:
if d.Tok == token.VAR { // ignore consts
for _, spec := range d.Specs {
f.valueSpec(spec.(*ast.ValueSpec))
}
}
case *ast.FuncDecl:
if d.Body != nil {
f.sig = f.info.Defs[d.Name].Type().(*types.Signature)
f.stmt(d.Body)
f.sig = nil
}
}
}
}
f.info = nil
}
var (
tInvalid = types.Typ[types.Invalid]
tUntypedBool = types.Typ[types.UntypedBool]
tUntypedNil = types.Typ[types.UntypedNil]
)
// exprN visits an expression in a multi-value context.
func (f *Finder) exprN(e ast.Expr) types.Type {
typ := f.info.Types[e].Type.(*types.Tuple)
switch e := e.(type) {
case *ast.ParenExpr:
return f.exprN(e.X)
case *ast.CallExpr:
// x, err := f(args)
sig := f.expr(e.Fun).Underlying().(*types.Signature)
f.call(sig, e.Args)
case *ast.IndexExpr:
// y, ok := x[i]
x := f.expr(e.X)
f.assign(f.expr(e.Index), x.Underlying().(*types.Map).Key())
case *ast.TypeAssertExpr:
// y, ok := x.(T)
f.typeAssert(f.expr(e.X), typ.At(0).Type())
case *ast.UnaryExpr: // must be receive <-
// y, ok := <-x
f.expr(e.X)
default:
panic(e)
}
return typ
}
func (f *Finder) call(sig *types.Signature, args []ast.Expr) {
if len(args) == 0 {
return
}
// Ellipsis call? e.g. f(x, y, z...)
if _, ok := args[len(args)-1].(*ast.Ellipsis); ok {
for i, arg := range args {
// The final arg is a slice, and so is the final param.
f.assign(sig.Params().At(i).Type(), f.expr(arg))
}
return
}
var argtypes []types.Type
// Gather the effective actual parameter types.
if tuple, ok := f.info.Types[args[0]].Type.(*types.Tuple); ok {
// f(g()) call where g has multiple results?
f.expr(args[0])
// unpack the tuple
for i := 0; i < tuple.Len(); i++ {
argtypes = append(argtypes, tuple.At(i).Type())
}
} else {
for _, arg := range args {
argtypes = append(argtypes, f.expr(arg))
}
}
// Assign the actuals to the formals.
if !sig.Variadic() {
for i, argtype := range argtypes {
f.assign(sig.Params().At(i).Type(), argtype)
}
} else {
// The first n-1 parameters are assigned normally.
nnormals := sig.Params().Len() - 1
for i, argtype := range argtypes[:nnormals] {
f.assign(sig.Params().At(i).Type(), argtype)
}
// Remaining args are assigned to elements of varargs slice.
tElem := sig.Params().At(nnormals).Type().(*types.Slice).Elem()
for i := nnormals; i < len(argtypes); i++ {
f.assign(tElem, argtypes[i])
}
}
}
func (f *Finder) builtin(obj *types.Builtin, sig *types.Signature, args []ast.Expr, T types.Type) types.Type {
switch obj.Name() {
case "make", "new":
// skip the type operand
for _, arg := range args[1:] {
f.expr(arg)
}
case "append":
s := f.expr(args[0])
if _, ok := args[len(args)-1].(*ast.Ellipsis); ok && len(args) == 2 {
// append(x, y...) including append([]byte, "foo"...)
f.expr(args[1])
} else {
// append(x, y, z)
tElem := s.Underlying().(*types.Slice).Elem()
for _, arg := range args[1:] {
f.assign(tElem, f.expr(arg))
}
}
case "delete":
m := f.expr(args[0])
k := f.expr(args[1])
f.assign(m.Underlying().(*types.Map).Key(), k)
default:
// ordinary call
f.call(sig, args)
}
return T
}
func (f *Finder) extract(tuple types.Type, i int) types.Type {
if tuple, ok := tuple.(*types.Tuple); ok && i < tuple.Len() {
return tuple.At(i).Type()
}
return tInvalid
}
func (f *Finder) valueSpec(spec *ast.ValueSpec) {
var T types.Type
if spec.Type != nil {
T = f.info.Types[spec.Type].Type
}
switch len(spec.Values) {
case len(spec.Names): // e.g. var x, y = f(), g()
for _, value := range spec.Values {
v := f.expr(value)
if T != nil {
f.assign(T, v)
}
}
case 1: // e.g. var x, y = f()
tuple := f.exprN(spec.Values[0])
for i := range spec.Names {
if T != nil {
f.assign(T, f.extract(tuple, i))
}
}
}
}
// assign records pairs of distinct types that are related by
// assignability, where the left-hand side is an interface and both
// sides have methods.
//
// It should be called for all assignability checks, type assertions,
// explicit conversions and comparisons between two types, unless the
// types are uninteresting (e.g. lhs is a concrete type, or the empty
// interface; rhs has no methods).
//
func (f *Finder) assign(lhs, rhs types.Type) {
if types.Identical(lhs, rhs) {
return
}
if !isInterface(lhs) {
return
}
if f.msetcache.MethodSet(lhs).Len() == 0 {
return
}
if f.msetcache.MethodSet(rhs).Len() == 0 {
return
}
// record the pair
f.Result[Constraint{lhs, rhs}] = true
}
// typeAssert must be called for each type assertion x.(T) where x has
// interface type I.
func (f *Finder) typeAssert(I, T types.Type) {
// Type assertions are slightly subtle, because they are allowed
// to be "impossible", e.g.
//
// var x interface{f()}
// _ = x.(interface{f()int}) // legal
//
// (In hindsight, the language spec should probably not have
// allowed this, but it's too late to fix now.)
//
// This means that a type assert from I to T isn't exactly a
// constraint that T is assignable to I, but for a refactoring
// tool it is a conditional constraint that, if T is assignable
// to I before a refactoring, it should remain so after.
if types.AssignableTo(T, I) {
f.assign(I, T)
}
}
// compare must be called for each comparison x==y.
func (f *Finder) compare(x, y types.Type) {
if types.AssignableTo(x, y) {
f.assign(y, x)
} else if types.AssignableTo(y, x) {
f.assign(x, y)
}
}
// expr visits a true expression (not a type or defining ident)
// and returns its type.
func (f *Finder) expr(e ast.Expr) types.Type {
tv := f.info.Types[e]
if tv.Value != nil {
return tv.Type // prune the descent for constants
}
// tv.Type may be nil for an ast.Ident.
switch e := e.(type) {
case *ast.BadExpr, *ast.BasicLit:
// no-op
case *ast.Ident:
// (referring idents only)
if obj, ok := f.info.Uses[e]; ok {
return obj.Type()
}
if e.Name == "_" { // e.g. "for _ = range x"
return tInvalid
}
panic("undefined ident: " + e.Name)
case *ast.Ellipsis:
if e.Elt != nil {
f.expr(e.Elt)
}
case *ast.FuncLit:
saved := f.sig
f.sig = tv.Type.(*types.Signature)
f.stmt(e.Body)
f.sig = saved
case *ast.CompositeLit:
switch T := deref(tv.Type).Underlying().(type) {
case *types.Struct:
for i, elem := range e.Elts {
if kv, ok := elem.(*ast.KeyValueExpr); ok {
f.assign(f.info.Uses[kv.Key.(*ast.Ident)].Type(), f.expr(kv.Value))
} else {
f.assign(T.Field(i).Type(), f.expr(elem))
}
}
case *types.Map:
for _, elem := range e.Elts {
elem := elem.(*ast.KeyValueExpr)
f.assign(T.Key(), f.expr(elem.Key))
f.assign(T.Elem(), f.expr(elem.Value))
}
case *types.Array, *types.Slice:
tElem := T.(interface {
Elem() types.Type
}).Elem()
for _, elem := range e.Elts {
if kv, ok := elem.(*ast.KeyValueExpr); ok {
// ignore the key
f.assign(tElem, f.expr(kv.Value))
} else {
f.assign(tElem, f.expr(elem))
}
}
default:
panic("unexpected composite literal type: " + tv.Type.String())
}
case *ast.ParenExpr:
f.expr(e.X)
case *ast.SelectorExpr:
if _, ok := f.info.Selections[e]; ok {
f.expr(e.X) // selection
} else {
return f.info.Uses[e.Sel].Type() // qualified identifier
}
case *ast.IndexExpr:
x := f.expr(e.X)
i := f.expr(e.Index)
if ux, ok := x.Underlying().(*types.Map); ok {
f.assign(ux.Key(), i)
}
case *ast.SliceExpr:
f.expr(e.X)
if e.Low != nil {
f.expr(e.Low)
}
if e.High != nil {
f.expr(e.High)
}
if e.Max != nil {
f.expr(e.Max)
}
case *ast.TypeAssertExpr:
x := f.expr(e.X)
f.typeAssert(x, f.info.Types[e.Type].Type)
case *ast.CallExpr:
if tvFun := f.info.Types[e.Fun]; tvFun.IsType() {
// conversion
arg0 := f.expr(e.Args[0])
f.assign(tvFun.Type, arg0)
} else {
// function call
if id, ok := unparen(e.Fun).(*ast.Ident); ok {
if obj, ok := f.info.Uses[id].(*types.Builtin); ok {
sig := f.info.Types[id].Type.(*types.Signature)
return f.builtin(obj, sig, e.Args, tv.Type)
}
}
// ordinary call
f.call(f.expr(e.Fun).Underlying().(*types.Signature), e.Args)
}
case *ast.StarExpr:
f.expr(e.X)
case *ast.UnaryExpr:
f.expr(e.X)
case *ast.BinaryExpr:
x := f.expr(e.X)
y := f.expr(e.Y)
if e.Op == token.EQL || e.Op == token.NEQ {
f.compare(x, y)
}
case *ast.KeyValueExpr:
f.expr(e.Key)
f.expr(e.Value)
case *ast.ArrayType,
*ast.StructType,
*ast.FuncType,
*ast.InterfaceType,
*ast.MapType,
*ast.ChanType:
panic(e)
}
if tv.Type == nil {
panic(fmt.Sprintf("no type for %T", e))
}
return tv.Type
}
func (f *Finder) stmt(s ast.Stmt) {
switch s := s.(type) {
case *ast.BadStmt,
*ast.EmptyStmt,
*ast.BranchStmt:
// no-op
case *ast.DeclStmt:
d := s.Decl.(*ast.GenDecl)
if d.Tok == token.VAR { // ignore consts
for _, spec := range d.Specs {
f.valueSpec(spec.(*ast.ValueSpec))
}
}
case *ast.LabeledStmt:
f.stmt(s.Stmt)
case *ast.ExprStmt:
f.expr(s.X)
case *ast.SendStmt:
ch := f.expr(s.Chan)
val := f.expr(s.Value)
f.assign(ch.Underlying().(*types.Chan).Elem(), val)
case *ast.IncDecStmt:
f.expr(s.X)
case *ast.AssignStmt:
switch s.Tok {
case token.ASSIGN, token.DEFINE:
// y := x or y = x
var rhsTuple types.Type
if len(s.Lhs) != len(s.Rhs) {
rhsTuple = f.exprN(s.Rhs[0])
}
for i := range s.Lhs {
var lhs, rhs types.Type
if rhsTuple == nil {
rhs = f.expr(s.Rhs[i]) // 1:1 assignment
} else {
rhs = f.extract(rhsTuple, i) // n:1 assignment
}
if id, ok := s.Lhs[i].(*ast.Ident); ok {
if id.Name != "_" {
if obj, ok := f.info.Defs[id]; ok {
lhs = obj.Type() // definition
}
}
}
if lhs == nil {
lhs = f.expr(s.Lhs[i]) // assignment
}
f.assign(lhs, rhs)
}
default:
// y op= x
f.expr(s.Lhs[0])
f.expr(s.Rhs[0])
}
case *ast.GoStmt:
f.expr(s.Call)
case *ast.DeferStmt:
f.expr(s.Call)
case *ast.ReturnStmt:
formals := f.sig.Results()
switch len(s.Results) {
case formals.Len(): // 1:1
for i, result := range s.Results {
f.assign(formals.At(i).Type(), f.expr(result))
}
case 1: // n:1
tuple := f.exprN(s.Results[0])
for i := 0; i < formals.Len(); i++ {
f.assign(formals.At(i).Type(), f.extract(tuple, i))
}
}
case *ast.SelectStmt:
f.stmt(s.Body)
case *ast.BlockStmt:
for _, s := range s.List {
f.stmt(s)
}
case *ast.IfStmt:
if s.Init != nil {
f.stmt(s.Init)
}
f.expr(s.Cond)
f.stmt(s.Body)
if s.Else != nil {
f.stmt(s.Else)
}
case *ast.SwitchStmt:
if s.Init != nil {
f.stmt(s.Init)
}
var tag types.Type = tUntypedBool
if s.Tag != nil {
tag = f.expr(s.Tag)
}
for _, cc := range s.Body.List {
cc := cc.(*ast.CaseClause)
for _, cond := range cc.List {
f.compare(tag, f.info.Types[cond].Type)
}
for _, s := range cc.Body {
f.stmt(s)
}
}
case *ast.TypeSwitchStmt:
if s.Init != nil {
f.stmt(s.Init)
}
var I types.Type
switch ass := s.Assign.(type) {
case *ast.ExprStmt: // x.(type)
I = f.expr(unparen(ass.X).(*ast.TypeAssertExpr).X)
case *ast.AssignStmt: // y := x.(type)
I = f.expr(unparen(ass.Rhs[0]).(*ast.TypeAssertExpr).X)
}
for _, cc := range s.Body.List {
cc := cc.(*ast.CaseClause)
for _, cond := range cc.List {
tCase := f.info.Types[cond].Type
if tCase != tUntypedNil {
f.typeAssert(I, tCase)
}
}
for _, s := range cc.Body {
f.stmt(s)
}
}
case *ast.CommClause:
if s.Comm != nil {
f.stmt(s.Comm)
}
for _, s := range s.Body {
f.stmt(s)
}
case *ast.ForStmt:
if s.Init != nil {
f.stmt(s.Init)
}
if s.Cond != nil {
f.expr(s.Cond)
}
if s.Post != nil {
f.stmt(s.Post)
}
f.stmt(s.Body)
case *ast.RangeStmt:
x := f.expr(s.X)
// No conversions are involved when Tok==DEFINE.
if s.Tok == token.ASSIGN {
if s.Key != nil {
k := f.expr(s.Key)
var xelem types.Type
// keys of array, *array, slice, string aren't interesting
switch ux := x.Underlying().(type) {
case *types.Chan:
xelem = ux.Elem()
case *types.Map:
xelem = ux.Key()
}
if xelem != nil {
f.assign(xelem, k)
}
}
if s.Value != nil {
val := f.expr(s.Value)
var xelem types.Type
// values of strings aren't interesting
switch ux := x.Underlying().(type) {
case *types.Array:
xelem = ux.Elem()
case *types.Chan:
xelem = ux.Elem()
case *types.Map:
xelem = ux.Elem()
case *types.Pointer: // *array
xelem = deref(ux).(*types.Array).Elem()
case *types.Slice:
xelem = ux.Elem()
}
if xelem != nil {
f.assign(xelem, val)
}
}
}
f.stmt(s.Body)
default:
panic(s)
}
}
// -- 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
}
func unparen(e ast.Expr) ast.Expr { return astutil.Unparen(e) }
func isInterface(T types.Type) bool { return types.IsInterface(T) }