mirror of
https://github.com/golang/go.git
synced 2025-05-05 15:43:04 +00:00
refactor/eg: fix build after deletion of x/tools/go/types
Change-Id: Ic8d9c37230b3359a3caa7514c89eae9595f2fb80 Reviewed-on: https://go-review.googlesource.com/21584 Reviewed-by: Robert Griesemer <gri@golang.org>
This commit is contained in:
parent
adaaa07486
commit
f07cde3d91
@ -1,347 +0,0 @@
|
||||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build !go1.5
|
||||
|
||||
// Package eg implements the example-based refactoring tool whose
|
||||
// command-line is defined in golang.org/x/tools/cmd/eg.
|
||||
package eg // import "golang.org/x/tools/refactor/eg"
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/format"
|
||||
"go/printer"
|
||||
"go/token"
|
||||
"os"
|
||||
|
||||
"golang.org/x/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 expression has no
|
||||
result, for example:
|
||||
|
||||
func before(msg string) { log.Fatalf("%s", msg) }
|
||||
func after(msg string) { log.Fatal(msg) }
|
||||
|
||||
The parameters of both functions are wildcards that may match any
|
||||
expression assignable to that type. If the pattern contains multiple
|
||||
occurrences of the same parameter, each must match the same expression
|
||||
in the input for the pattern to match. If the replacement contains
|
||||
multiple occurrences of the same parameter, the expression will be
|
||||
duplicated, possibly changing the side-effects.
|
||||
|
||||
The tool analyses all Go code in the packages specified by the
|
||||
arguments, replacing all occurrences of the pattern with the
|
||||
substitution.
|
||||
|
||||
So, the transform above would change this input:
|
||||
err := fmt.Errorf("%s", "error: " + msg)
|
||||
to this output:
|
||||
err := errors.New("error: " + msg)
|
||||
|
||||
Identifiers, including qualified identifiers (p.X) are considered to
|
||||
match only if they denote the same object. This allows correct
|
||||
matching even in the presence of dot imports, named imports and
|
||||
locally shadowed package names in the input program.
|
||||
|
||||
Matching of type syntax is semantic, not syntactic: type syntax in the
|
||||
pattern matches type syntax in the input if the types are identical.
|
||||
Thus, func(x int) matches func(y int).
|
||||
|
||||
This tool was inspired by other example-based refactoring tools,
|
||||
'gofmt -r' for Go and Refaster for Java.
|
||||
|
||||
|
||||
LIMITATIONS
|
||||
===========
|
||||
|
||||
EXPRESSIVENESS
|
||||
|
||||
Only refactorings that replace one expression with another, regardless
|
||||
of the expression's context, may be expressed. Refactoring arbitrary
|
||||
statements (or sequences of statements) is a less well-defined problem
|
||||
and is less amenable to this approach.
|
||||
|
||||
A pattern that contains a function literal (and hence statements)
|
||||
never matches.
|
||||
|
||||
There is no way to generalize over related types, e.g. to express that
|
||||
a wildcard may have any integer type, for example.
|
||||
|
||||
It is not possible to replace an expression by one of a different
|
||||
type, even in contexts where this is legal, such as x in fmt.Print(x).
|
||||
|
||||
The struct literals T{x} and T{K: x} cannot both be matched by a single
|
||||
template.
|
||||
|
||||
|
||||
SAFETY
|
||||
|
||||
Verifying that a transformation does not introduce type errors is very
|
||||
complex in the general case. An innocuous-looking replacement of one
|
||||
constant by another (e.g. 1 to 2) may cause type errors relating to
|
||||
array types and indices, for example. The tool performs only very
|
||||
superficial checks of type preservation.
|
||||
|
||||
|
||||
IMPORTS
|
||||
|
||||
Although the matching algorithm is fully aware of scoping rules, the
|
||||
replacement algorithm is not, so the replacement code may contain
|
||||
incorrect identifier syntax for imported objects if there are dot
|
||||
imports, named imports or locally shadowed package names in the input
|
||||
program.
|
||||
|
||||
Imports are added as needed, but they are not removed as needed.
|
||||
Run 'goimports' on the modified file for now.
|
||||
|
||||
Dot imports are forbidden in the template.
|
||||
|
||||
|
||||
TIPS
|
||||
====
|
||||
|
||||
Sometimes a little creativity is required to implement the desired
|
||||
migration. This section lists a few tips and tricks.
|
||||
|
||||
To remove the final parameter from a function, temporarily change the
|
||||
function signature so that the final parameter is variadic, as this
|
||||
allows legal calls both with and without the argument. Then use eg to
|
||||
remove the final argument from all callers, and remove the variadic
|
||||
parameter by hand. The reverse process can be used to add a final
|
||||
parameter.
|
||||
|
||||
To add or remove parameters other than the final one, you must do it in
|
||||
stages: (1) declare a variant function f' with a different name and the
|
||||
desired parameters; (2) use eg to transform calls to f into calls to f',
|
||||
changing the arguments as needed; (3) change the declaration of f to
|
||||
match f'; (4) use eg to rename f' to f in all calls; (5) delete f'.
|
||||
`
|
||||
|
||||
// TODO(adonovan): expand upon the above documentation as an HTML page.
|
||||
|
||||
// A Transformer represents a single example-based transformation.
|
||||
type Transformer struct {
|
||||
fset *token.FileSet
|
||||
verbose bool
|
||||
info *types.Info // combined type info for template/input/output ASTs
|
||||
seenInfos map[*types.Info]bool
|
||||
wildcards map[*types.Var]bool // set of parameters in func before()
|
||||
env map[string]ast.Expr // maps parameter name to wildcard binding
|
||||
importedObjs map[types.Object]*ast.SelectorExpr // objects imported by after().
|
||||
before, after ast.Expr
|
||||
allowWildcards bool
|
||||
|
||||
// Working state of Transform():
|
||||
nsubsts int // number of substitutions made
|
||||
currentPkg *types.Package // package of current call
|
||||
}
|
||||
|
||||
// NewTransformer returns a transformer based on the specified template,
|
||||
// a single-file package containing "before" and "after" functions as
|
||||
// described in the package documentation.
|
||||
// tmplInfo is the type information for tmplFile.
|
||||
//
|
||||
func NewTransformer(fset *token.FileSet, tmplPkg *types.Package, tmplFile *ast.File, tmplInfo *types.Info, verbose bool) (*Transformer, error) {
|
||||
// Check the template.
|
||||
beforeSig := funcSig(tmplPkg, "before")
|
||||
if beforeSig == nil {
|
||||
return nil, fmt.Errorf("no 'before' func found in template")
|
||||
}
|
||||
afterSig := funcSig(tmplPkg, "after")
|
||||
if afterSig == nil {
|
||||
return nil, fmt.Errorf("no 'after' func found in template")
|
||||
}
|
||||
|
||||
// TODO(adonovan): should we also check the names of the params match?
|
||||
if !types.Identical(afterSig, beforeSig) {
|
||||
return nil, fmt.Errorf("before %s and after %s functions have different signatures",
|
||||
beforeSig, afterSig)
|
||||
}
|
||||
|
||||
for _, imp := range tmplFile.Imports {
|
||||
if imp.Name != nil && imp.Name.Name == "." {
|
||||
// Dot imports are currently forbidden. We
|
||||
// make the simplifying assumption that all
|
||||
// imports are regular, without local renames.
|
||||
// TODO(adonovan): document
|
||||
return nil, fmt.Errorf("dot-import (of %s) in template", imp.Path.Value)
|
||||
}
|
||||
}
|
||||
var beforeDecl, afterDecl *ast.FuncDecl
|
||||
for _, decl := range tmplFile.Decls {
|
||||
if decl, ok := decl.(*ast.FuncDecl); ok {
|
||||
switch decl.Name.Name {
|
||||
case "before":
|
||||
beforeDecl = decl
|
||||
case "after":
|
||||
afterDecl = decl
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
before, err := soleExpr(beforeDecl)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("before: %s", err)
|
||||
}
|
||||
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 := tmplInfo.TypeOf(before)
|
||||
Ta := tmplInfo.TypeOf(after)
|
||||
if types.AssignableTo(Tb, Ta) {
|
||||
// safe: replacement is assignable to pattern.
|
||||
} else if tuple, ok := Tb.(*types.Tuple); ok && tuple.Len() == 0 {
|
||||
// safe: pattern has void type (must appear in an ExprStmt).
|
||||
} else {
|
||||
return nil, fmt.Errorf("%s is not a safe replacement for %s", Ta, Tb)
|
||||
}
|
||||
|
||||
tr := &Transformer{
|
||||
fset: fset,
|
||||
verbose: verbose,
|
||||
wildcards: wildcards,
|
||||
allowWildcards: true,
|
||||
seenInfos: make(map[*types.Info]bool),
|
||||
importedObjs: make(map[types.Object]*ast.SelectorExpr),
|
||||
before: before,
|
||||
after: after,
|
||||
}
|
||||
|
||||
// Combine type info from the template and input packages, and
|
||||
// type info for the synthesized ASTs too. This saves us
|
||||
// having to book-keep where each ast.Node originated as we
|
||||
// construct the resulting hybrid AST.
|
||||
tr.info = &types.Info{
|
||||
Types: make(map[ast.Expr]types.TypeAndValue),
|
||||
Defs: make(map[*ast.Ident]types.Object),
|
||||
Uses: make(map[*ast.Ident]types.Object),
|
||||
Selections: make(map[*ast.SelectorExpr]*types.Selection),
|
||||
}
|
||||
mergeTypeInfo(tr.info, tmplInfo)
|
||||
|
||||
// Compute set of imported objects required by after().
|
||||
// TODO(adonovan): reject dot-imports in pattern
|
||||
ast.Inspect(after, func(n ast.Node) bool {
|
||||
if n, ok := n.(*ast.SelectorExpr); ok {
|
||||
if _, ok := tr.info.Selections[n]; !ok {
|
||||
// qualified ident
|
||||
obj := tr.info.Uses[n.Sel]
|
||||
tr.importedObjs[obj] = n
|
||||
return false // prune
|
||||
}
|
||||
}
|
||||
return true // recur
|
||||
})
|
||||
|
||||
return tr, nil
|
||||
}
|
||||
|
||||
// WriteAST is a convenience function that writes AST f to the specified file.
|
||||
func WriteAST(fset *token.FileSet, filename string, f *ast.File) (err error) {
|
||||
fh, err := os.Create(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
if err2 := fh.Close(); err != nil {
|
||||
err = err2 // prefer earlier error
|
||||
}
|
||||
}()
|
||||
return format.Node(fh, fset, f)
|
||||
}
|
||||
|
||||
// -- utilities --------------------------------------------------------
|
||||
|
||||
// funcSig returns the signature of the specified package-level function.
|
||||
func funcSig(pkg *types.Package, name string) *types.Signature {
|
||||
if f, ok := pkg.Scope().Lookup(name).(*types.Func); ok {
|
||||
return f.Type().(*types.Signature)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// soleExpr returns the sole expression in the before/after template function.
|
||||
func soleExpr(fn *ast.FuncDecl) (ast.Expr, error) {
|
||||
if fn.Body == nil {
|
||||
return nil, fmt.Errorf("no body")
|
||||
}
|
||||
if len(fn.Body.List) != 1 {
|
||||
return nil, fmt.Errorf("must contain a single statement")
|
||||
}
|
||||
switch stmt := fn.Body.List[0].(type) {
|
||||
case *ast.ReturnStmt:
|
||||
if len(stmt.Results) != 1 {
|
||||
return nil, fmt.Errorf("return statement must have a single operand")
|
||||
}
|
||||
return stmt.Results[0], nil
|
||||
|
||||
case *ast.ExprStmt:
|
||||
return stmt.X, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("must contain a single return or expression statement")
|
||||
}
|
||||
|
||||
// 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()
|
||||
}
|
@ -1,162 +0,0 @@
|
||||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build !go1.5
|
||||
|
||||
// No testdata on Android.
|
||||
|
||||
// +build !android
|
||||
|
||||
package eg_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"flag"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"golang.org/x/tools/go/exact"
|
||||
"golang.org/x/tools/go/loader"
|
||||
"golang.org/x/tools/go/types"
|
||||
"golang.org/x/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,
|
||||
}
|
||||
|
||||
// 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/F.template",
|
||||
"testdata/F1.go",
|
||||
|
||||
"testdata/G.template",
|
||||
"testdata/G1.go",
|
||||
|
||||
"testdata/H.template",
|
||||
"testdata/H1.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")
|
||||
conf.CreateFromFilenames(pkgname, filename)
|
||||
}
|
||||
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.Pkg, file, &info.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)
|
||||
}
|
||||
defer os.Remove(got)
|
||||
|
||||
// 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", 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -9,6 +9,7 @@ package eg
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
exact "go/constant"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"log"
|
||||
@ -16,7 +17,6 @@ import (
|
||||
"reflect"
|
||||
|
||||
"golang.org/x/tools/go/ast/astutil"
|
||||
"golang.org/x/tools/go/exact"
|
||||
)
|
||||
|
||||
// matchExpr reports whether pattern x matches y.
|
||||
@ -69,8 +69,8 @@ func (tr *Transformer) matchExpr(x, y ast.Expr) bool {
|
||||
|
||||
case *ast.BasicLit:
|
||||
y := y.(*ast.BasicLit)
|
||||
xval := exact.MakeFromLiteral(x.Value, x.Kind)
|
||||
yval := exact.MakeFromLiteral(y.Value, y.Kind)
|
||||
xval := exact.MakeFromLiteral(x.Value, x.Kind, 0)
|
||||
yval := exact.MakeFromLiteral(y.Value, y.Kind, 0)
|
||||
return exact.Compare(xval, token.EQL, yval)
|
||||
|
||||
case *ast.FuncLit:
|
||||
|
@ -1,251 +0,0 @@
|
||||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build !go1.5
|
||||
|
||||
package eg
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/token"
|
||||
"log"
|
||||
"os"
|
||||
"reflect"
|
||||
|
||||
"golang.org/x/tools/go/ast/astutil"
|
||||
"golang.org/x/tools/go/exact"
|
||||
"golang.org/x/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 xobj, ok := tr.wildcardObj(x); ok {
|
||||
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.matchSelectorExpr(x, y) &&
|
||||
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.Types[x.Fun].IsType() {
|
||||
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) wildcardObj(x ast.Expr) (*types.Var, bool) {
|
||||
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 xobj, true
|
||||
}
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func (tr *Transformer) matchSelectorExpr(x, y *ast.SelectorExpr) bool {
|
||||
if xobj, ok := tr.wildcardObj(x.X); ok {
|
||||
field := x.Sel.Name
|
||||
yt := tr.info.TypeOf(y.X)
|
||||
o, _, _ := types.LookupFieldOrMethod(yt, true, tr.currentPkg, field)
|
||||
if o != nil {
|
||||
tr.env[xobj.Name()] = y.X // record binding
|
||||
return true
|
||||
}
|
||||
}
|
||||
return tr.matchExpr(x.X, y.X)
|
||||
}
|
||||
|
||||
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.
|
||||
yt := tr.info.TypeOf(y)
|
||||
if yt == nil {
|
||||
// y has no type.
|
||||
// Perhaps it is an *ast.Ellipsis in [...]T{}, or
|
||||
// an *ast.KeyValueExpr in T{k: v}.
|
||||
// Clearly these pseudo-expressions cannot match a
|
||||
// wildcard, but it would nice if we had a way to ignore
|
||||
// the difference between T{v} and T{k:v} for structs.
|
||||
return false
|
||||
}
|
||||
if !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 --------------------------------------------------------
|
||||
|
||||
func unparen(e ast.Expr) ast.Expr { return astutil.Unparen(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 *types.Info) types.Object {
|
||||
switch n := n.(type) {
|
||||
case *ast.Ident:
|
||||
return info.Uses[n]
|
||||
|
||||
case *ast.SelectorExpr:
|
||||
if _, ok := info.Selections[n]; !ok {
|
||||
// qualified ident
|
||||
return info.Uses[n.Sel]
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
@ -1,346 +0,0 @@
|
||||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build !go1.5
|
||||
|
||||
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"
|
||||
|
||||
"golang.org/x/tools/go/ast/astutil"
|
||||
"golang.org/x/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)
|
||||
}
|
||||
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, 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
|
||||
}
|
||||
|
||||
// -- utilities -------------------------------------------------------
|
||||
|
||||
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
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user