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. |
7 | package eg // import "golang.org/x/tools/refactor/eg" |
8 | |
9 | import ( |
10 | "bytes" |
11 | "fmt" |
12 | "go/ast" |
13 | "go/format" |
14 | "go/printer" |
15 | "go/token" |
16 | "go/types" |
17 | "os" |
18 | ) |
19 | |
20 | const Help = ` |
21 | This tool implements example-based refactoring of expressions. |
22 | |
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 |
28 | replacement. |
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 | |
35 | The expression statement form is useful when the expression has no |
36 | result, for example: |
37 | |
38 | func before(msg string) { log.Fatalf("%s", msg) } |
39 | func after(msg string) { log.Fatal(msg) } |
40 | |
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. |
47 | |
48 | The tool analyses all Go code in the packages specified by the |
49 | arguments, replacing all occurrences of the pattern with the |
50 | substitution. |
51 | |
52 | So, the transform above would change this input: |
53 | err := fmt.Errorf("%s", "error: " + msg) |
54 | to this output: |
55 | err := errors.New("error: " + msg) |
56 | |
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. |
61 | |
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). |
65 | |
66 | This tool was inspired by other example-based refactoring tools, |
67 | 'gofmt -r' for Go and Refaster for Java. |
68 | |
69 | |
70 | LIMITATIONS |
71 | =========== |
72 | |
73 | EXPRESSIVENESS |
74 | |
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. |
79 | |
80 | A pattern that contains a function literal (and hence statements) |
81 | never matches. |
82 | |
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. |
85 | |
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). |
88 | |
89 | The struct literals T{x} and T{K: x} cannot both be matched by a single |
90 | template. |
91 | |
92 | |
93 | SAFETY |
94 | |
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. |
100 | |
101 | |
102 | IMPORTS |
103 | |
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 |
108 | program. |
109 | |
110 | Imports are added as needed, but they are not removed as needed. |
111 | Run 'goimports' on the modified file for now. |
112 | |
113 | Dot imports are forbidden in the template. |
114 | |
115 | |
116 | TIPS |
117 | ==== |
118 | |
119 | Sometimes a little creativity is required to implement the desired |
120 | migration. This section lists a few tips and tricks. |
121 | |
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 |
127 | parameter. |
128 | |
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'. |
134 | ` |
135 | |
136 | // TODO(adonovan): expand upon the above documentation as an HTML page. |
137 | |
138 | // A Transformer represents a single example-based transformation. |
139 | type 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 | before, after 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. |
160 | func NewTransformer(fset *token.FileSet, tmplPkg *types.Package, tmplFile *ast.File, tmplInfo *types.Info, verbose bool) (*Transformer, error) { |
161 | // Check the template. |
162 | beforeSig := funcSig(tmplPkg, "before") |
163 | if beforeSig == nil { |
164 | return nil, fmt.Errorf("no 'before' func found in template") |
165 | } |
166 | afterSig := funcSig(tmplPkg, "after") |
167 | if afterSig == nil { |
168 | return nil, fmt.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(afterSig, beforeSig) { |
173 | return nil, fmt.Errorf("before %s and after %s functions have different signatures", |
174 | beforeSig, afterSig) |
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 nil, fmt.Errorf("dot-import (of %s) in template", imp.Path.Value) |
183 | } |
184 | } |
185 | var beforeDecl, afterDecl *ast.FuncDecl |
186 | for _, decl := range tmplFile.Decls { |
187 | if decl, ok := 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 | before, err := soleExpr(beforeDecl) |
198 | if err != nil { |
199 | return nil, fmt.Errorf("before: %s", err) |
200 | } |
201 | afterStmts, after, err := stmtAndExpr(afterDecl) |
202 | if err != nil { |
203 | return nil, fmt.Errorf("after: %s", err) |
204 | } |
205 | |
206 | wildcards := make(map[*types.Var]bool) |
207 | for i := 0; i < 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(Tb, Ta) { |
229 | // safe: replacement is assignable to pattern. |
230 | } else if tuple, ok := Tb.(*types.Tuple); ok && tuple.Len() == 0 { |
231 | // safe: pattern has void type (must appear in an ExprStmt). |
232 | } else { |
233 | return nil, fmt.Errorf("%s is not a safe replacement for %s", Ta, Tb) |
234 | } |
235 | |
236 | tr := &Transformer{ |
237 | fset: fset, |
238 | verbose: verbose, |
239 | wildcards: wildcards, |
240 | allowWildcards: true, |
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 | Selections: make(map[*ast.SelectorExpr]*types.Selection), |
257 | } |
258 | mergeTypeInfo(tr.info, tmplInfo) |
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.Node) bool { |
263 | if n, ok := 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 tr, nil |
275 | } |
276 | |
277 | // WriteAST is a convenience function that writes AST f to the specified file. |
278 | func WriteAST(fset *token.FileSet, filename string, f *ast.File) (err error) { |
279 | fh, err := 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(fh, fset, f) |
290 | } |
291 | |
292 | // -- utilities -------------------------------------------------------- |
293 | |
294 | // funcSig returns the signature of the specified package-level function. |
295 | func funcSig(pkg *types.Package, name string) *types.Signature { |
296 | if f, ok := 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. |
303 | func soleExpr(fn *ast.FuncDecl) (ast.Expr, error) { |
304 | if fn.Body == nil { |
305 | return nil, fmt.Errorf("no body") |
306 | } |
307 | if len(fn.Body.List) != 1 { |
308 | return nil, fmt.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 nil, fmt.Errorf("return statement must have a single operand") |
314 | } |
315 | return stmt.Results[0], nil |
316 | |
317 | case *ast.ExprStmt: |
318 | return stmt.X, nil |
319 | } |
320 | |
321 | return nil, fmt.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. |
325 | func stmtAndExpr(fn *ast.FuncDecl) ([]ast.Stmt, ast.Expr, error) { |
326 | if fn.Body == nil { |
327 | return nil, nil, fmt.Errorf("no body") |
328 | } |
329 | |
330 | n := len(fn.Body.List) |
331 | if n == 0 { |
332 | return nil, nil, fmt.Errorf("must contain at least one statement") |
333 | } |
334 | |
335 | stmts, last := 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 nil, nil, fmt.Errorf("return statement must have a single operand") |
341 | } |
342 | return stmts, last.Results[0], nil |
343 | |
344 | case *ast.ExprStmt: |
345 | return stmts, last.X, nil |
346 | } |
347 | |
348 | return nil, nil, fmt.Errorf("must end with a single return or expression statement") |
349 | } |
350 | |
351 | // mergeTypeInfo adds type info from src to dst. |
352 | func mergeTypeInfo(dst, src *types.Info) { |
353 | for k, v := range src.Types { |
354 | dst.Types[k] = v |
355 | } |
356 | for k, v := range src.Defs { |
357 | dst.Defs[k] = v |
358 | } |
359 | for k, v := range src.Uses { |
360 | dst.Uses[k] = v |
361 | } |
362 | for k, v := range src.Selections { |
363 | dst.Selections[k] = v |
364 | } |
365 | } |
366 | |
367 | // (debugging only) |
368 | func astString(fset *token.FileSet, n ast.Node) string { |
369 | var buf bytes.Buffer |
370 | printer.Fprint(&buf, fset, n) |
371 | return buf.String() |
372 | } |
373 |
Members