1 // Copyright 2014 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
5 // Package eg implements the example-based refactoring tool whose
6 // command-line is defined in golang.org/x/tools/cmd/eg.
7 package eg // import "golang.org/x/tools/refactor/eg"
21 This tool implements example-based refactoring of expressions.
23 The transformation is specified as a Go file defining two functions,
24 'before' and 'after', of identical types. Each function body consists
25 of a single statement: either a return statement with a single
26 (possibly multi-valued) expression, or an expression statement. The
27 'before' expression specifies a pattern and the 'after' expression its
31 import ( "errors"; "fmt" )
32 func before(s string) error { return fmt.Errorf("%s", s) }
33 func after(s string) error { return errors.New(s) }
35 The expression statement form is useful when the expression has no
38 func before(msg string) { log.Fatalf("%s", msg) }
39 func after(msg string) { log.Fatal(msg) }
41 The parameters of both functions are wildcards that may match any
42 expression assignable to that type. If the pattern contains multiple
43 occurrences of the same parameter, each must match the same expression
44 in the input for the pattern to match. If the replacement contains
45 multiple occurrences of the same parameter, the expression will be
46 duplicated, possibly changing the side-effects.
48 The tool analyses all Go code in the packages specified by the
49 arguments, replacing all occurrences of the pattern with the
52 So, the transform above would change this input:
53 err := fmt.Errorf("%s", "error: " + msg)
55 err := errors.New("error: " + msg)
57 Identifiers, including qualified identifiers (p.X) are considered to
58 match only if they denote the same object. This allows correct
59 matching even in the presence of dot imports, named imports and
60 locally shadowed package names in the input program.
62 Matching of type syntax is semantic, not syntactic: type syntax in the
63 pattern matches type syntax in the input if the types are identical.
64 Thus, func(x int) matches func(y int).
66 This tool was inspired by other example-based refactoring tools,
67 'gofmt -r' for Go and Refaster for Java.
75 Only refactorings that replace one expression with another, regardless
76 of the expression's context, may be expressed. Refactoring arbitrary
77 statements (or sequences of statements) is a less well-defined problem
78 and is less amenable to this approach.
80 A pattern that contains a function literal (and hence statements)
83 There is no way to generalize over related types, e.g. to express that
84 a wildcard may have any integer type, for example.
86 It is not possible to replace an expression by one of a different
87 type, even in contexts where this is legal, such as x in fmt.Print(x).
89 The struct literals T{x} and T{K: x} cannot both be matched by a single
95 Verifying that a transformation does not introduce type errors is very
96 complex in the general case. An innocuous-looking replacement of one
97 constant by another (e.g. 1 to 2) may cause type errors relating to
98 array types and indices, for example. The tool performs only very
99 superficial checks of type preservation.
104 Although the matching algorithm is fully aware of scoping rules, the
105 replacement algorithm is not, so the replacement code may contain
106 incorrect identifier syntax for imported objects if there are dot
107 imports, named imports or locally shadowed package names in the input
110 Imports are added as needed, but they are not removed as needed.
111 Run 'goimports' on the modified file for now.
113 Dot imports are forbidden in the template.
119 Sometimes a little creativity is required to implement the desired
120 migration. This section lists a few tips and tricks.
122 To remove the final parameter from a function, temporarily change the
123 function signature so that the final parameter is variadic, as this
124 allows legal calls both with and without the argument. Then use eg to
125 remove the final argument from all callers, and remove the variadic
126 parameter by hand. The reverse process can be used to add a final
129 To add or remove parameters other than the final one, you must do it in
130 stages: (1) declare a variant function f' with a different name and the
131 desired parameters; (2) use eg to transform calls to f into calls to f',
132 changing the arguments as needed; (3) change the declaration of f to
133 match f'; (4) use eg to rename f' to f in all calls; (5) delete f'.
136 // TODO(adonovan): expand upon the above documentation as an HTML page.
138 // A Transformer represents a single example-based transformation.
139 type Transformer struct {
142 info *types.Info // combined type info for template/input/output ASTs
143 seenInfos map[*types.Info]bool
144 wildcards map[*types.Var]bool // set of parameters in func before()
145 env map[string]ast.Expr // maps parameter name to wildcard binding
146 importedObjs map[types.Object]*ast.SelectorExpr // objects imported by after().
147 before, after ast.Expr
148 afterStmts []ast.Stmt
151 // Working state of Transform():
152 nsubsts int // number of substitutions made
153 currentPkg *types.Package // package of current call
156 // NewTransformer returns a transformer based on the specified template,
157 // a single-file package containing "before" and "after" functions as
158 // described in the package documentation.
159 // tmplInfo is the type information for tmplFile.
161 func NewTransformer(fset *token.FileSet, tmplPkg *types.Package, tmplFile *ast.File, tmplInfo *types.Info, verbose bool) (*Transformer, error) {
162 // Check the template.
163 beforeSig := funcSig(tmplPkg, "before")
164 if beforeSig == nil {
165 return nil, fmt.Errorf("no 'before' func found in template")
167 afterSig := funcSig(tmplPkg, "after")
169 return nil, fmt.Errorf("no 'after' func found in template")
172 // TODO(adonovan): should we also check the names of the params match?
173 if !types.Identical(afterSig, beforeSig) {
174 return nil, fmt.Errorf("before %s and after %s functions have different signatures",
178 for _, imp := range tmplFile.Imports {
179 if imp.Name != nil && imp.Name.Name == "." {
180 // Dot imports are currently forbidden. We
181 // make the simplifying assumption that all
182 // imports are regular, without local renames.
183 return nil, fmt.Errorf("dot-import (of %s) in template", imp.Path.Value)
186 var beforeDecl, afterDecl *ast.FuncDecl
187 for _, decl := range tmplFile.Decls {
188 if decl, ok := decl.(*ast.FuncDecl); ok {
189 switch decl.Name.Name {
198 before, err := soleExpr(beforeDecl)
200 return nil, fmt.Errorf("before: %s", err)
202 afterStmts, after, err := stmtAndExpr(afterDecl)
204 return nil, fmt.Errorf("after: %s", err)
207 wildcards := make(map[*types.Var]bool)
208 for i := 0; i < beforeSig.Params().Len(); i++ {
209 wildcards[beforeSig.Params().At(i)] = true
212 // checkExprTypes returns an error if Tb (type of before()) is not
213 // safe to replace with Ta (type of after()).
215 // Only superficial checks are performed, and they may result in both
216 // false positives and negatives.
218 // Ideally, we would only require that the replacement be assignable
219 // to the context of a specific pattern occurrence, but the type
220 // checker doesn't record that information and it's complex to deduce.
221 // A Go type cannot capture all the constraints of a given expression
222 // context, which may include the size, constness, signedness,
223 // namedness or constructor of its type, and even the specific value
224 // of the replacement. (Consider the rule that array literal keys
225 // must be unique.) So we cannot hope to prove the safety of a
226 // transformation in general.
227 Tb := tmplInfo.TypeOf(before)
228 Ta := tmplInfo.TypeOf(after)
229 if types.AssignableTo(Tb, Ta) {
230 // safe: replacement is assignable to pattern.
231 } else if tuple, ok := Tb.(*types.Tuple); ok && tuple.Len() == 0 {
232 // safe: pattern has void type (must appear in an ExprStmt).
234 return nil, fmt.Errorf("%s is not a safe replacement for %s", Ta, Tb)
240 wildcards: wildcards,
241 allowWildcards: true,
242 seenInfos: make(map[*types.Info]bool),
243 importedObjs: make(map[types.Object]*ast.SelectorExpr),
246 afterStmts: afterStmts,
249 // Combine type info from the template and input packages, and
250 // type info for the synthesized ASTs too. This saves us
251 // having to book-keep where each ast.Node originated as we
252 // construct the resulting hybrid AST.
253 tr.info = &types.Info{
254 Types: make(map[ast.Expr]types.TypeAndValue),
255 Defs: make(map[*ast.Ident]types.Object),
256 Uses: make(map[*ast.Ident]types.Object),
257 Selections: make(map[*ast.SelectorExpr]*types.Selection),
259 mergeTypeInfo(tr.info, tmplInfo)
261 // Compute set of imported objects required by after().
262 // TODO(adonovan): reject dot-imports in pattern
263 ast.Inspect(after, func(n ast.Node) bool {
264 if n, ok := n.(*ast.SelectorExpr); ok {
265 if _, ok := tr.info.Selections[n]; !ok {
267 obj := tr.info.Uses[n.Sel]
268 tr.importedObjs[obj] = n
269 return false // prune
278 // WriteAST is a convenience function that writes AST f to the specified file.
279 func WriteAST(fset *token.FileSet, filename string, f *ast.File) (err error) {
280 fh, err := os.Create(filename)
286 if err2 := fh.Close(); err != nil {
287 err = err2 // prefer earlier error
290 return format.Node(fh, fset, f)
293 // -- utilities --------------------------------------------------------
295 // funcSig returns the signature of the specified package-level function.
296 func funcSig(pkg *types.Package, name string) *types.Signature {
297 if f, ok := pkg.Scope().Lookup(name).(*types.Func); ok {
298 return f.Type().(*types.Signature)
303 // soleExpr returns the sole expression in the before/after template function.
304 func soleExpr(fn *ast.FuncDecl) (ast.Expr, error) {
306 return nil, fmt.Errorf("no body")
308 if len(fn.Body.List) != 1 {
309 return nil, fmt.Errorf("must contain a single statement")
311 switch stmt := fn.Body.List[0].(type) {
312 case *ast.ReturnStmt:
313 if len(stmt.Results) != 1 {
314 return nil, fmt.Errorf("return statement must have a single operand")
316 return stmt.Results[0], nil
322 return nil, fmt.Errorf("must contain a single return or expression statement")
325 // stmtAndExpr returns the expression in the last return statement as well as the preceding lines.
326 func stmtAndExpr(fn *ast.FuncDecl) ([]ast.Stmt, ast.Expr, error) {
328 return nil, nil, fmt.Errorf("no body")
331 n := len(fn.Body.List)
333 return nil, nil, fmt.Errorf("must contain at least one statement")
336 stmts, last := fn.Body.List[:n-1], fn.Body.List[n-1]
338 switch last := last.(type) {
339 case *ast.ReturnStmt:
340 if len(last.Results) != 1 {
341 return nil, nil, fmt.Errorf("return statement must have a single operand")
343 return stmts, last.Results[0], nil
346 return stmts, last.X, nil
349 return nil, nil, fmt.Errorf("must end with a single return or expression statement")
352 // mergeTypeInfo adds type info from src to dst.
353 func mergeTypeInfo(dst, src *types.Info) {
354 for k, v := range src.Types {
357 for k, v := range src.Defs {
360 for k, v := range src.Uses {
363 for k, v := range src.Selections {
364 dst.Selections[k] = v
369 func astString(fset *token.FileSet, n ast.Node) string {
371 printer.Fprint(&buf, fset, n)