// Copyright 2020 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 source import ( "bytes" "go/ast" "go/parser" "go/token" "go/types" "testing" ) func TestSearchForEnclosing(t *testing.T) { tests := []struct { desc string // For convenience, consider the first occurence of the identifier "X" in // src. src string // By convention, "" means no type found. wantTypeName string }{ { desc: "self enclosing", src: `package a; type X struct {}`, wantTypeName: "X", }, { // TODO(rFindley): is this correct, or do we want to resolve I2 here? desc: "embedded interface in interface", src: `package a; var y = i1.X; type i1 interface {I2}; type I2 interface{X()}`, wantTypeName: "", }, { desc: "embedded interface in struct", src: `package a; var y = t.X; type t struct {I}; type I interface{X()}`, wantTypeName: "I", }, { desc: "double embedding", src: `package a; var y = t1.X; type t1 struct {t2}; type t2 struct {I}; type I interface{X()}`, wantTypeName: "I", }, { desc: "struct field", src: `package a; type T struct { X int }`, wantTypeName: "T", }, { desc: "nested struct field", src: `package a; type T struct { E struct { X int } }`, wantTypeName: "T", }, { desc: "slice entry", src: `package a; type T []int; var S = T{X}; var X int = 2`, wantTypeName: "T", }, { desc: "struct pointer literal", src: `package a; type T struct {i int}; var L = &T{X}; const X = 2`, wantTypeName: "T", }, } for _, test := range tests { test := test t.Run(test.desc, func(t *testing.T) { fset := token.NewFileSet() file, err := parser.ParseFile(fset, "a.go", test.src, parser.AllErrors) if err != nil { t.Fatal(err) } column := 1 + bytes.IndexRune([]byte(test.src), 'X') pos := posAt(1, column, fset, "a.go") path := pathEnclosingObjNode(file, pos) if path == nil { t.Fatalf("no ident found at (1, %d)", column) } info := newInfo() if _, err = (*types.Config)(nil).Check("p", fset, []*ast.File{file}, info); err != nil { t.Fatal(err) } typ := searchForEnclosing(info, path) if typ == nil { if test.wantTypeName != "" { t.Errorf("searchForEnclosing(...) = , want %q", test.wantTypeName) } return } if got := typ.(*types.Named).Obj().Name(); got != test.wantTypeName { t.Errorf("searchForEnclosing(...) = %q, want %q", got, test.wantTypeName) } }) } } // posAt returns the token.Pos corresponding to the 1-based (line, column) // coordinates in the file fname of fset. func posAt(line, column int, fset *token.FileSet, fname string) token.Pos { var tok *token.File fset.Iterate(func(f *token.File) bool { if f.Name() == fname { tok = f return false } return true }) if tok == nil { return token.NoPos } start := tok.LineStart(line) return start + token.Pos(column-1) } // newInfo returns a types.Info with all maps populated. func newInfo() *types.Info { return &types.Info{ Types: make(map[ast.Expr]types.TypeAndValue), Defs: make(map[*ast.Ident]types.Object), Uses: make(map[*ast.Ident]types.Object), Implicits: make(map[ast.Node]types.Object), Selections: make(map[*ast.SelectorExpr]*types.Selection), Scopes: make(map[ast.Node]*types.Scope), } }