Skip to content

Commit c9ea558

Browse files
adonovangopherbot
authored andcommitted
gopls/internal/golang: Hover: use Cursor throughout
This CL replaces the use of []Node slices from PathEnclosingInterval by the use of Cursor.FindByPos, which simplifies a number of algorithms. In particular, the various ways of finding an Object from a source position have been consolidated and clarified in objectsAt. This sets us up to fix the attached issue in the next CL. Details: - Use cursorutil.FirstEnclosing in more places. - Ditto internalastutil.NodeContains. - Avoid numerous calls to PathEnclosingInterval. - pathEnclosingObjNode has gone away. All callers use Cursor.Find* or objectsAt. Its logic for StarExpr has moved into objectsAt. - hoverDefinitionObjectAtPos merged into objectsAt. Its logic for embedded fields has been pulled out into Definition and Hover (two copies), which are the only callers that want it. A follow-up CL will delete it from Hover since it is the cause of golang/go#75975. - typeSwitchImplicits rewritten more simply (as typeSwitchVars) using Cursor. For golang/go#75975 Change-Id: Ifd62dd3499320f7e7de590348d8769adcdae395e Reviewed-on: https://go-review.googlesource.com/c/tools/+/714640 LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Hongxiang Jiang <hxjiang@golang.org> Auto-Submit: Alan Donovan <adonovan@google.com>
1 parent c0daa7f commit c9ea558

File tree

14 files changed

+412
-423
lines changed

14 files changed

+412
-423
lines changed

gopls/internal/analysis/yield/yield.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,9 @@ import (
3030
"golang.org/x/tools/go/analysis/passes/inspect"
3131
"golang.org/x/tools/go/ast/inspector"
3232
"golang.org/x/tools/go/ssa"
33+
"golang.org/x/tools/gopls/internal/util/cursorutil"
3334
"golang.org/x/tools/gopls/internal/util/safetoken"
3435
"golang.org/x/tools/internal/analysisinternal"
35-
"golang.org/x/tools/internal/moreiters"
3636
)
3737

3838
//go:embed doc.go
@@ -137,11 +137,11 @@ func run(pass *analysis.Pass) (any, error) {
137137
if !ok {
138138
panic(fmt.Sprintf("can't find node at %v", safetoken.StartPosition(pass.Fset, pos)))
139139
}
140-
call, ok := moreiters.First(cur.Enclosing((*ast.CallExpr)(nil)))
141-
if !ok {
140+
call, _ := cursorutil.FirstEnclosing[*ast.CallExpr](cur)
141+
if call == nil {
142142
panic(fmt.Sprintf("no call enclosing %v", safetoken.StartPosition(pass.Fset, pos)))
143143
}
144-
return call.Node()
144+
return call
145145
}
146146

147147
if !info.reported && ssaYieldCalls[instr] != nil {

gopls/internal/golang/definition.go

Lines changed: 30 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ import (
1515
"regexp"
1616
"strings"
1717

18-
"golang.org/x/tools/go/ast/astutil"
1918
"golang.org/x/tools/gopls/internal/cache"
2019
"golang.org/x/tools/gopls/internal/cache/metadata"
2120
"golang.org/x/tools/gopls/internal/cache/parsego"
@@ -39,6 +38,7 @@ func Definition(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, p
3938
if err != nil {
4039
return nil, err
4140
}
41+
cur, _ := pgf.Cursor.FindByPos(pos, pos) // can't fail
4242

4343
// Handle the case where the cursor is in an import.
4444
importLocations, err := importDefinition(ctx, snapshot, pkg, pgf, pos)
@@ -51,7 +51,7 @@ func Definition(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, p
5151

5252
// Handle the case where the cursor is in the package name.
5353
// We use "<= End" to accept a query immediately after the package name.
54-
if pgf.File != nil && pgf.File.Name.Pos() <= pos && pos <= pgf.File.Name.End() {
54+
if pgf.File != nil && internalastutil.NodeContains(pgf.File.Name, pos) {
5555
// If there's no package documentation, just use current file.
5656
declFile := pgf
5757
for _, pgf := range pkg.CompiledGoFiles() {
@@ -86,14 +86,12 @@ func Definition(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, p
8686
}
8787

8888
// Handle definition requests for various special kinds of syntax node.
89-
path, _ := astutil.PathEnclosingInterval(pgf.File, pos, pos)
90-
ancestors := path[1:]
91-
switch node := path[0].(type) {
89+
switch node := cur.Node().(type) {
9290
// Handle the case where the cursor is on a return statement by jumping to the result variables.
9391
case *ast.ReturnStmt:
9492
var funcType *ast.FuncType
95-
for _, n := range ancestors {
96-
switch n := n.(type) {
93+
for c := range cur.Enclosing() {
94+
switch n := c.Node().(type) {
9795
case *ast.FuncLit:
9896
funcType = n.Type
9997
case *ast.FuncDecl:
@@ -128,14 +126,14 @@ func Definition(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, p
128126

129127
case token.BREAK, token.CONTINUE:
130128
// Find innermost relevant ancestor for break/continue.
131-
for i, n := range ancestors {
132-
if isLabeled && i+1 < len(ancestors) {
133-
l, ok := ancestors[i+1].(*ast.LabeledStmt)
129+
for c := range cur.Enclosing() {
130+
if isLabeled {
131+
l, ok := c.Parent().Node().(*ast.LabeledStmt)
134132
if !(ok && l.Label.Name == label.Name()) {
135133
continue
136134
}
137135
}
138-
switch n.(type) {
136+
switch n := c.Node().(type) {
139137
case *ast.ForStmt, *ast.RangeStmt:
140138
var start, end token.Pos
141139
if node.Tok == token.BREAK {
@@ -164,12 +162,29 @@ func Definition(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, p
164162
}
165163
}
166164

167-
// The general case: the cursor is on an identifier.
168-
_, obj, _ := hoverDefinitionObjectAtPos(pkg.TypesInfo(), pgf, pos)
169-
if obj == nil {
165+
// The general case: the cursor is on (or near) an identifier.
166+
objects, err := objectsAt(pkg.TypesInfo(), cur)
167+
if err != nil {
168+
return nil, nil
169+
}
170+
obj := objects[0].obj
171+
cur = objects[0].cur // nearby
172+
id, ok := cur.Node().(*ast.Ident)
173+
if !ok {
174+
// objectsAt can return a non-Ident (e.g. ImportSpec),
175+
// but we dealt with them earlier. Can't happen?
170176
return nil, nil
171177
}
172178

179+
// If the query position was an embedded field, we want to jump
180+
// to the field's type definition, not the field's definition (#42254).
181+
if v, ok := obj.(*types.Var); ok && v.Embedded() {
182+
// types.Info.Uses contains the embedded field's *types.TypeName.
183+
if typeName := pkg.TypesInfo().Uses[id]; typeName != nil {
184+
obj = typeName
185+
}
186+
}
187+
173188
// Non-go (e.g. assembly) symbols
174189
//
175190
// When already at the definition of a Go function without
@@ -304,57 +319,6 @@ func builtinDecl(ctx context.Context, snapshot *cache.Snapshot, obj types.Object
304319
return pgf, ident, nil
305320
}
306321

307-
// hoverDefinitionObjectAtPos returns the identifier and object referenced at the
308-
// specified position, which must be within the file pgf, for the purposes of
309-
// hover and definition operations. It returns a nil object if no
310-
// object was found at the given position.
311-
//
312-
// If the returned identifier is a type-switch implicit (i.e. the x in x :=
313-
// e.(type)), the third result will be the type of the expression being
314-
// switched on (the type of e in the example). This facilitates workarounds for
315-
// limitations of the go/types API, which does not report an object for the
316-
// identifier x.
317-
//
318-
// For embedded fields, hoverDefinitionObjectAtPos returns the type name object rather
319-
// than the var (field) object.
320-
//
321-
// TODO(rfindley): this function exists to preserve the pre-existing behavior
322-
// of golang.Identifier. Eliminate this helper in favor of sharing
323-
// functionality with [objectsAt] and [pathEnclosingObjNode]
324-
// after choosing suitable primitives.
325-
func hoverDefinitionObjectAtPos(info *types.Info, pgf *parsego.File, pos token.Pos) (*ast.Ident, types.Object, types.Type) {
326-
path := pathEnclosingObjNode(pgf.File, pos)
327-
if len(path) == 0 {
328-
return nil, nil, nil
329-
}
330-
if id, ok := path[0].(*ast.Ident); ok {
331-
obj := info.ObjectOf(id)
332-
// If n is the var's declaring ident in a type switch
333-
// [i.e. the x in x := foo.(type)], it will not have an object. In this
334-
// case, set obj to the first implicit object (if any), and return the type
335-
// of the expression being switched on.
336-
//
337-
// The type switch may have no case clauses and thus no
338-
// implicit objects; this is a type error ("unused x"),
339-
if obj == nil {
340-
if implicits, typ := typeSwitchImplicits(info, path); len(implicits) > 0 {
341-
return id, implicits[0], typ
342-
}
343-
}
344-
345-
// If the original position was an embedded field, we want to jump
346-
// to the field's type definition, not the field's definition.
347-
if v, ok := obj.(*types.Var); ok && v.Embedded() {
348-
// types.Info.Uses contains the embedded field's *types.TypeName.
349-
if typeName := info.Uses[id]; typeName != nil {
350-
obj = typeName
351-
}
352-
}
353-
return id, obj, nil
354-
}
355-
return nil, nil, nil
356-
}
357-
358322
// importDefinition returns locations defining a package referenced by the
359323
// import spec containing pos.
360324
//
@@ -363,7 +327,7 @@ func importDefinition(ctx context.Context, s *cache.Snapshot, pkg *cache.Package
363327
var imp *ast.ImportSpec
364328
for _, spec := range pgf.File.Imports {
365329
// We use "<= End" to accept a query immediately after an ImportSpec.
366-
if spec.Path.Pos() <= pos && pos <= spec.Path.End() {
330+
if internalastutil.NodeContains(spec.Path, pos) {
367331
imp = spec
368332
}
369333
}

gopls/internal/golang/highlight.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,7 @@ func highlightPrintf(call *ast.CallExpr, idx int, cursorPos token.Pos, lit *ast.
228228
highlightRange(result, rng, protocol.Write)
229229
if arg != nil {
230230
succeededArg = argIndex
231-
highlightRange(result, astutil.NodeRange(arg), protocol.Read)
231+
highlightNode(result, arg, protocol.Read)
232232
}
233233
}
234234
}

0 commit comments

Comments
 (0)