github.com/graybobo/golang.org-package-offline-cache@v0.0.0-20200626051047-6608995c132f/x/tools/refactor/eg/eg14.go (about)

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