Skip to content

Commit 4f820db

Browse files
committed
gopls/internal/golang: definition support break, goto and continue statements
Enables you to use the definition operation on a goto, break, or continue statement by returning the location of the label, the closing brace of the relevant block statement, or the start of the relevant loop, respectively. Fixes golang/go#70462 Change-Id: Ib8640283e4d80115f76f7c23961ce277bc093b4d Reviewed-on: https://go-review.googlesource.com/c/tools/+/637818 Reviewed-by: Alan Donovan <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]> Reviewed-by: Robert Findley <[email protected]>
1 parent f202b36 commit 4f820db

File tree

4 files changed

+234
-3
lines changed

4 files changed

+234
-3
lines changed

gopls/doc/features/navigation.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ A definition query also works in these unexpected places:
2525
- On the declaration of a non-Go function (a `func` with no body),
2626
it returns the location of the assembly implementation, if any,
2727
- On a **return statement**, it returns the location of the function's result variables.
28+
- On a **goto**, **break**, or **continue** statement, it returns the
29+
location of the label, the closing brace of the relevant block statement, or the
30+
start of the relevant loop, respectively.
2831

2932
<!-- On a built-in symbol such as `append` or `unsafe.Pointer`, `definition` reports
3033
the location of the declaration in the builtin or unsafe pseudo-packages,

gopls/doc/release/v0.18.0.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,9 @@ The Definition query now supports additional locations:
5252

5353
- When invoked on a return statement, it reports the location
5454
of the function's result variables.
55+
- When invoked on a break, goto, or continue statement, it reports
56+
the location of the label, the closing brace of the relevant
57+
block statement, or the start of the relevant loop, respectively.
5558

5659
## Improvements to "Hover"
5760

gopls/internal/golang/definition.go

Lines changed: 60 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -85,11 +85,13 @@ func Definition(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, p
8585
return locations, err // may be success or failure
8686
}
8787

88-
// Handle the case where the cursor is on a return statement by jumping to the result variables.
88+
// Handle definition requests for various special kinds of syntax node.
8989
path, _ := astutil.PathEnclosingInterval(pgf.File, pos, pos)
90-
if is[*ast.ReturnStmt](path[0]) {
90+
switch node := path[0].(type) {
91+
// Handle the case where the cursor is on a return statement by jumping to the result variables.
92+
case *ast.ReturnStmt:
9193
var funcType *ast.FuncType
92-
for _, n := range path {
94+
for _, n := range path[1:] {
9395
switch n := n.(type) {
9496
case *ast.FuncLit:
9597
funcType = n.Type
@@ -109,6 +111,61 @@ func Definition(ctx context.Context, snapshot *cache.Snapshot, fh file.Handle, p
109111
return nil, err
110112
}
111113
return []protocol.Location{loc}, nil
114+
115+
case *ast.BranchStmt:
116+
// Handle the case where the cursor is on a goto, break or continue statement by returning the
117+
// location of the label, the closing brace of the relevant block statement, or the
118+
// start of the relevant loop, respectively.
119+
label, isLabeled := pkg.TypesInfo().Uses[node.Label].(*types.Label)
120+
switch node.Tok {
121+
case token.GOTO:
122+
if isLabeled {
123+
loc, err := pgf.PosLocation(label.Pos(), label.Pos()+token.Pos(len(label.Name())))
124+
if err != nil {
125+
return nil, err
126+
}
127+
return []protocol.Location{loc}, nil
128+
} else {
129+
// Workaround for #70957.
130+
// TODO(madelinekalil): delete when go1.25 fixes it.
131+
return nil, nil
132+
}
133+
case token.BREAK, token.CONTINUE:
134+
// Find innermost relevant ancestor for break/continue.
135+
for i, n := range path[1:] {
136+
if isLabeled {
137+
l, ok := path[1:][i+1].(*ast.LabeledStmt)
138+
if !(ok && l.Label.Name == label.Name()) {
139+
continue
140+
}
141+
}
142+
switch n.(type) {
143+
case *ast.ForStmt, *ast.RangeStmt:
144+
var start, end token.Pos
145+
if node.Tok == token.BREAK {
146+
start, end = n.End()-token.Pos(len("}")), n.End()
147+
} else { // CONTINUE
148+
start, end = n.Pos(), n.Pos()+token.Pos(len("for"))
149+
}
150+
loc, err := pgf.PosLocation(start, end)
151+
if err != nil {
152+
return nil, err
153+
}
154+
return []protocol.Location{loc}, nil
155+
case *ast.SwitchStmt, *ast.TypeSwitchStmt, *ast.SelectStmt:
156+
if node.Tok == token.BREAK {
157+
loc, err := pgf.PosLocation(n.End()-1, n.End())
158+
if err != nil {
159+
return nil, err
160+
}
161+
return []protocol.Location{loc}, nil
162+
}
163+
case *ast.FuncDecl, *ast.FuncLit:
164+
// bad syntax; avoid jumping outside the current function
165+
return nil, nil
166+
}
167+
}
168+
}
112169
}
113170

114171
// The general case: the cursor is on an identifier.
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
This test checks definition operations in branch statements break, goto and continue.
2+
3+
-- go.mod --
4+
module mod.com
5+
6+
go 1.18
7+
8+
-- a/a.go --
9+
package a
10+
11+
import "log"
12+
13+
func breakLoop() {
14+
for i := 0; i < 10; i++ {
15+
if i > 6 {
16+
break //@def("break", rbrace1)
17+
}
18+
} //@loc(rbrace1, `}`)
19+
}
20+
21+
func breakNestedLoop() {
22+
for i := 0; i < 10; i++ {
23+
for j := 0; j < 5; j++ {
24+
if j > 1 {
25+
break //@def("break", rbrace2)
26+
}
27+
} //@loc(rbrace2, `}`)
28+
}
29+
}
30+
31+
func breakNestedLoopWithLabel() {
32+
Outer:
33+
for i := 0; i < 10; i++ {
34+
for j := 0; j < 5; j++ {
35+
if j > 1 {
36+
break Outer//@def("break", outerparen)
37+
}
38+
}
39+
} //@loc(outerparen, `}`)
40+
}
41+
42+
func breakSwitch(i int) {
43+
switch i {
44+
case 1:
45+
break //@def("break", rbrace4)
46+
case 2:
47+
log.Printf("2")
48+
case 3:
49+
log.Printf("3")
50+
} //@loc(rbrace4, `}`)
51+
}
52+
53+
func breakSwitchLabel(i int) {
54+
loop:
55+
for {
56+
switch i {
57+
case 1:
58+
break loop //@def("break", loopparen)
59+
case 2:
60+
log.Printf("2")
61+
case 3:
62+
continue loop
63+
}
64+
} //@loc(loopparen, `}`)
65+
}
66+
67+
func breakSelect(c, quit chan int) {
68+
x, y := 0, 1
69+
for {
70+
select {
71+
case c <- x:
72+
x, y = y, x+y
73+
break //@def("break", rbrace5)
74+
case <-quit:
75+
log.Println("quit")
76+
return
77+
} //@loc(rbrace5, `}`)
78+
}
79+
}
80+
81+
func breakWithContinue() {
82+
for j := 0; j < 5; j++ {
83+
if (j < 4) {
84+
continue
85+
}
86+
break //@def("break", rbrace6)
87+
} //@loc(rbrace6, `}`)
88+
}
89+
90+
func gotoNestedLoop() {
91+
Outer: //@loc(outer, "Outer")
92+
for i := 0; i < 10; i++ {
93+
for j := 0; j < 5; j++ {
94+
if (j > 1) {
95+
goto Outer//@def("goto", outer)
96+
}
97+
}
98+
}
99+
}
100+
101+
func continueLoop() {
102+
for j := 0; j < 5; j++ { //@loc(for3, `for`)
103+
if (j < 4) {
104+
continue //@def("continue", for3)
105+
}
106+
break
107+
}
108+
}
109+
110+
func continueDoubleLoop() {
111+
for i := 0; i < 10; i++ { //@loc(for4, `for`)
112+
for j := 0; j < 5; j++ {
113+
if (j > 1) {
114+
break
115+
}
116+
}
117+
if (i > 7) {
118+
continue//@def("continue", for4)
119+
}
120+
}
121+
}
122+
123+
func breakInBlockStmt() {
124+
for {
125+
if 0 < 10 {
126+
{
127+
break //@def("break", rbrace9)
128+
}
129+
}
130+
} //@loc(rbrace9, `}`)
131+
}
132+
133+
func breakInLabeledStmt() {
134+
outer:
135+
for {
136+
goto inner
137+
inner:
138+
break outer //@def("break", for5)
139+
} //@loc(for5, `}`)
140+
}
141+
142+
func breakToLabel(n int) {
143+
outer1:
144+
switch n {
145+
case 1:
146+
print("1")
147+
for i := 0; i < 10; i++ {
148+
if i > 3 {
149+
break outer1 //@def("break", outer1)
150+
}
151+
}
152+
} //@loc(outer1, "}")
153+
}
154+
155+
func continueToLabel(n int) {
156+
outer1:
157+
for { //@loc(outer2, "for")
158+
switch n {
159+
case 1:
160+
print("1")
161+
for i := 0; i < 10; i++ {
162+
if i > 3 {
163+
continue outer1 //@def("continue", outer2)
164+
}
165+
}
166+
}
167+
}
168+
}

0 commit comments

Comments
 (0)