mirror of
https://github.com/golang/go
synced 2024-11-18 13:04:46 -07:00
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
This commit is contained in:
parent
d7048bec64
commit
bfcffc697d
121
cmd/eg/eg.go
Normal file
121
cmd/eg/eg.go
Normal file
@ -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
|
||||||
|
}
|
1
refactor/README
Normal file
1
refactor/README
Normal file
@ -0,0 +1 @@
|
|||||||
|
code.google.com/p/go.tools/refactor: libraries for refactoring tools.
|
326
refactor/eg/eg.go
Normal file
326
refactor/eg/eg.go
Normal file
@ -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()
|
||||||
|
}
|
145
refactor/eg/eg_test.go
Normal file
145
refactor/eg/eg_test.go
Normal file
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
226
refactor/eg/match.go
Normal file
226
refactor/eg/match.go
Normal file
@ -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
|
||||||
|
}
|
347
refactor/eg/rewrite.go
Normal file
347
refactor/eg/rewrite.go
Normal file
@ -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()
|
||||||
|
}
|
13
refactor/eg/testdata/A.template
vendored
Normal file
13
refactor/eg/testdata/A.template
vendored
Normal file
@ -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) }
|
51
refactor/eg/testdata/A1.go
vendored
Normal file
51
refactor/eg/testdata/A1.go
vendored
Normal file
@ -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"))
|
||||||
|
}
|
52
refactor/eg/testdata/A1.golden
vendored
Normal file
52
refactor/eg/testdata/A1.golden
vendored
Normal file
@ -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"))
|
||||||
|
}
|
12
refactor/eg/testdata/A2.go
vendored
Normal file
12
refactor/eg/testdata/A2.go
vendored
Normal file
@ -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", "")
|
||||||
|
}
|
15
refactor/eg/testdata/A2.golden
vendored
Normal file
15
refactor/eg/testdata/A2.golden
vendored
Normal file
@ -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("")
|
||||||
|
}
|
9
refactor/eg/testdata/B.template
vendored
Normal file
9
refactor/eg/testdata/B.template
vendored
Normal file
@ -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) }
|
17
refactor/eg/testdata/B1.go
vendored
Normal file
17
refactor/eg/testdata/B1.go
vendored
Normal file
@ -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)
|
||||||
|
}
|
17
refactor/eg/testdata/B1.golden
vendored
Normal file
17
refactor/eg/testdata/B1.golden
vendored
Normal file
@ -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)
|
||||||
|
}
|
10
refactor/eg/testdata/C.template
vendored
Normal file
10
refactor/eg/testdata/C.template
vendored
Normal file
@ -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 }
|
22
refactor/eg/testdata/C1.go
vendored
Normal file
22
refactor/eg/testdata/C1.go
vendored
Normal file
@ -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
|
||||||
|
}
|
22
refactor/eg/testdata/C1.golden
vendored
Normal file
22
refactor/eg/testdata/C1.golden
vendored
Normal file
@ -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
|
||||||
|
}
|
8
refactor/eg/testdata/D.template
vendored
Normal file
8
refactor/eg/testdata/D.template
vendored
Normal file
@ -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, "!") }
|
12
refactor/eg/testdata/D1.go
vendored
Normal file
12
refactor/eg/testdata/D1.go
vendored
Normal file
@ -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
|
||||||
|
}
|
12
refactor/eg/testdata/D1.golden
vendored
Normal file
12
refactor/eg/testdata/D1.golden
vendored
Normal file
@ -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
|
||||||
|
}
|
12
refactor/eg/testdata/E.template
vendored
Normal file
12
refactor/eg/testdata/E.template
vendored
Normal file
@ -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) }
|
9
refactor/eg/testdata/E1.go
vendored
Normal file
9
refactor/eg/testdata/E1.go
vendored
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
// +build ignore
|
||||||
|
|
||||||
|
package E1
|
||||||
|
|
||||||
|
import "log"
|
||||||
|
|
||||||
|
func example() {
|
||||||
|
log.Fatal("oops") // match
|
||||||
|
}
|
13
refactor/eg/testdata/E1.golden
vendored
Normal file
13
refactor/eg/testdata/E1.golden
vendored
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
// +build ignore
|
||||||
|
|
||||||
|
package E1
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
func example() {
|
||||||
|
fmt.Fprintf(os.Stderr, "warning: %v", "oops") // match
|
||||||
|
}
|
8
refactor/eg/testdata/bad_type.template
vendored
Normal file
8
refactor/eg/testdata/bad_type.template
vendored
Normal file
@ -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 }
|
15
refactor/eg/testdata/expr_type_mismatch.template
vendored
Normal file
15
refactor/eg/testdata/expr_type_mismatch.template
vendored
Normal file
@ -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() }
|
6
refactor/eg/testdata/no_after_return.template
vendored
Normal file
6
refactor/eg/testdata/no_after_return.template
vendored
Normal file
@ -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 }
|
5
refactor/eg/testdata/no_before.template
vendored
Normal file
5
refactor/eg/testdata/no_before.template
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
package template
|
||||||
|
|
||||||
|
const shouldFail = "no 'before' func found in template"
|
||||||
|
|
||||||
|
func Before() {}
|
6
refactor/eg/testdata/type_mismatch.template
vendored
Normal file
6
refactor/eg/testdata/type_mismatch.template
vendored
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
package template
|
||||||
|
|
||||||
|
const shouldFail = "different signatures"
|
||||||
|
|
||||||
|
func before() int { return 0 }
|
||||||
|
func after() string { return "" }
|
Loading…
Reference in New Issue
Block a user