mirror of
https://github.com/golang/go
synced 2024-11-18 10:14:45 -07:00
2226533658
The semantics of this change are that the last line will be subsituted in place of the expression, where as the lines before that will undergo variable substitution and be prepended before the lowest (in the AST tree sense) statement which included the expression. Change-Id: Ie2571934dcc1b0a30b5cec157e690924a4ac2c5a Reviewed-on: https://go-review.googlesource.com/77730 Reviewed-by: Alan Donovan <adonovan@google.com>
374 lines
12 KiB
Go
374 lines
12 KiB
Go
// 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"
|
|
"fmt"
|
|
"go/ast"
|
|
"go/format"
|
|
"go/printer"
|
|
"go/token"
|
|
"go/types"
|
|
"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) {
|
|
// 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 i := 0; i < beforeSig.Params().Len(); i++ {
|
|
wildcards[beforeSig.Params().At(i)] = 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 preceeding 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) {
|
|
for k, v := range src.Types {
|
|
dst.Types[k] = v
|
|
}
|
|
for k, v := range src.Defs {
|
|
dst.Defs[k] = v
|
|
}
|
|
for k, v := range src.Uses {
|
|
dst.Uses[k] = v
|
|
}
|
|
for k, v := range src.Selections {
|
|
dst.Selections[k] = v
|
|
}
|
|
}
|
|
|
|
// (debugging only)
|
|
func astString(fset *token.FileSet, n ast.Node) string {
|
|
var buf bytes.Buffer
|
|
printer.Fprint(&buf, fset, n)
|
|
return buf.String()
|
|
}
|