diff --git a/internal/lsp/source/identifier.go b/internal/lsp/source/identifier.go index 7d3a8e7b87..879fcf552a 100644 --- a/internal/lsp/source/identifier.go +++ b/internal/lsp/source/identifier.go @@ -33,9 +33,10 @@ type IdentifierInfo struct { } type declaration struct { - rng span.Range - node ast.Node - obj types.Object + rng span.Range + node ast.Node + obj types.Object + wasImplicit bool } 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.decl.obj = pkg.GetTypesInfo().ObjectOf(result.ident) 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 @@ -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 +} diff --git a/internal/lsp/source/references.go b/internal/lsp/source/references.go index 8a3ae73668..a317a5251b 100644 --- a/internal/lsp/source/references.go +++ b/internal/lsp/source/references.go @@ -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. - declObj := i.decl.obj - if declObj == nil { + if i.decl.obj == nil { return []*ReferenceInfo{}, nil } var references []*ReferenceInfo - for ident, obj := range pkgInfo.Defs { - if obj == declObj { - references = append(references, &ReferenceInfo{ - Name: ident.Name, - Range: span.NewRange(i.File.FileSet(), ident.Pos(), ident.End()), - ident: ident, - }) - } + + 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.Uses { - if obj == declObj { - references = append(references, &ReferenceInfo{ - Name: ident.Name, - Range: span.NewRange(i.File.FileSet(), ident.Pos(), ident.End()), - ident: ident, - }) + + for ident, obj := range pkgInfo.Defs { + 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 == 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 diff --git a/internal/lsp/testdata/godef/a/f.go b/internal/lsp/testdata/godef/a/f.go new file mode 100644 index 0000000000..d1b8c23d0d --- /dev/null +++ b/internal/lsp/testdata/godef/a/f.go @@ -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) + } + +} diff --git a/internal/lsp/testdata/godef/a/f.go.golden b/internal/lsp/testdata/godef/a/f.go.golden new file mode 100644 index 0000000000..45ba99575d --- /dev/null +++ b/internal/lsp/testdata/godef/a/f.go.golden @@ -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 \ No newline at end of file diff --git a/internal/lsp/testdata/godef/b/b.go.golden b/internal/lsp/testdata/godef/b/b.go.golden index febfce685b..444b9852fa 100644 --- a/internal/lsp/testdata/godef/b/b.go.golden +++ b/internal/lsp/testdata/godef/b/b.go.golden @@ -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\")" diff --git a/internal/lsp/tests/tests.go b/internal/lsp/tests/tests.go index 373d6755c1..efdd7382b7 100644 --- a/internal/lsp/tests/tests.go +++ b/internal/lsp/tests/tests.go @@ -30,7 +30,7 @@ const ( ExpectedDiagnosticsCount = 17 ExpectedFormatCount = 5 ExpectedImportCount = 2 - ExpectedDefinitionsCount = 35 + ExpectedDefinitionsCount = 38 ExpectedTypeDefinitionsCount = 2 ExpectedHighlightsCount = 2 ExpectedReferencesCount = 2