| // 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. | |
| // 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" | |
| "errors" | |
| "fmt" | |
| "go/ast" | |
| "go/format" | |
| "go/printer" | |
| "go/token" | |
| "go/types" | |
| "maps" | |
| "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 | |
| afterStmts []ast.Stmt | |
| 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) { | |
| // These maps are required by types.Info.TypeOf. | |
| if tmplInfo.Types == nil || tmplInfo.Defs == nil || tmplInfo.Uses == nil { | |
| return nil, errors.New("eg.NewTransformer: types.Info argument missing one of Types, Defs or Uses") | |
| } | |
| // 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. | |
| 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) | |
| } | |
| afterStmts, after, err := stmtAndExpr(afterDecl) | |
| if err != nil { | |
| return nil, fmt.Errorf("after: %s", err) | |
| } | |
| wildcards := make(map[*types.Var]bool) | |
| for v := range beforeSig.Params().Variables() { | |
| wildcards[v] = 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, | |
| afterStmts: afterStmts, | |
| } | |
| // 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") | |
| } | |
| // stmtAndExpr returns the expression in the last return statement as well as the preceding lines. | |
| func stmtAndExpr(fn *ast.FuncDecl) ([]ast.Stmt, ast.Expr, error) { | |
| if fn.Body == nil { | |
| return nil, nil, fmt.Errorf("no body") | |
| } | |
| n := len(fn.Body.List) | |
| if n == 0 { | |
| return nil, nil, fmt.Errorf("must contain at least one statement") | |
| } | |
| stmts, last := fn.Body.List[:n-1], fn.Body.List[n-1] | |
| switch last := last.(type) { | |
| case *ast.ReturnStmt: | |
| if len(last.Results) != 1 { | |
| return nil, nil, fmt.Errorf("return statement must have a single operand") | |
| } | |
| return stmts, last.Results[0], nil | |
| case *ast.ExprStmt: | |
| return stmts, last.X, nil | |
| } | |
| return nil, nil, fmt.Errorf("must end with a single return or expression statement") | |
| } | |
| // mergeTypeInfo adds type info from src to dst. | |
| func mergeTypeInfo(dst, src *types.Info) { | |
| maps.Copy(dst.Types, src.Types) | |
| maps.Copy(dst.Defs, src.Defs) | |
| maps.Copy(dst.Uses, src.Uses) | |
| maps.Copy(dst.Selections, src.Selections) | |
| } | |
| // (debugging only) | |
| func astString(fset *token.FileSet, n ast.Node) string { | |
| var buf bytes.Buffer | |
| printer.Fprint(&buf, fset, n) | |
| return buf.String() | |
| } |