GoPLS Viewer

Home|gopls/refactor/eg/eg.go
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.
4
5// Package eg implements the example-based refactoring tool whose
6// command-line is defined in golang.org/x/tools/cmd/eg.
7package eg // import "golang.org/x/tools/refactor/eg"
8
9import (
10    "bytes"
11    "fmt"
12    "go/ast"
13    "go/format"
14    "go/printer"
15    "go/token"
16    "go/types"
17    "os"
18)
19
20const Help = `
21This tool implements example-based refactoring of expressions.
22
23The transformation is specified as a Go file defining two functions,
24'before' and 'after', of identical types.  Each function body consists
25of 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
28replacement.
29
30    package P
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) }
34
35The expression statement form is useful when the expression has no
36result, for example:
37
38     func before(msg string) { log.Fatalf("%s", msg) }
39     func after(msg string)  { log.Fatal(msg) }
40
41The parameters of both functions are wildcards that may match any
42expression assignable to that type.  If the pattern contains multiple
43occurrences of the same parameter, each must match the same expression
44in the input for the pattern to match.  If the replacement contains
45multiple occurrences of the same parameter, the expression will be
46duplicated, possibly changing the side-effects.
47
48The tool analyses all Go code in the packages specified by the
49arguments, replacing all occurrences of the pattern with the
50substitution.
51
52So, the transform above would change this input:
53    err := fmt.Errorf("%s", "error: " + msg)
54to this output:
55    err := errors.New("error: " + msg)
56
57Identifiers, including qualified identifiers (p.X) are considered to
58match only if they denote the same object.  This allows correct
59matching even in the presence of dot imports, named imports and
60locally shadowed package names in the input program.
61
62Matching of type syntax is semantic, not syntactic: type syntax in the
63pattern matches type syntax in the input if the types are identical.
64Thus, func(x int) matches func(y int).
65
66This tool was inspired by other example-based refactoring tools,
67'gofmt -r' for Go and Refaster for Java.
68
69
70LIMITATIONS
71===========
72
73EXPRESSIVENESS
74
75Only refactorings that replace one expression with another, regardless
76of the expression's context, may be expressed.  Refactoring arbitrary
77statements (or sequences of statements) is a less well-defined problem
78and is less amenable to this approach.
79
80A pattern that contains a function literal (and hence statements)
81never matches.
82
83There is no way to generalize over related types, e.g. to express that
84a wildcard may have any integer type, for example.
85
86It is not possible to replace an expression by one of a different
87type, even in contexts where this is legal, such as x in fmt.Print(x).
88
89The struct literals T{x} and T{K: x} cannot both be matched by a single
90template.
91
92
93SAFETY
94
95Verifying that a transformation does not introduce type errors is very
96complex in the general case.  An innocuous-looking replacement of one
97constant by another (e.g. 1 to 2) may cause type errors relating to
98array types and indices, for example.  The tool performs only very
99superficial checks of type preservation.
100
101
102IMPORTS
103
104Although the matching algorithm is fully aware of scoping rules, the
105replacement algorithm is not, so the replacement code may contain
106incorrect identifier syntax for imported objects if there are dot
107imports, named imports or locally shadowed package names in the input
108program.
109
110Imports are added as needed, but they are not removed as needed.
111Run 'goimports' on the modified file for now.
112
113Dot imports are forbidden in the template.
114
115
116TIPS
117====
118
119Sometimes a little creativity is required to implement the desired
120migration.  This section lists a few tips and tricks.
121
122To remove the final parameter from a function, temporarily change the
123function signature so that the final parameter is variadic, as this
124allows legal calls both with and without the argument.  Then use eg to
125remove the final argument from all callers, and remove the variadic
126parameter by hand.  The reverse process can be used to add a final
127parameter.
128
129To add or remove parameters other than the final one, you must do it in
130stages: (1) declare a variant function f' with a different name and the
131desired parameters; (2) use eg to transform calls to f into calls to f',
132changing the arguments as needed; (3) change the declaration of f to
133match f'; (4) use eg to rename f' to f in all calls; (5) delete f'.
134`
135
136// TODO(adonovan): expand upon the above documentation as an HTML page.
137
138// A Transformer represents a single example-based transformation.
139type Transformer struct {
140    fset           *token.FileSet
141    verbose        bool
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    beforeafter  ast.Expr
148    afterStmts     []ast.Stmt
149    allowWildcards bool
150
151    // Working state of Transform():
152    nsubsts    int            // number of substitutions made
153    currentPkg *types.Package // package of current call
154}
155
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.
160func NewTransformer(fset *token.FileSettmplPkg *types.PackagetmplFile *ast.FiletmplInfo *types.Infoverbose bool) (*Transformererror) {
161    // Check the template.
162    beforeSig := funcSig(tmplPkg"before")
163    if beforeSig == nil {
164        return nilfmt.Errorf("no 'before' func found in template")
165    }
166    afterSig := funcSig(tmplPkg"after")
167    if afterSig == nil {
168        return nilfmt.Errorf("no 'after' func found in template")
169    }
170
171    // TODO(adonovan): should we also check the names of the params match?
172    if !types.Identical(afterSigbeforeSig) {
173        return nilfmt.Errorf("before %s and after %s functions have different signatures",
174            beforeSigafterSig)
175    }
176
177    for _imp := range tmplFile.Imports {
178        if imp.Name != nil && imp.Name.Name == "." {
179            // Dot imports are currently forbidden.  We
180            // make the simplifying assumption that all
181            // imports are regular, without local renames.
182            return nilfmt.Errorf("dot-import (of %s) in template"imp.Path.Value)
183        }
184    }
185    var beforeDeclafterDecl *ast.FuncDecl
186    for _decl := range tmplFile.Decls {
187        if declok := decl.(*ast.FuncDecl); ok {
188            switch decl.Name.Name {
189            case "before":
190                beforeDecl = decl
191            case "after":
192                afterDecl = decl
193            }
194        }
195    }
196
197    beforeerr := soleExpr(beforeDecl)
198    if err != nil {
199        return nilfmt.Errorf("before: %s"err)
200    }
201    afterStmtsaftererr := stmtAndExpr(afterDecl)
202    if err != nil {
203        return nilfmt.Errorf("after: %s"err)
204    }
205
206    wildcards := make(map[*types.Var]bool)
207    for i := 0i < beforeSig.Params().Len(); i++ {
208        wildcards[beforeSig.Params().At(i)] = true
209    }
210
211    // checkExprTypes returns an error if Tb (type of before()) is not
212    // safe to replace with Ta (type of after()).
213    //
214    // Only superficial checks are performed, and they may result in both
215    // false positives and negatives.
216    //
217    // Ideally, we would only require that the replacement be assignable
218    // to the context of a specific pattern occurrence, but the type
219    // checker doesn't record that information and it's complex to deduce.
220    // A Go type cannot capture all the constraints of a given expression
221    // context, which may include the size, constness, signedness,
222    // namedness or constructor of its type, and even the specific value
223    // of the replacement.  (Consider the rule that array literal keys
224    // must be unique.)  So we cannot hope to prove the safety of a
225    // transformation in general.
226    Tb := tmplInfo.TypeOf(before)
227    Ta := tmplInfo.TypeOf(after)
228    if types.AssignableTo(TbTa) {
229        // safe: replacement is assignable to pattern.
230    } else if tupleok := Tb.(*types.Tuple); ok && tuple.Len() == 0 {
231        // safe: pattern has void type (must appear in an ExprStmt).
232    } else {
233        return nilfmt.Errorf("%s is not a safe replacement for %s"TaTb)
234    }
235
236    tr := &Transformer{
237        fset:           fset,
238        verbose:        verbose,
239        wildcards:      wildcards,
240        allowWildcardstrue,
241        seenInfos:      make(map[*types.Info]bool),
242        importedObjs:   make(map[types.Object]*ast.SelectorExpr),
243        before:         before,
244        after:          after,
245        afterStmts:     afterStmts,
246    }
247
248    // Combine type info from the template and input packages, and
249    // type info for the synthesized ASTs too.  This saves us
250    // having to book-keep where each ast.Node originated as we
251    // construct the resulting hybrid AST.
252    tr.info = &types.Info{
253        Types:      make(map[ast.Expr]types.TypeAndValue),
254        Defs:       make(map[*ast.Ident]types.Object),
255        Uses:       make(map[*ast.Ident]types.Object),
256        Selectionsmake(map[*ast.SelectorExpr]*types.Selection),
257    }
258    mergeTypeInfo(tr.infotmplInfo)
259
260    // Compute set of imported objects required by after().
261    // TODO(adonovan): reject dot-imports in pattern
262    ast.Inspect(after, func(n ast.Nodebool {
263        if nok := n.(*ast.SelectorExpr); ok {
264            if _ok := tr.info.Selections[n]; !ok {
265                // qualified ident
266                obj := tr.info.Uses[n.Sel]
267                tr.importedObjs[obj] = n
268                return false // prune
269            }
270        }
271        return true // recur
272    })
273
274    return trnil
275}
276
277// WriteAST is a convenience function that writes AST f to the specified file.
278func WriteAST(fset *token.FileSetfilename stringf *ast.File) (err error) {
279    fherr := os.Create(filename)
280    if err != nil {
281        return err
282    }
283
284    defer func() {
285        if err2 := fh.Close(); err != nil {
286            err = err2 // prefer earlier error
287        }
288    }()
289    return format.Node(fhfsetf)
290}
291
292// -- utilities --------------------------------------------------------
293
294// funcSig returns the signature of the specified package-level function.
295func funcSig(pkg *types.Packagename string) *types.Signature {
296    if fok := pkg.Scope().Lookup(name).(*types.Func); ok {
297        return f.Type().(*types.Signature)
298    }
299    return nil
300}
301
302// soleExpr returns the sole expression in the before/after template function.
303func soleExpr(fn *ast.FuncDecl) (ast.Exprerror) {
304    if fn.Body == nil {
305        return nilfmt.Errorf("no body")
306    }
307    if len(fn.Body.List) != 1 {
308        return nilfmt.Errorf("must contain a single statement")
309    }
310    switch stmt := fn.Body.List[0].(type) {
311    case *ast.ReturnStmt:
312        if len(stmt.Results) != 1 {
313            return nilfmt.Errorf("return statement must have a single operand")
314        }
315        return stmt.Results[0], nil
316
317    case *ast.ExprStmt:
318        return stmt.Xnil
319    }
320
321    return nilfmt.Errorf("must contain a single return or expression statement")
322}
323
324// stmtAndExpr returns the expression in the last return statement as well as the preceding lines.
325func stmtAndExpr(fn *ast.FuncDecl) ([]ast.Stmtast.Exprerror) {
326    if fn.Body == nil {
327        return nilnilfmt.Errorf("no body")
328    }
329
330    n := len(fn.Body.List)
331    if n == 0 {
332        return nilnilfmt.Errorf("must contain at least one statement")
333    }
334
335    stmtslast := fn.Body.List[:n-1], fn.Body.List[n-1]
336
337    switch last := last.(type) {
338    case *ast.ReturnStmt:
339        if len(last.Results) != 1 {
340            return nilnilfmt.Errorf("return statement must have a single operand")
341        }
342        return stmtslast.Results[0], nil
343
344    case *ast.ExprStmt:
345        return stmtslast.Xnil
346    }
347
348    return nilnilfmt.Errorf("must end with a single return or expression statement")
349}
350
351// mergeTypeInfo adds type info from src to dst.
352func mergeTypeInfo(dstsrc *types.Info) {
353    for kv := range src.Types {
354        dst.Types[k] = v
355    }
356    for kv := range src.Defs {
357        dst.Defs[k] = v
358    }
359    for kv := range src.Uses {
360        dst.Uses[k] = v
361    }
362    for kv := range src.Selections {
363        dst.Selections[k] = v
364    }
365}
366
367// (debugging only)
368func astString(fset *token.FileSetn ast.Nodestring {
369    var buf bytes.Buffer
370    printer.Fprint(&buffsetn)
371    return buf.String()
372}
373
MembersX
mergeTypeInfo.RangeStmt_11965.v
mergeTypeInfo.RangeStmt_12067.k
WriteAST.fset
WriteAST.f
mergeTypeInfo.RangeStmt_11912.k
NewTransformer.afterStmts
mergeTypeInfo.dst
NewTransformer.fset
WriteAST.err
funcSig.name
astString.buf
Transformer.currentPkg
WriteAST.filename
NewTransformer.tmplInfo
Transformer.wildcards
NewTransformer
WriteAST.fh
mergeTypeInfo.RangeStmt_12016.k
Help
Transformer
NewTransformer.wildcards
NewTransformer.Tb
NewTransformer.Ta
soleExpr
astString.n
bytes
Transformer.allowWildcards
NewTransformer.afterSig
stmtAndExpr
ast
NewTransformer.beforeSig
mergeTypeInfo.RangeStmt_11912.v
mergeTypeInfo.RangeStmt_12067.v
Transformer.fset
stmtAndExpr.fn
Transformer.importedObjs
NewTransformer.RangeStmt_6487.imp
NewTransformer.err
token
Transformer.seenInfos
NewTransformer.after
NewTransformer.i
astString
NewTransformer.verbose
NewTransformer.RangeStmt_6842.decl
mergeTypeInfo
fmt
Transformer.env
NewTransformer.tmplPkg
NewTransformer.before
mergeTypeInfo.RangeStmt_11965.k
astString.fset
Transformer.after
Transformer.nsubsts
os
Transformer.before
NewTransformer.tmplFile
NewTransformer.tr
WriteAST
WriteAST.BlockStmt.err2
printer
types
funcSig.pkg
stmtAndExpr.n
mergeTypeInfo.RangeStmt_12016.v
format
Transformer.afterStmts
NewTransformer.beforeDecl
NewTransformer.afterDecl
funcSig
soleExpr.fn
mergeTypeInfo.src
Transformer.verbose
Transformer.info
Members
X