go.tools/refactor/eg: an example-based refactoring tool.
See refactor/eg/eg.go for details.
LGTM=crawshaw
R=crawshaw, gri, kamil.kisiel, josharian
CC=golang-codereviews
https://golang.org/cl/81010043
diff --git a/cmd/eg/eg.go b/cmd/eg/eg.go
new file mode 100644
index 0000000..3a12d33
--- /dev/null
+++ b/cmd/eg/eg.go
@@ -0,0 +1,121 @@
+// The eg command performs example-based refactoring.
+package main
+
+import (
+ "flag"
+ "fmt"
+ "go/parser"
+ "go/printer"
+ "go/token"
+ "os"
+ "path/filepath"
+
+ "code.google.com/p/go.tools/go/loader"
+ "code.google.com/p/go.tools/refactor/eg"
+)
+
+var (
+ helpFlag = flag.Bool("help", false, "show detailed help message")
+ templateFlag = flag.String("t", "", "template.go file specifying the refactoring")
+ transitiveFlag = flag.Bool("transitive", false, "apply refactoring to all dependencies too")
+ writeFlag = flag.Bool("w", false, "rewrite input files in place (by default, the results are printed to standard output)")
+ verboseFlag = flag.Bool("v", false, "show verbose matcher diagnostics")
+)
+
+const usage = `eg: an example-based refactoring tool.
+
+Usage: eg -t template.go [-w] [-transitive] <args>...
+-t template.go specifies the template file (use -help to see explanation)
+-w causes files to be re-written in place.
+-transitive causes all dependencies to be refactored too.
+` + loader.FromArgsUsage
+
+func main() {
+ if err := doMain(); err != nil {
+ fmt.Fprintf(os.Stderr, "%s: %s.\n", filepath.Base(os.Args[0]), err)
+ os.Exit(1)
+ }
+}
+
+func doMain() error {
+ flag.Parse()
+ args := flag.Args()
+
+ if *helpFlag {
+ fmt.Fprintf(os.Stderr, eg.Help)
+ os.Exit(2)
+ }
+
+ if *templateFlag == "" {
+ return fmt.Errorf("no -t template.go file specified")
+ }
+
+ conf := loader.Config{
+ Fset: token.NewFileSet(),
+ ParserMode: parser.ParseComments,
+ SourceImports: true,
+ }
+
+ // The first Created package is the template.
+ if err := conf.CreateFromFilenames("template", *templateFlag); err != nil {
+ return err // e.g. "foo.go:1: syntax error"
+ }
+
+ if len(args) == 0 {
+ fmt.Fprint(os.Stderr, usage)
+ os.Exit(1)
+ }
+
+ if _, err := conf.FromArgs(args, true); err != nil {
+ return err
+ }
+
+ // Load, parse and type-check the whole program.
+ iprog, err := conf.Load()
+ if err != nil {
+ return err
+ }
+
+ // Analyze the template.
+ template := iprog.Created[0]
+ xform, err := eg.NewTransformer(iprog.Fset, template, *verboseFlag)
+ if err != nil {
+ return err
+ }
+
+ // Apply it to the input packages.
+ var pkgs []*loader.PackageInfo
+ if *transitiveFlag {
+ for _, info := range iprog.AllPackages {
+ pkgs = append(pkgs, info)
+ }
+ } else {
+ pkgs = iprog.InitialPackages()
+ }
+ var hadErrors bool
+ for _, pkg := range pkgs {
+ if pkg == template {
+ continue
+ }
+ for _, file := range pkg.Files {
+ n := xform.Transform(&pkg.Info, pkg.Pkg, file)
+ if n == 0 {
+ continue
+ }
+ filename := iprog.Fset.File(file.Pos()).Name()
+ fmt.Fprintf(os.Stderr, "=== %s (%d matches):\n", filename, n)
+ if *writeFlag {
+ if err := eg.WriteAST(iprog.Fset, filename, file); err != nil {
+ fmt.Fprintf(os.Stderr, "Error: %s\n", err)
+ hadErrors = true
+ }
+ } else {
+ printer.Fprint(os.Stdout, iprog.Fset, file)
+ }
+ }
+ }
+ if hadErrors {
+ os.Exit(1)
+ }
+ return nil
+}
diff --git a/refactor/README b/refactor/README
new file mode 100644
index 0000000..a6edb13
--- /dev/null
+++ b/refactor/README
@@ -0,0 +1 @@
+code.google.com/p/go.tools/refactor: libraries for refactoring tools.
diff --git a/refactor/eg/eg.go b/refactor/eg/eg.go
new file mode 100644
index 0000000..5afe635
--- /dev/null
+++ b/refactor/eg/eg.go
@@ -0,0 +1,326 @@
+// Package eg implements the example-based refactoring tool whose
+// command-line is defined in code.google.com/p/go.tools/cmd/eg.
+package eg
+
+import (
+ "bytes"
+ "fmt"
+ "go/ast"
+ "go/printer"
+ "go/token"
+ "os"
+
+ "code.google.com/p/go.tools/go/loader"
+ "code.google.com/p/go.tools/go/types"
+)
+
+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 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.
+
+
+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.
+
+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).
+
+
+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.
+`
+
+// TODO(adonovan): allow the tool to be invoked using relative package
+// directory names (./foo). Requires changes to go/loader.
+
+// TODO(adonovan): expand upon the above documentation as an HTML page.
+
+// TODO(adonovan): eliminate dependency on loader.PackageInfo.
+// Move its ObjectOf/IsType/TypeOf methods into go/types.
+
+// A Transformer represents a single example-based transformation.
+type Transformer struct {
+ fset *token.FileSet
+ verbose bool
+ info loader.PackageInfo // 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
+ 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 package containing "before" and "after" functions as described
+// in the package documentation.
+//
+func NewTransformer(fset *token.FileSet, template *loader.PackageInfo, verbose bool) (*Transformer, error) {
+ // Check the template.
+ beforeSig := funcSig(template.Pkg, "before")
+ if beforeSig == nil {
+ return nil, fmt.Errorf("no 'before' func found in template")
+ }
+ afterSig := funcSig(template.Pkg, "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)
+ }
+
+ templateFile := template.Files[0]
+ for _, imp := range templateFile.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.
+ //TODO document
+ return nil, fmt.Errorf("dot-import (of %s) in template", imp.Path.Value)
+ }
+ }
+ var beforeDecl, afterDecl *ast.FuncDecl
+ for _, decl := range templateFile.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)
+ }
+ after, err := soleExpr(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 := template.TypeOf(before)
+ Ta := template.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,
+ }
+
+ // 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.
+ //
+ // TODO(adonovan): move type utility methods of PackageInfo to
+ // types.Info, or at least into go/types.typeutil.
+ tr.info.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.Info, &template.Info)
+
+ // Compute set of imported objects required by after().
+ // TODO reject dot-imports in pattern
+ ast.Inspect(after, func(n ast.Node) bool {
+ if n, ok := n.(*ast.SelectorExpr); ok {
+ sel := tr.info.Selections[n]
+ if sel.Kind() == types.PackageObj {
+ tr.importedObjs[sel.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 printer.Fprint(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")
+}
+
+// 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()
+}
diff --git a/refactor/eg/eg_test.go b/refactor/eg/eg_test.go
new file mode 100644
index 0000000..3817402
--- /dev/null
+++ b/refactor/eg/eg_test.go
@@ -0,0 +1,145 @@
+package eg_test
+
+import (
+ "bytes"
+ "flag"
+ "go/parser"
+ "go/token"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "runtime"
+ "strings"
+ "testing"
+
+ "code.google.com/p/go.tools/go/exact"
+ "code.google.com/p/go.tools/go/loader"
+ "code.google.com/p/go.tools/go/types"
+ "code.google.com/p/go.tools/refactor/eg"
+)
+
+// TODO(adonovan): more tests:
+// - of command-line tool
+// - of all parts of syntax
+// - of applying a template to a package it imports:
+// the replacement syntax should use unqualified names for its objects.
+
+var (
+ updateFlag = flag.Bool("update", false, "update the golden files")
+ verboseFlag = flag.Bool("verbose", false, "show matcher information")
+)
+
+func Test(t *testing.T) {
+ switch runtime.GOOS {
+ case "windows":
+ t.Skipf("skipping test on %q (no /usr/bin/diff)", runtime.GOOS)
+ }
+
+ conf := loader.Config{
+ Fset: token.NewFileSet(),
+ ParserMode: parser.ParseComments,
+ SourceImports: true,
+ }
+
+ // Each entry is a single-file package.
+ // (Multi-file packages aren't interesting for this test.)
+ // Order matters: each non-template package is processed using
+ // the preceding template package.
+ for _, filename := range []string{
+ "testdata/A.template",
+ "testdata/A1.go",
+ "testdata/A2.go",
+
+ "testdata/B.template",
+ "testdata/B1.go",
+
+ "testdata/C.template",
+ "testdata/C1.go",
+
+ "testdata/D.template",
+ "testdata/D1.go",
+
+ "testdata/E.template",
+ "testdata/E1.go",
+
+ "testdata/bad_type.template",
+ "testdata/no_before.template",
+ "testdata/no_after_return.template",
+ "testdata/type_mismatch.template",
+ "testdata/expr_type_mismatch.template",
+ } {
+ pkgname := strings.TrimSuffix(filepath.Base(filename), ".go")
+ if err := conf.CreateFromFilenames(pkgname, filename); err != nil {
+ t.Fatal(err)
+ }
+ }
+ iprog, err := conf.Load()
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ var xform *eg.Transformer
+ for _, info := range iprog.Created {
+ file := info.Files[0]
+ filename := iprog.Fset.File(file.Pos()).Name() // foo.go
+
+ if strings.HasSuffix(filename, "template") {
+ // a new template
+ shouldFail, _ := info.Pkg.Scope().Lookup("shouldFail").(*types.Const)
+ xform, err = eg.NewTransformer(iprog.Fset, info, *verboseFlag)
+ if err != nil {
+ if shouldFail == nil {
+ t.Errorf("NewTransformer(%s): %s", filename, err)
+ } else if want := exact.StringVal(shouldFail.Val()); !strings.Contains(err.Error(), want) {
+ t.Errorf("NewTransformer(%s): got error %q, want error %q", filename, err, want)
+ }
+ } else if shouldFail != nil {
+ t.Errorf("NewTransformer(%s) succeeded unexpectedly; want error %q",
+ filename, shouldFail.Val())
+ }
+ continue
+ }
+
+ if xform == nil {
+ t.Errorf("%s: no previous template", filename)
+ continue
+ }
+
+ // apply previous template to this package
+ n := xform.Transform(&info.Info, info.Pkg, file)
+ if n == 0 {
+ t.Errorf("%s: no matches", filename)
+ continue
+ }
+
+ got := filename + "t" // foo.got
+ golden := filename + "lden" // foo.golden
+
+ // Write actual output to foo.got.
+ if err := eg.WriteAST(iprog.Fset, got, file); err != nil {
+ t.Error(err)
+ }
+
+ // Compare foo.got with foo.golden.
+ var cmd *exec.Cmd
+ switch runtime.GOOS {
+ case "plan9":
+ cmd = exec.Command("/bin/diff", "-c", golden, got)
+ default:
+ cmd = exec.Command("/usr/bin/diff", "-u", "-N", golden, got)
+ }
+ buf := new(bytes.Buffer)
+ cmd.Stdout = buf
+ cmd.Stderr = os.Stderr
+ if err := cmd.Run(); err != nil {
+ t.Errorf("eg tests for %s failed: %s.\n%s\n", filename, err, buf)
+
+ if *updateFlag {
+ t.Logf("Updating %s...", golden)
+ if err := exec.Command("/bin/cp", got, golden).Run(); err != nil {
+ t.Errorf("Update failed: %s", err)
+ }
+ }
+ }
+ }
+}
diff --git a/refactor/eg/match.go b/refactor/eg/match.go
new file mode 100644
index 0000000..7476f9a
--- /dev/null
+++ b/refactor/eg/match.go
@@ -0,0 +1,226 @@
+package eg
+
+import (
+ "fmt"
+ "go/ast"
+ "go/token"
+ "log"
+ "os"
+ "reflect"
+
+ "code.google.com/p/go.tools/go/exact"
+ "code.google.com/p/go.tools/go/loader"
+ "code.google.com/p/go.tools/go/types"
+)
+
+// matchExpr reports whether pattern x matches y.
+//
+// If tr.allowWildcards, Idents in x that refer to parameters are
+// treated as wildcards, and match any y that is assignable to the
+// parameter type; matchExpr records this correspondence in tr.env.
+// Otherwise, matchExpr simply reports whether the two trees are
+// equivalent.
+//
+// A wildcard appearing more than once in the pattern must
+// consistently match the same tree.
+//
+func (tr *Transformer) matchExpr(x, y ast.Expr) bool {
+ if x == nil && y == nil {
+ return true
+ }
+ if x == nil || y == nil {
+ return false
+ }
+ x = unparen(x)
+ y = unparen(y)
+
+ // Is x a wildcard? (a reference to a 'before' parameter)
+ if x, ok := x.(*ast.Ident); ok && x != nil && tr.allowWildcards {
+ if xobj, ok := tr.info.Uses[x].(*types.Var); ok && tr.wildcards[xobj] {
+ return tr.matchWildcard(xobj, y)
+ }
+ }
+
+ // Object identifiers (including pkg-qualified ones)
+ // are handled semantically, not syntactically.
+ xobj := isRef(x, &tr.info)
+ yobj := isRef(y, &tr.info)
+ if xobj != nil {
+ return xobj == yobj
+ }
+ if yobj != nil {
+ return false
+ }
+
+ // TODO(adonovan): audit: we cannot assume these ast.Exprs
+ // contain non-nil pointers. e.g. ImportSpec.Name may be a
+ // nil *ast.Ident.
+
+ if reflect.TypeOf(x) != reflect.TypeOf(y) {
+ return false
+ }
+ switch x := x.(type) {
+ case *ast.Ident:
+ log.Fatalf("unexpected Ident: %s", astString(tr.fset, x))
+
+ case *ast.BasicLit:
+ y := y.(*ast.BasicLit)
+ xval := exact.MakeFromLiteral(x.Value, x.Kind)
+ yval := exact.MakeFromLiteral(y.Value, y.Kind)
+ return exact.Compare(xval, token.EQL, yval)
+
+ case *ast.FuncLit:
+ // func literals (and thus statement syntax) never match.
+ return false
+
+ case *ast.CompositeLit:
+ y := y.(*ast.CompositeLit)
+ return (x.Type == nil) == (y.Type == nil) &&
+ (x.Type == nil || tr.matchType(x.Type, y.Type)) &&
+ tr.matchExprs(x.Elts, y.Elts)
+
+ case *ast.SelectorExpr:
+ y := y.(*ast.SelectorExpr)
+ return tr.matchExpr(x.X, y.X) &&
+ tr.info.Selections[x].Obj() == tr.info.Selections[y].Obj()
+
+ case *ast.IndexExpr:
+ y := y.(*ast.IndexExpr)
+ return tr.matchExpr(x.X, y.X) &&
+ tr.matchExpr(x.Index, y.Index)
+
+ case *ast.SliceExpr:
+ y := y.(*ast.SliceExpr)
+ return tr.matchExpr(x.X, y.X) &&
+ tr.matchExpr(x.Low, y.Low) &&
+ tr.matchExpr(x.High, y.High) &&
+ tr.matchExpr(x.Max, y.Max) &&
+ x.Slice3 == y.Slice3
+
+ case *ast.TypeAssertExpr:
+ y := y.(*ast.TypeAssertExpr)
+ return tr.matchExpr(x.X, y.X) &&
+ tr.matchType(x.Type, y.Type)
+
+ case *ast.CallExpr:
+ y := y.(*ast.CallExpr)
+ match := tr.matchExpr // function call
+ if tr.info.IsType(x.Fun) {
+ match = tr.matchType // type conversion
+ }
+ return x.Ellipsis.IsValid() == y.Ellipsis.IsValid() &&
+ match(x.Fun, y.Fun) &&
+ tr.matchExprs(x.Args, y.Args)
+
+ case *ast.StarExpr:
+ y := y.(*ast.StarExpr)
+ return tr.matchExpr(x.X, y.X)
+
+ case *ast.UnaryExpr:
+ y := y.(*ast.UnaryExpr)
+ return x.Op == y.Op &&
+ tr.matchExpr(x.X, y.X)
+
+ case *ast.BinaryExpr:
+ y := y.(*ast.BinaryExpr)
+ return x.Op == y.Op &&
+ tr.matchExpr(x.X, y.X) &&
+ tr.matchExpr(x.Y, y.Y)
+
+ case *ast.KeyValueExpr:
+ y := y.(*ast.KeyValueExpr)
+ return tr.matchExpr(x.Key, y.Key) &&
+ tr.matchExpr(x.Value, y.Value)
+ }
+
+ panic(fmt.Sprintf("unhandled AST node type: %T", x))
+}
+
+func (tr *Transformer) matchExprs(xx, yy []ast.Expr) bool {
+ if len(xx) != len(yy) {
+ return false
+ }
+ for i := range xx {
+ if !tr.matchExpr(xx[i], yy[i]) {
+ return false
+ }
+ }
+ return true
+}
+
+// matchType reports whether the two type ASTs denote identical types.
+func (tr *Transformer) matchType(x, y ast.Expr) bool {
+ tx := tr.info.Types[x].Type
+ ty := tr.info.Types[y].Type
+ return types.Identical(tx, ty)
+}
+
+func (tr *Transformer) matchWildcard(xobj *types.Var, y ast.Expr) bool {
+ name := xobj.Name()
+
+ if tr.verbose {
+ fmt.Fprintf(os.Stderr, "%s: wildcard %s -> %s?: ",
+ tr.fset.Position(y.Pos()), name, astString(tr.fset, y))
+ }
+
+ // Check that y is assignable to the declared type of the param.
+ if yt := tr.info.TypeOf(y); !types.AssignableTo(yt, xobj.Type()) {
+ if tr.verbose {
+ fmt.Fprintf(os.Stderr, "%s not assignable to %s\n", yt, xobj.Type())
+ }
+ return false
+ }
+
+ // A wildcard matches any expression.
+ // If it appears multiple times in the pattern, it must match
+ // the same expression each time.
+ if old, ok := tr.env[name]; ok {
+ // found existing binding
+ tr.allowWildcards = false
+ r := tr.matchExpr(old, y)
+ if tr.verbose {
+ fmt.Fprintf(os.Stderr, "%t secondary match, primary was %s\n",
+ r, astString(tr.fset, old))
+ }
+ tr.allowWildcards = true
+ return r
+ }
+
+ if tr.verbose {
+ fmt.Fprintf(os.Stderr, "primary match\n")
+ }
+
+ tr.env[name] = y // record binding
+ return true
+}
+
+// -- utilities --------------------------------------------------------
+
+// unparen returns e with any enclosing parentheses stripped.
+// TODO(adonovan): move to astutil package.
+func unparen(e ast.Expr) ast.Expr {
+ for {
+ p, ok := e.(*ast.ParenExpr)
+ if !ok {
+ break
+ }
+ e = p.X
+ }
+ return e
+}
+
+// isRef returns the object referred to by this (possibly qualified)
+// identifier, or nil if the node is not a referring identifier.
+func isRef(n ast.Node, info *loader.PackageInfo) types.Object {
+ switch n := n.(type) {
+ case *ast.Ident:
+ return info.Uses[n]
+
+ case *ast.SelectorExpr:
+ sel := info.Selections[n]
+ if sel.Kind() == types.PackageObj {
+ return sel.Obj()
+ }
+ }
+ return nil
+}
diff --git a/refactor/eg/rewrite.go b/refactor/eg/rewrite.go
new file mode 100644
index 0000000..92921c4
--- /dev/null
+++ b/refactor/eg/rewrite.go
@@ -0,0 +1,347 @@
+package eg
+
+// This file defines the AST rewriting pass.
+// Most of it was plundered directly from
+// $GOROOT/src/cmd/gofmt/rewrite.go (after convergent evolution).
+
+import (
+ "fmt"
+ "go/ast"
+ "go/token"
+ "os"
+ "reflect"
+ "sort"
+ "strconv"
+ "strings"
+
+ "code.google.com/p/go.tools/astutil"
+ "code.google.com/p/go.tools/go/types"
+)
+
+// Transform applies the transformation to the specified parsed file,
+// whose type information is supplied in info, and returns the number
+// of replacements that were made.
+//
+// It mutates the AST in place (the identity of the root node is
+// unchanged), and may add nodes for which no type information is
+// available in info.
+//
+// Derived from rewriteFile in $GOROOT/src/cmd/gofmt/rewrite.go.
+//
+func (tr *Transformer) Transform(info *types.Info, pkg *types.Package, file *ast.File) int {
+ if !tr.seenInfos[info] {
+ tr.seenInfos[info] = true
+ mergeTypeInfo(&tr.info.Info, info)
+ }
+ tr.currentPkg = pkg
+ tr.nsubsts = 0
+
+ if tr.verbose {
+ fmt.Fprintf(os.Stderr, "before: %s\n", astString(tr.fset, tr.before))
+ fmt.Fprintf(os.Stderr, "after: %s\n", astString(tr.fset, tr.after))
+ }
+
+ var f func(rv reflect.Value) reflect.Value
+ f = func(rv reflect.Value) reflect.Value {
+ // don't bother if val is invalid to start with
+ if !rv.IsValid() {
+ return reflect.Value{}
+ }
+
+ rv = apply(f, rv)
+
+ e := rvToExpr(rv)
+ if e != nil {
+ savedEnv := tr.env
+ tr.env = make(map[string]ast.Expr) // inefficient! Use a slice of k/v pairs
+
+ if tr.matchExpr(tr.before, e) {
+ if tr.verbose {
+ fmt.Fprintf(os.Stderr, "%s matches %s",
+ astString(tr.fset, tr.before), astString(tr.fset, e))
+ if len(tr.env) > 0 {
+ fmt.Fprintf(os.Stderr, " with:")
+ for name, ast := range tr.env {
+ fmt.Fprintf(os.Stderr, " %s->%s",
+ name, astString(tr.fset, ast))
+ }
+ }
+ fmt.Fprintf(os.Stderr, "\n")
+ }
+ tr.nsubsts++
+
+ // Clone the replacement tree, performing parameter substitution.
+ // We update all positions to n.Pos() to aid comment placement.
+ rv = tr.subst(tr.env, reflect.ValueOf(tr.after),
+ reflect.ValueOf(e.Pos()))
+ }
+ tr.env = savedEnv
+ }
+
+ return rv
+ }
+ file2 := apply(f, reflect.ValueOf(file)).Interface().(*ast.File)
+
+ // By construction, the root node is unchanged.
+ if file != file2 {
+ panic("BUG")
+ }
+
+ // Add any necessary imports.
+ // TODO(adonovan): remove no-longer needed imports too.
+ if tr.nsubsts > 0 {
+ pkgs := make(map[string]*types.Package)
+ for obj := range tr.importedObjs {
+ pkgs[obj.Pkg().Path()] = obj.Pkg()
+ }
+
+ for _, imp := range file.Imports {
+ path, _ := strconv.Unquote(imp.Path.Value)
+ delete(pkgs, path)
+ }
+ delete(pkgs, pkg.Path()) // don't import self
+
+ // NB: AddImport may completely replace the AST!
+ // It thus renders info and tr.info no longer relevant to file.
+ var paths []string
+ for path := range pkgs {
+ paths = append(paths, path)
+ }
+ sort.Strings(paths)
+ for _, path := range paths {
+ astutil.AddImport(tr.fset, file, path)
+ }
+ }
+
+ tr.currentPkg = nil
+
+ return tr.nsubsts
+}
+
+// setValue is a wrapper for x.SetValue(y); it protects
+// the caller from panics if x cannot be changed to y.
+func setValue(x, y reflect.Value) {
+ // don't bother if y is invalid to start with
+ if !y.IsValid() {
+ return
+ }
+ defer func() {
+ if x := recover(); x != nil {
+ if s, ok := x.(string); ok &&
+ (strings.Contains(s, "type mismatch") || strings.Contains(s, "not assignable")) {
+ // x cannot be set to y - ignore this rewrite
+ return
+ }
+ panic(x)
+ }
+ }()
+ x.Set(y)
+}
+
+// Values/types for special cases.
+var (
+ objectPtrNil = reflect.ValueOf((*ast.Object)(nil))
+ scopePtrNil = reflect.ValueOf((*ast.Scope)(nil))
+
+ identType = reflect.TypeOf((*ast.Ident)(nil))
+ selectorExprType = reflect.TypeOf((*ast.SelectorExpr)(nil))
+ objectPtrType = reflect.TypeOf((*ast.Object)(nil))
+ positionType = reflect.TypeOf(token.NoPos)
+ callExprType = reflect.TypeOf((*ast.CallExpr)(nil))
+ scopePtrType = reflect.TypeOf((*ast.Scope)(nil))
+)
+
+// apply replaces each AST field x in val with f(x), returning val.
+// To avoid extra conversions, f operates on the reflect.Value form.
+func apply(f func(reflect.Value) reflect.Value, val reflect.Value) reflect.Value {
+ if !val.IsValid() {
+ return reflect.Value{}
+ }
+
+ // *ast.Objects introduce cycles and are likely incorrect after
+ // rewrite; don't follow them but replace with nil instead
+ if val.Type() == objectPtrType {
+ return objectPtrNil
+ }
+
+ // similarly for scopes: they are likely incorrect after a rewrite;
+ // replace them with nil
+ if val.Type() == scopePtrType {
+ return scopePtrNil
+ }
+
+ switch v := reflect.Indirect(val); v.Kind() {
+ case reflect.Slice:
+ for i := 0; i < v.Len(); i++ {
+ e := v.Index(i)
+ setValue(e, f(e))
+ }
+ case reflect.Struct:
+ for i := 0; i < v.NumField(); i++ {
+ e := v.Field(i)
+ setValue(e, f(e))
+ }
+ case reflect.Interface:
+ e := v.Elem()
+ setValue(v, f(e))
+ }
+ return val
+}
+
+// subst returns a copy of (replacement) pattern with values from env
+// substituted in place of wildcards and pos used as the position of
+// tokens from the pattern. if env == nil, subst returns a copy of
+// pattern and doesn't change the line number information.
+func (tr *Transformer) subst(env map[string]ast.Expr, pattern, pos reflect.Value) reflect.Value {
+ if !pattern.IsValid() {
+ return reflect.Value{}
+ }
+
+ // *ast.Objects introduce cycles and are likely incorrect after
+ // rewrite; don't follow them but replace with nil instead
+ if pattern.Type() == objectPtrType {
+ return objectPtrNil
+ }
+
+ // similarly for scopes: they are likely incorrect after a rewrite;
+ // replace them with nil
+ if pattern.Type() == scopePtrType {
+ return scopePtrNil
+ }
+
+ // Wildcard gets replaced with map value.
+ if env != nil && pattern.Type() == identType {
+ id := pattern.Interface().(*ast.Ident)
+ if old, ok := env[id.Name]; ok {
+ return tr.subst(nil, reflect.ValueOf(old), reflect.Value{})
+ }
+ }
+
+ // Emit qualified identifiers in the pattern by appropriate
+ // (possibly qualified) identifier in the input.
+ //
+ // The template cannot contain dot imports, so all identifiers
+ // for imported objects are explicitly qualified.
+ //
+ // We assume (unsoundly) that there are no dot or named
+ // imports in the input code, nor are any imported package
+ // names shadowed, so the usual normal qualified identifier
+ // syntax may be used.
+ // TODO(adonovan): fix: avoid this assumption.
+ //
+ // A refactoring may be applied to a package referenced by the
+ // template. Objects belonging to the current package are
+ // denoted by unqualified identifiers.
+ //
+ if tr.importedObjs != nil && pattern.Type() == selectorExprType {
+ obj := isRef(pattern.Interface().(*ast.SelectorExpr), &tr.info)
+ if obj != nil {
+ if sel, ok := tr.importedObjs[obj]; ok {
+ var id ast.Expr
+ if obj.Pkg() == tr.currentPkg {
+ id = sel.Sel // unqualified
+ } else {
+ id = sel // pkg-qualified
+ }
+
+ // Return a clone of id.
+ saved := tr.importedObjs
+ tr.importedObjs = nil // break cycle
+ r := tr.subst(nil, reflect.ValueOf(id), pos)
+ tr.importedObjs = saved
+ return r
+ }
+ }
+ }
+
+ if pos.IsValid() && pattern.Type() == positionType {
+ // use new position only if old position was valid in the first place
+ if old := pattern.Interface().(token.Pos); !old.IsValid() {
+ return pattern
+ }
+ return pos
+ }
+
+ // Otherwise copy.
+ switch p := pattern; p.Kind() {
+ case reflect.Slice:
+ v := reflect.MakeSlice(p.Type(), p.Len(), p.Len())
+ for i := 0; i < p.Len(); i++ {
+ v.Index(i).Set(tr.subst(env, p.Index(i), pos))
+ }
+ return v
+
+ case reflect.Struct:
+ v := reflect.New(p.Type()).Elem()
+ for i := 0; i < p.NumField(); i++ {
+ v.Field(i).Set(tr.subst(env, p.Field(i), pos))
+ }
+ return v
+
+ case reflect.Ptr:
+ v := reflect.New(p.Type()).Elem()
+ if elem := p.Elem(); elem.IsValid() {
+ v.Set(tr.subst(env, elem, pos).Addr())
+ }
+
+ // Duplicate type information for duplicated ast.Expr.
+ // All ast.Node implementations are *structs,
+ // so this case catches them all.
+ if e := rvToExpr(v); e != nil {
+ updateTypeInfo(&tr.info.Info, e, p.Interface().(ast.Expr))
+ }
+ return v
+
+ case reflect.Interface:
+ v := reflect.New(p.Type()).Elem()
+ if elem := p.Elem(); elem.IsValid() {
+ v.Set(tr.subst(env, elem, pos))
+ }
+ return v
+ }
+
+ return pattern
+}
+
+// -- utilitiies -------------------------------------------------------
+
+func rvToExpr(rv reflect.Value) ast.Expr {
+ if rv.CanInterface() {
+ if e, ok := rv.Interface().(ast.Expr); ok {
+ return e
+ }
+ }
+ return nil
+}
+
+// updateTypeInfo duplicates type information for the existing AST old
+// so that it also applies to duplicated AST new.
+func updateTypeInfo(info *types.Info, new, old ast.Expr) {
+ switch new := new.(type) {
+ case *ast.Ident:
+ orig := old.(*ast.Ident)
+ if obj, ok := info.Defs[orig]; ok {
+ info.Defs[new] = obj
+ }
+ if obj, ok := info.Uses[orig]; ok {
+ info.Uses[new] = obj
+ }
+
+ case *ast.SelectorExpr:
+ orig := old.(*ast.SelectorExpr)
+ if sel, ok := info.Selections[orig]; ok {
+ info.Selections[new] = sel
+ }
+ }
+
+ if tv, ok := info.Types[old]; ok {
+ info.Types[new] = tv
+ }
+}
+
+func F() {}
+func G() {}
+
+func init() {
+ F()
+}
diff --git a/refactor/eg/testdata/A.template b/refactor/eg/testdata/A.template
new file mode 100644
index 0000000..f611961
--- /dev/null
+++ b/refactor/eg/testdata/A.template
@@ -0,0 +1,13 @@
+// +build ignore
+
+package template
+
+// Basic test of type-aware expression refactoring.
+
+import (
+ "errors"
+ "fmt"
+)
+
+func before(s string) error { return fmt.Errorf("%s", s) }
+func after(s string) error { return errors.New(s) }
diff --git a/refactor/eg/testdata/A1.go b/refactor/eg/testdata/A1.go
new file mode 100644
index 0000000..9e65eb3
--- /dev/null
+++ b/refactor/eg/testdata/A1.go
@@ -0,0 +1,51 @@
+// +build ignore
+
+package A1
+
+import (
+ . "fmt"
+ myfmt "fmt"
+ "os"
+ "strings"
+)
+
+func example(n int) {
+ x := "foo" + strings.Repeat("\t", n)
+ // Match, despite named import.
+ myfmt.Errorf("%s", x)
+
+ // Match, despite dot import.
+ Errorf("%s", x)
+
+ // Match: multiple matches in same function are possible.
+ myfmt.Errorf("%s", x)
+
+ // No match: wildcarded operand has the wrong type.
+ myfmt.Errorf("%s", 3)
+
+ // No match: function operand doesn't match.
+ myfmt.Printf("%s", x)
+
+ // No match again, dot import.
+ Printf("%s", x)
+
+ // Match.
+ myfmt.Fprint(os.Stderr, myfmt.Errorf("%s", x+"foo"))
+
+ // No match: though this literally matches the template,
+ // fmt doesn't resolve to a package here.
+ var fmt struct{ Errorf func(string, string) }
+ fmt.Errorf("%s", x)
+
+ // Recursive matching:
+
+ // Match: both matches are well-typed, so both succeed.
+ myfmt.Errorf("%s", myfmt.Errorf("%s", x+"foo").Error())
+
+ // Outer match succeeds, inner doesn't: 3 has wrong type.
+ myfmt.Errorf("%s", myfmt.Errorf("%s", 3).Error())
+
+ // Inner match succeeds, outer doesn't: the inner replacement
+ // has the wrong type (error not string).
+ myfmt.Errorf("%s", myfmt.Errorf("%s", x+"foo"))
+}
diff --git a/refactor/eg/testdata/A1.golden b/refactor/eg/testdata/A1.golden
new file mode 100644
index 0000000..4f7ba82
--- /dev/null
+++ b/refactor/eg/testdata/A1.golden
@@ -0,0 +1,52 @@
+// +build ignore
+
+package A1
+
+import (
+ . "fmt"
+ "errors"
+ myfmt "fmt"
+ "os"
+ "strings"
+)
+
+func example(n int) {
+ x := "foo" + strings.Repeat("\t", n)
+ // Match, despite named import.
+ errors.New(x)
+
+ // Match, despite dot import.
+ errors.New(x)
+
+ // Match: multiple matches in same function are possible.
+ errors.New(x)
+
+ // No match: wildcarded operand has the wrong type.
+ myfmt.Errorf("%s", 3)
+
+ // No match: function operand doesn't match.
+ myfmt.Printf("%s", x)
+
+ // No match again, dot import.
+ Printf("%s", x)
+
+ // Match.
+ myfmt.Fprint(os.Stderr, errors.New(x+"foo"))
+
+ // No match: though this literally matches the template,
+ // fmt doesn't resolve to a package here.
+ var fmt struct{ Errorf func(string, string) }
+ fmt.Errorf("%s", x)
+
+ // Recursive matching:
+
+ // Match: both matches are well-typed, so both succeed.
+ errors.New(errors.New(x + "foo").Error())
+
+ // Outer match succeeds, inner doesn't: 3 has wrong type.
+ errors.New(myfmt.Errorf("%s", 3).Error())
+
+ // Inner match succeeds, outer doesn't: the inner replacement
+ // has the wrong type (error not string).
+ myfmt.Errorf("%s", errors.New(x+"foo"))
+}
diff --git a/refactor/eg/testdata/A2.go b/refactor/eg/testdata/A2.go
new file mode 100644
index 0000000..3ae29ad
--- /dev/null
+++ b/refactor/eg/testdata/A2.go
@@ -0,0 +1,12 @@
+// +build ignore
+
+package A2
+
+// This refactoring causes addition of "errors" import.
+// TODO(adonovan): fix: it should also remove "fmt".
+
+import myfmt "fmt"
+
+func example(n int) {
+ myfmt.Errorf("%s", "")
+}
diff --git a/refactor/eg/testdata/A2.golden b/refactor/eg/testdata/A2.golden
new file mode 100644
index 0000000..5c2384b
--- /dev/null
+++ b/refactor/eg/testdata/A2.golden
@@ -0,0 +1,15 @@
+// +build ignore
+
+package A2
+
+// This refactoring causes addition of "errors" import.
+// TODO(adonovan): fix: it should also remove "fmt".
+
+import (
+ myfmt "fmt"
+ "errors"
+)
+
+func example(n int) {
+ errors.New("")
+}
diff --git a/refactor/eg/testdata/B.template b/refactor/eg/testdata/B.template
new file mode 100644
index 0000000..c16627b
--- /dev/null
+++ b/refactor/eg/testdata/B.template
@@ -0,0 +1,9 @@
+package template
+
+// Basic test of expression refactoring.
+// (Types are not important in this case; it could be done with gofmt -r.)
+
+import "time"
+
+func before(t time.Time) time.Duration { return time.Now().Sub(t) }
+func after(t time.Time) time.Duration { return time.Since(t) }
diff --git a/refactor/eg/testdata/B1.go b/refactor/eg/testdata/B1.go
new file mode 100644
index 0000000..8b52546
--- /dev/null
+++ b/refactor/eg/testdata/B1.go
@@ -0,0 +1,17 @@
+// +build ignore
+
+package B1
+
+import "time"
+
+var startup = time.Now()
+
+func example() time.Duration {
+ before := time.Now()
+ time.Sleep(1)
+ return time.Now().Sub(before)
+}
+
+func msSinceStartup() int64 {
+ return int64(time.Now().Sub(startup) / time.Millisecond)
+}
diff --git a/refactor/eg/testdata/B1.golden b/refactor/eg/testdata/B1.golden
new file mode 100644
index 0000000..4d4da21
--- /dev/null
+++ b/refactor/eg/testdata/B1.golden
@@ -0,0 +1,17 @@
+// +build ignore
+
+package B1
+
+import "time"
+
+var startup = time.Now()
+
+func example() time.Duration {
+ before := time.Now()
+ time.Sleep(1)
+ return time.Since(before)
+}
+
+func msSinceStartup() int64 {
+ return int64(time.Since(startup) / time.Millisecond)
+}
diff --git a/refactor/eg/testdata/C.template b/refactor/eg/testdata/C.template
new file mode 100644
index 0000000..f6f94d4
--- /dev/null
+++ b/refactor/eg/testdata/C.template
@@ -0,0 +1,10 @@
+package template
+
+// Test of repeated use of wildcard in pattern.
+
+// NB: multiple patterns would be required to handle variants such as
+// s[:len(s)], s[x:len(s)], etc, since a wildcard can't match nothing at all.
+// TODO(adonovan): support multiple templates in a single pass.
+
+func before(s string) string { return s[:len(s)] }
+func after(s string) string { return s }
diff --git a/refactor/eg/testdata/C1.go b/refactor/eg/testdata/C1.go
new file mode 100644
index 0000000..523b388
--- /dev/null
+++ b/refactor/eg/testdata/C1.go
@@ -0,0 +1,22 @@
+// +build ignore
+
+package C1
+
+import "strings"
+
+func example() {
+ x := "foo"
+ println(x[:len(x)])
+
+ // Match, but the transformation is not sound w.r.t. possible side effects.
+ println(strings.Repeat("*", 3)[:len(strings.Repeat("*", 3))])
+
+ // No match, since second use of wildcard doesn't match first.
+ println(strings.Repeat("*", 3)[:len(strings.Repeat("*", 2))])
+
+ // Recursive match demonstrating bottom-up rewrite:
+ // only after the inner replacement occurs does the outer syntax match.
+ println((x[:len(x)])[:len(x[:len(x)])])
+ // -> (x[:len(x)])
+ // -> x
+}
diff --git a/refactor/eg/testdata/C1.golden b/refactor/eg/testdata/C1.golden
new file mode 100644
index 0000000..ae7759d
--- /dev/null
+++ b/refactor/eg/testdata/C1.golden
@@ -0,0 +1,22 @@
+// +build ignore
+
+package C1
+
+import "strings"
+
+func example() {
+ x := "foo"
+ println(x)
+
+ // Match, but the transformation is not sound w.r.t. possible side effects.
+ println(strings.Repeat("*", 3))
+
+ // No match, since second use of wildcard doesn't match first.
+ println(strings.Repeat("*", 3)[:len(strings.Repeat("*", 2))])
+
+ // Recursive match demonstrating bottom-up rewrite:
+ // only after the inner replacement occurs does the outer syntax match.
+ println(x)
+ // -> (x[:len(x)])
+ // -> x
+}
diff --git a/refactor/eg/testdata/D.template b/refactor/eg/testdata/D.template
new file mode 100644
index 0000000..6d3b6fe
--- /dev/null
+++ b/refactor/eg/testdata/D.template
@@ -0,0 +1,8 @@
+package template
+
+import "fmt"
+
+// Test of semantic (not syntactic) matching of basic literals.
+
+func before() (int, error) { return fmt.Println(123, "a") }
+func after() (int, error) { return fmt.Println(456, "!") }
diff --git a/refactor/eg/testdata/D1.go b/refactor/eg/testdata/D1.go
new file mode 100644
index 0000000..ae0a806
--- /dev/null
+++ b/refactor/eg/testdata/D1.go
@@ -0,0 +1,12 @@
+// +build ignore
+
+package D1
+
+import "fmt"
+
+func example() {
+ fmt.Println(123, "a") // match
+ fmt.Println(0x7b, `a`) // match
+ fmt.Println(0173, "\x61") // match
+ fmt.Println(100+20+3, "a"+"") // no match: constant expressions, but not basic literals
+}
diff --git a/refactor/eg/testdata/D1.golden b/refactor/eg/testdata/D1.golden
new file mode 100644
index 0000000..3f2dc59
--- /dev/null
+++ b/refactor/eg/testdata/D1.golden
@@ -0,0 +1,12 @@
+// +build ignore
+
+package D1
+
+import "fmt"
+
+func example() {
+ fmt.Println(456, "!") // match
+ fmt.Println(456, "!") // match
+ fmt.Println(456, "!") // match
+ fmt.Println(100+20+3, "a"+"") // no match: constant expressions, but not basic literals
+}
diff --git a/refactor/eg/testdata/E.template b/refactor/eg/testdata/E.template
new file mode 100644
index 0000000..4bbbd11
--- /dev/null
+++ b/refactor/eg/testdata/E.template
@@ -0,0 +1,12 @@
+package template
+
+import (
+ "fmt"
+ "log"
+ "os"
+)
+
+// Replace call to void function by call to non-void function.
+
+func before(x interface{}) { log.Fatal(x) }
+func after(x interface{}) { fmt.Fprintf(os.Stderr, "warning: %v", x) }
diff --git a/refactor/eg/testdata/E1.go b/refactor/eg/testdata/E1.go
new file mode 100644
index 0000000..3ea1793
--- /dev/null
+++ b/refactor/eg/testdata/E1.go
@@ -0,0 +1,9 @@
+// +build ignore
+
+package E1
+
+import "log"
+
+func example() {
+ log.Fatal("oops") // match
+}
diff --git a/refactor/eg/testdata/E1.golden b/refactor/eg/testdata/E1.golden
new file mode 100644
index 0000000..a0adfc8
--- /dev/null
+++ b/refactor/eg/testdata/E1.golden
@@ -0,0 +1,13 @@
+// +build ignore
+
+package E1
+
+import (
+ "log"
+ "os"
+ "fmt"
+)
+
+func example() {
+ fmt.Fprintf(os.Stderr, "warning: %v", "oops") // match
+}
diff --git a/refactor/eg/testdata/bad_type.template b/refactor/eg/testdata/bad_type.template
new file mode 100644
index 0000000..6d53d7e
--- /dev/null
+++ b/refactor/eg/testdata/bad_type.template
@@ -0,0 +1,8 @@
+package template
+
+// Test in which replacement has a different type.
+
+const shouldFail = "int is not a safe replacement for string"
+
+func before() interface{} { return "three" }
+func after() interface{} { return 3 }
diff --git a/refactor/eg/testdata/expr_type_mismatch.template b/refactor/eg/testdata/expr_type_mismatch.template
new file mode 100644
index 0000000..2c5c3f0
--- /dev/null
+++ b/refactor/eg/testdata/expr_type_mismatch.template
@@ -0,0 +1,15 @@
+package template
+
+import (
+ "crypto/x509"
+ "fmt"
+)
+
+// This test demonstrates a false negative: according to the language
+// rules this replacement should be ok, but types.Assignable doesn't work
+// in the expected way (elementwise assignability) for tuples.
+// Perhaps that's even a type-checker bug?
+const shouldFail = "(n int, err error) is not a safe replacement for (key interface{}, err error)"
+
+func before() (interface{}, error) { return x509.ParsePKCS8PrivateKey(nil) }
+func after() (interface{}, error) { return fmt.Print() }
diff --git a/refactor/eg/testdata/no_after_return.template b/refactor/eg/testdata/no_after_return.template
new file mode 100644
index 0000000..536b01e
--- /dev/null
+++ b/refactor/eg/testdata/no_after_return.template
@@ -0,0 +1,6 @@
+package template
+
+const shouldFail = "after: must contain a single statement"
+
+func before() int { return 0 }
+func after() int { println(); return 0 }
diff --git a/refactor/eg/testdata/no_before.template b/refactor/eg/testdata/no_before.template
new file mode 100644
index 0000000..9205e66
--- /dev/null
+++ b/refactor/eg/testdata/no_before.template
@@ -0,0 +1,5 @@
+package template
+
+const shouldFail = "no 'before' func found in template"
+
+func Before() {}
diff --git a/refactor/eg/testdata/type_mismatch.template b/refactor/eg/testdata/type_mismatch.template
new file mode 100644
index 0000000..787c9a7
--- /dev/null
+++ b/refactor/eg/testdata/type_mismatch.template
@@ -0,0 +1,6 @@
+package template
+
+const shouldFail = "different signatures"
+
+func before() int { return 0 }
+func after() string { return "" }