internal/lsp: fix references for type switch vars

Implicit local variables for type switches do not appear in the Uses
map and do not have objects associated with them.  This change
associates all of the different types objects for the same local type
switch declaration with one another in the declaration.

The identifier for the implicit local variable does not have a type but
does have declaration objects.

Find references for type switch vars will return references to all the
identifiers in all of the case clauses and the declaration.

Fixes golang/go#32584

Change-Id: I5563a2a48d31ca615c1e4e73b46eabca0f5dd72a
Reviewed-on: https://go-review.googlesource.com/c/tools/+/182462
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
This commit is contained in:
Suzy Mueller 2019-06-14 14:55:24 -04:00
parent 8b2b8cf54a
commit b76e30ffa0
6 changed files with 169 additions and 26 deletions

View File

@ -36,6 +36,7 @@ type declaration struct {
rng span.Range
node ast.Node
obj types.Object
wasImplicit bool
}
func (i *IdentifierInfo) DeclarationRange() span.Range {
@ -103,8 +104,20 @@ func identifier(ctx context.Context, v View, f GoFile, pos token.Pos) (*Identifi
result.Range = span.NewRange(f.FileSet(), result.ident.Pos(), result.ident.End())
result.decl.obj = pkg.GetTypesInfo().ObjectOf(result.ident)
if result.decl.obj == nil {
// If there was no types.Object for the declaration, there might be an implicit local variable
// declaration in a type switch.
if objs := typeSwitchVar(pkg.GetTypesInfo(), path); len(objs) > 0 {
// There is no types.Object for the declaration of an implicit local variable,
// but all of the types.Objects associated with the usages of this variable can be
// used to connect it back to the declaration.
// Preserve the first of these objects and treat it as if it were the declaring object.
result.decl.obj = objs[0]
result.decl.wasImplicit = true
} else {
// Probably a type error.
return nil, fmt.Errorf("no object for ident %v", result.Name)
}
}
var err error
@ -131,6 +144,15 @@ func identifier(ctx context.Context, v View, f GoFile, pos token.Pos) (*Identifi
}
}
for _, obj := range pkg.GetTypesInfo().Implicits {
if obj.Pos() == result.decl.obj.Pos() {
// Mark this declaration as implicit, since it will not
// appear in a (*types.Info).Defs map.
result.decl.wasImplicit = true
break
}
}
if result.decl.rng, err = objToRange(ctx, f.FileSet(), result.decl.obj); err != nil {
return nil, err
}
@ -139,8 +161,9 @@ func identifier(ctx context.Context, v View, f GoFile, pos token.Pos) (*Identifi
}
typ := pkg.GetTypesInfo().TypeOf(result.ident)
if typ == nil {
return nil, fmt.Errorf("no type for %s", result.Name)
return result, nil
}
result.Type.Object = typeToObject(typ)
if result.Type.Object != nil {
// Identifiers with the type "error" are a special case with no position.
@ -170,6 +193,21 @@ func hasErrorType(obj types.Object) bool {
}
func objToRange(ctx context.Context, fset *token.FileSet, obj types.Object) (span.Range, error) {
if pkgName, ok := obj.(*types.PkgName); ok {
// An imported Go package has a package-local, unqualified name.
// When the name matches the imported package name, there is no
// identifier in the import spec with the local package name.
//
// For example:
// import "go/ast" // name "ast" matches package name
// import a "go/ast" // name "a" does not match package name
//
// When the identifier does not appear in the source, have the range
// of the object be the point at the beginning of the declaration.
if pkgName.Imported().Name() == pkgName.Name() {
return posToRange(ctx, fset, "", obj.Pos())
}
}
return posToRange(ctx, fset, obj.Name(), obj.Pos())
}
@ -261,3 +299,32 @@ func importSpec(f GoFile, fAST *ast.File, pkg Package, pos token.Pos) (*Identifi
}
return nil, nil
}
// typeSwitchVar handles the special case of a local variable implicitly defined in a type switch.
// In such cases, the definition of the implicit variable will not be recorded in the *types.Info.Defs map,
// but rather in the *types.Info.Implicits map.
func typeSwitchVar(info *types.Info, path []ast.Node) []types.Object {
if len(path) < 3 {
return nil
}
// Check for [Ident AssignStmt TypeSwitchStmt...]
if _, ok := path[0].(*ast.Ident); !ok {
return nil
}
if _, ok := path[1].(*ast.AssignStmt); !ok {
return nil
}
sw, ok := path[2].(*ast.TypeSwitchStmt)
if !ok {
return nil
}
var res []types.Object
for _, stmt := range sw.Body.List {
obj := info.Implicits[stmt.(*ast.CaseClause)]
if obj != nil {
res = append(res, obj)
}
}
return res
}

View File

@ -27,30 +27,43 @@ func (i *IdentifierInfo) References(ctx context.Context) ([]*ReferenceInfo, erro
}
// If the object declaration is nil, assume it is an import spec and do not look for references.
declObj := i.decl.obj
if declObj == nil {
if i.decl.obj == nil {
return []*ReferenceInfo{}, nil
}
var references []*ReferenceInfo
if i.decl.wasImplicit {
// The definition is implicit, so we must add it separately.
// This occurs when the variable is declared in a type switch statement
// or is an implicit package name.
references = append(references, &ReferenceInfo{
Name: i.decl.obj.Name(),
Range: i.decl.rng,
})
}
for ident, obj := range pkgInfo.Defs {
if obj == declObj {
if obj == nil || obj.Pos() != i.decl.obj.Pos() {
continue
}
references = append(references, &ReferenceInfo{
Name: ident.Name,
Range: span.NewRange(i.File.FileSet(), ident.Pos(), ident.End()),
ident: ident,
})
}
}
for ident, obj := range pkgInfo.Uses {
if obj == declObj {
if obj == nil || obj.Pos() != i.decl.obj.Pos() {
continue
}
references = append(references, &ReferenceInfo{
Name: ident.Name,
Range: span.NewRange(i.File.FileSet(), ident.Pos(), ident.End()),
ident: ident,
})
}
}
return references, nil
}

15
internal/lsp/testdata/godef/a/f.go vendored Normal file
View File

@ -0,0 +1,15 @@
package a
import "fmt"
func TypeStuff() { //@Stuff
var x string
switch y := interface{}(x).(type) { //@mark(switchY, "y"),mark(switchStringY,"y"),godef("y", switchY)
case int:
fmt.Printf("%v", y) //@godef("y", switchY)
case string:
fmt.Printf("%v", y) //@godef("y", switchStringY)
}
}

View File

@ -0,0 +1,48 @@
-- switchY-definition --
godef/a/f.go:8:9-10: defined here as var y int
-- switchY-definition-json --
{
"span": {
"uri": "file://godef/a/f.go",
"start": {
"line": 8,
"column": 9,
"offset": 76
},
"end": {
"line": 8,
"column": 10,
"offset": 77
}
},
"description": "var y int"
}
-- switchY-hover --
var y int
-- switchStringY-definition --
godef/a/f.go:8:9-10: defined here as var y string
-- switchStringY-definition-json --
{
"span": {
"uri": "file://godef/a/f.go",
"start": {
"line": 8,
"column": 9,
"offset": 76
},
"end": {
"line": 8,
"column": 10,
"offset": 77
}
},
"description": "var y string"
}
-- switchStringY-hover --
var y string

View File

@ -23,7 +23,7 @@ godef/a/a.go:7:6-7: defined here as A string //@A
A string //@A
-- AImport-definition --
godef/b/b.go:5:2-3: defined here as package a ("golang.org/x/tools/internal/lsp/godef/a")
godef/b/b.go:5:2: defined here as package a ("golang.org/x/tools/internal/lsp/godef/a")
-- AImport-definition-json --
{
@ -36,8 +36,8 @@ godef/b/b.go:5:2-3: defined here as package a ("golang.org/x/tools/internal/lsp/
},
"end": {
"line": 5,
"column": 3,
"offset": 122
"column": 2,
"offset": 121
}
},
"description": "package a (\"golang.org/x/tools/internal/lsp/godef/a\")"

View File

@ -30,7 +30,7 @@ const (
ExpectedDiagnosticsCount = 17
ExpectedFormatCount = 5
ExpectedImportCount = 2
ExpectedDefinitionsCount = 35
ExpectedDefinitionsCount = 38
ExpectedTypeDefinitionsCount = 2
ExpectedHighlightsCount = 2
ExpectedReferencesCount = 2