...

Source file src/golang.org/x/tools/refactor/eg/eg.go

Documentation: golang.org/x/tools/refactor/eg

     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  

View as plain text