diff --git a/refactor/eg/eg.go b/refactor/eg/eg.go index b935db4281..2e843b5c48 100644 --- a/refactor/eg/eg.go +++ b/refactor/eg/eg.go @@ -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" diff --git a/refactor/eg/eg18.go b/refactor/eg/eg18.go deleted file mode 100644 index 177e652fdf..0000000000 --- a/refactor/eg/eg18.go +++ /dev/null @@ -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() -} diff --git a/refactor/eg/match.go b/refactor/eg/match.go index 1152e4dcff..43466202aa 100644 --- a/refactor/eg/match.go +++ b/refactor/eg/match.go @@ -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 ( diff --git a/refactor/eg/match18.go b/refactor/eg/match18.go deleted file mode 100644 index 01c96ab9ff..0000000000 --- a/refactor/eg/match18.go +++ /dev/null @@ -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 -} diff --git a/refactor/rename/check.go b/refactor/rename/check.go index 365d756148..838fc7b79e 100644 --- a/refactor/rename/check.go +++ b/refactor/rename/check.go @@ -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. diff --git a/refactor/rename/check18.go b/refactor/rename/check18.go deleted file mode 100644 index 8bbfce139e..0000000000 --- a/refactor/rename/check18.go +++ /dev/null @@ -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 -} diff --git a/refactor/rename/util.go b/refactor/rename/util.go index a400be7d11..e8f8d7498a 100644 --- a/refactor/rename/util.go +++ b/refactor/rename/util.go @@ -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 ( diff --git a/refactor/rename/util18.go b/refactor/rename/util18.go deleted file mode 100644 index 2594fc834b..0000000000 --- a/refactor/rename/util18.go +++ /dev/null @@ -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) } diff --git a/refactor/satisfy/find.go b/refactor/satisfy/find.go index 1bc43cf8d5..9b365b9b33 100644 --- a/refactor/satisfy/find.go +++ b/refactor/satisfy/find.go @@ -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 diff --git a/refactor/satisfy/find18.go b/refactor/satisfy/find18.go deleted file mode 100644 index abb15d9179..0000000000 --- a/refactor/satisfy/find18.go +++ /dev/null @@ -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) }