diff --git a/debug/debug.go b/debug/debug.go index 0d9142caf..3d9599a6d 100644 --- a/debug/debug.go +++ b/debug/debug.go @@ -9,6 +9,7 @@ import ( "go/parser" "go/token" "go/types" + "sync" ) // TypeCheck parses and type-checks a single-file Go package from a string. @@ -45,3 +46,17 @@ func FormatNode(node ast.Node) string { format.Node(&buf, fset, node) return buf.String() } + +var aliasesDefaultOnce sync.Once +var gotypesaliasDefault bool + +func AliasesEnabled() bool { + // Dynamically check if Aliases will be produced from go/types. + aliasesDefaultOnce.Do(func() { + fset := token.NewFileSet() + f, _ := parser.ParseFile(fset, "a.go", "package p; type A = int", 0) + pkg, _ := new(types.Config).Check("p", fset, []*ast.File{f}, nil) + _, gotypesaliasDefault = pkg.Scope().Lookup("A").Type().(*types.Alias) + }) + return gotypesaliasDefault +} diff --git a/pattern/match.go b/pattern/match.go index 3eae3bd63..4fb4f8ec6 100644 --- a/pattern/match.go +++ b/pattern/match.go @@ -586,13 +586,28 @@ func (fn Symbol) Match(m *Matcher, node interface{}) (interface{}, bool) { case *types.Builtin: name = obj.Name() case *types.TypeName: - if obj.Pkg() == nil { - return nil, false - } - if obj.Parent() != obj.Pkg().Scope() { - return nil, false + origObj := obj + for { + if obj.Parent() != obj.Pkg().Scope() { + return nil, false + } + name = types.TypeString(obj.Type(), nil) + _, ok = match(m, fn.Name, name) + if ok || !obj.IsAlias() { + return origObj, ok + } else { + // FIXME(dh): we should peel away one layer of alias at a time; this is blocked on + // github.com/golang/go/issues/66559 + switch typ := types.Unalias(obj.Type()).(type) { + case interface{ Obj() *types.TypeName }: + obj = typ.Obj() + case *types.Basic: + return match(m, fn.Name, typ.Name()) + default: + return nil, false + } + } } - name = types.TypeString(obj.Type(), nil) case *types.Const, *types.Var: if obj.Pkg() == nil { return nil, false diff --git a/pattern/parser_test.go b/pattern/parser_test.go index 027effc0b..dd2c9b85b 100644 --- a/pattern/parser_test.go +++ b/pattern/parser_test.go @@ -11,6 +11,8 @@ import ( "runtime" "strings" "testing" + + "honnef.co/go/tools/debug" ) func TestParse(t *testing.T) { @@ -112,3 +114,34 @@ func FuzzParse(f *testing.F) { } }) } + +func TestMatchAlias(t *testing.T) { + p1 := MustParse(`(CallExpr (Symbol "foo.Alias") _)`) + p2 := MustParse(`(CallExpr (Symbol "int") _)`) + + f, _, info, err := debug.TypeCheck(` +package pkg +type Alias = int +func _() { _ = Alias(0) } +`) + if err != nil { + t.Fatal(err) + } + + m := &Matcher{ + TypesInfo: info, + } + node := f.Decls[1].(*ast.FuncDecl).Body.List[0].(*ast.AssignStmt).Rhs[0] + + if debug.AliasesEnabled() { + // Check that we can match on the name of the alias + if ok := m.Match(p1, node); !ok { + t.Errorf("%s did not match", p1.Root) + } + } + + // Check that we can match on the name of the alias's target + if ok := m.Match(p2, node); !ok { + t.Errorf("%s did not match", p2.Root) + } +}