mirror of
https://github.com/golang/go.git
synced 2025-05-05 23:53:05 +00:00
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:
parent
8b2b8cf54a
commit
b76e30ffa0
@ -33,9 +33,10 @@ type IdentifierInfo struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type declaration struct {
|
type declaration struct {
|
||||||
rng span.Range
|
rng span.Range
|
||||||
node ast.Node
|
node ast.Node
|
||||||
obj types.Object
|
obj types.Object
|
||||||
|
wasImplicit bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *IdentifierInfo) DeclarationRange() span.Range {
|
func (i *IdentifierInfo) DeclarationRange() span.Range {
|
||||||
@ -103,7 +104,19 @@ 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.Range = span.NewRange(f.FileSet(), result.ident.Pos(), result.ident.End())
|
||||||
result.decl.obj = pkg.GetTypesInfo().ObjectOf(result.ident)
|
result.decl.obj = pkg.GetTypesInfo().ObjectOf(result.ident)
|
||||||
if result.decl.obj == nil {
|
if result.decl.obj == nil {
|
||||||
return nil, fmt.Errorf("no object for ident %v", result.Name)
|
// 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
|
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 {
|
if result.decl.rng, err = objToRange(ctx, f.FileSet(), result.decl.obj); err != nil {
|
||||||
return nil, err
|
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)
|
typ := pkg.GetTypesInfo().TypeOf(result.ident)
|
||||||
if typ == nil {
|
if typ == nil {
|
||||||
return nil, fmt.Errorf("no type for %s", result.Name)
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
result.Type.Object = typeToObject(typ)
|
result.Type.Object = typeToObject(typ)
|
||||||
if result.Type.Object != nil {
|
if result.Type.Object != nil {
|
||||||
// Identifiers with the type "error" are a special case with no position.
|
// 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) {
|
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())
|
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
|
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
|
||||||
|
}
|
||||||
|
@ -27,29 +27,42 @@ 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.
|
// If the object declaration is nil, assume it is an import spec and do not look for references.
|
||||||
declObj := i.decl.obj
|
if i.decl.obj == nil {
|
||||||
if declObj == nil {
|
|
||||||
return []*ReferenceInfo{}, nil
|
return []*ReferenceInfo{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
var references []*ReferenceInfo
|
var references []*ReferenceInfo
|
||||||
for ident, obj := range pkgInfo.Defs {
|
|
||||||
if obj == declObj {
|
if i.decl.wasImplicit {
|
||||||
references = append(references, &ReferenceInfo{
|
// The definition is implicit, so we must add it separately.
|
||||||
Name: ident.Name,
|
// This occurs when the variable is declared in a type switch statement
|
||||||
Range: span.NewRange(i.File.FileSet(), ident.Pos(), ident.End()),
|
// or is an implicit package name.
|
||||||
ident: ident,
|
references = append(references, &ReferenceInfo{
|
||||||
})
|
Name: i.decl.obj.Name(),
|
||||||
}
|
Range: i.decl.rng,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
for ident, obj := range pkgInfo.Uses {
|
|
||||||
if obj == declObj {
|
for ident, obj := range pkgInfo.Defs {
|
||||||
references = append(references, &ReferenceInfo{
|
if obj == nil || obj.Pos() != i.decl.obj.Pos() {
|
||||||
Name: ident.Name,
|
continue
|
||||||
Range: span.NewRange(i.File.FileSet(), ident.Pos(), ident.End()),
|
|
||||||
ident: ident,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
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 == 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
|
return references, nil
|
||||||
|
15
internal/lsp/testdata/godef/a/f.go
vendored
Normal file
15
internal/lsp/testdata/godef/a/f.go
vendored
Normal 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
48
internal/lsp/testdata/godef/a/f.go.golden
vendored
Normal file
48
internal/lsp/testdata/godef/a/f.go.golden
vendored
Normal 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
|
6
internal/lsp/testdata/godef/b/b.go.golden
vendored
6
internal/lsp/testdata/godef/b/b.go.golden
vendored
@ -23,7 +23,7 @@ godef/a/a.go:7:6-7: defined here as A string //@A
|
|||||||
A string //@A
|
A string //@A
|
||||||
|
|
||||||
-- AImport-definition --
|
-- 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 --
|
-- 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": {
|
"end": {
|
||||||
"line": 5,
|
"line": 5,
|
||||||
"column": 3,
|
"column": 2,
|
||||||
"offset": 122
|
"offset": 121
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"description": "package a (\"golang.org/x/tools/internal/lsp/godef/a\")"
|
"description": "package a (\"golang.org/x/tools/internal/lsp/godef/a\")"
|
||||||
|
@ -30,7 +30,7 @@ const (
|
|||||||
ExpectedDiagnosticsCount = 17
|
ExpectedDiagnosticsCount = 17
|
||||||
ExpectedFormatCount = 5
|
ExpectedFormatCount = 5
|
||||||
ExpectedImportCount = 2
|
ExpectedImportCount = 2
|
||||||
ExpectedDefinitionsCount = 35
|
ExpectedDefinitionsCount = 38
|
||||||
ExpectedTypeDefinitionsCount = 2
|
ExpectedTypeDefinitionsCount = 2
|
||||||
ExpectedHighlightsCount = 2
|
ExpectedHighlightsCount = 2
|
||||||
ExpectedReferencesCount = 2
|
ExpectedReferencesCount = 2
|
||||||
|
Loading…
x
Reference in New Issue
Block a user