1
2
3
4
5
6
7 package 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
137
138
139 type Transformer struct {
140 fset *token.FileSet
141 verbose bool
142 info *types.Info
143 seenInfos map[*types.Info]bool
144 wildcards map[*types.Var]bool
145 env map[string]ast.Expr
146 importedObjs map[types.Object]*ast.SelectorExpr
147 before, after ast.Expr
148 afterStmts []ast.Stmt
149 allowWildcards bool
150
151
152 nsubsts int
153 currentPkg *types.Package
154 }
155
156
157
158
159
160 func NewTransformer(fset *token.FileSet, tmplPkg *types.Package, tmplFile *ast.File, tmplInfo *types.Info, verbose bool) (*Transformer, error) {
161
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
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
180
181
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
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226 Tb := tmplInfo.TypeOf(before)
227 Ta := tmplInfo.TypeOf(after)
228 if types.AssignableTo(Tb, Ta) {
229
230 } else if tuple, ok := Tb.(*types.Tuple); ok && tuple.Len() == 0 {
231
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
249
250
251
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
261
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
266 obj := tr.info.Uses[n.Sel]
267 tr.importedObjs[obj] = n
268 return false
269 }
270 }
271 return true
272 })
273
274 return tr, nil
275 }
276
277
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
287 }
288 }()
289 return format.Node(fh, fset, f)
290 }
291
292
293
294
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
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
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
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
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
View as plain text