// 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 }