| // 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. |
| |
| package eg |
| |
| import ( |
| "fmt" |
| "go/ast" |
| "go/constant" |
| "go/token" |
| "go/types" |
| "log" |
| "os" |
| "reflect" |
| |
| "golang.org/x/tools/go/ast/astutil" |
| ) |
| |
| // 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 := constant.MakeFromLiteral(x.Value, x.Kind, 0) |
| yval := constant.MakeFromLiteral(y.Value, y.Kind, 0) |
| return constant.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 |
| } |