Skip to content

Commit 6d8c261

Browse files
authored
checker,vls: add gotodef for more node types (#26115)
1 parent 9c644a6 commit 6d8c261

3 files changed

Lines changed: 257 additions & 14 deletions

File tree

‎vlib/v/checker/autocomplete.v‎

Lines changed: 73 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,22 @@ pub fn (mut c Checker) autocomplete_for_fn_call_expr(node ast.CallExpr) {
7373
exit(0)
7474
}
7575

76+
fn (mut c Checker) name_pos_gotodef(name string) ?token.Pos {
77+
idx := c.table.find_type_idx(name)
78+
if idx > 0 {
79+
sym := c.table.type_symbols[idx]
80+
return match sym.info {
81+
ast.Struct, ast.Alias, ast.SumType, ast.Enum, ast.Interface {
82+
sym.info.name_pos
83+
}
84+
else {
85+
none
86+
}
87+
}
88+
}
89+
return none
90+
}
91+
7692
fn (mut c Checker) ident_gotodef(node_ ast.Expr) {
7793
if c.pref.linfo.method != .definition {
7894
return
@@ -113,21 +129,21 @@ fn (mut c Checker) ident_gotodef(node_ ast.Expr) {
113129
pos = obj.pos
114130
}
115131
}
116-
//
132+
// If not found as a variable/const, try as a type
133+
if pos == token.Pos{} {
134+
full_name := if node.mod != '' { '${node.mod}.${node.name}' } else { node.name }
135+
if np := c.name_pos_gotodef(full_name) {
136+
pos = np
137+
}
138+
}
117139
}
118140
ast.StructInit {
119141
if !c.vls_is_the_node(node.name_pos) {
120142
return
121143
}
122-
mut info := c.table.sym(node.typ).info
123-
pos = match mut info {
124-
ast.Struct {
125-
info.name_pos
126-
}
127-
ast.Alias {
128-
info.name_pos
129-
}
130-
ast.SumType {
144+
info := c.table.sym(node.typ).info
145+
pos = match info {
146+
ast.Struct, ast.Alias, ast.SumType {
131147
info.name_pos
132148
}
133149
else {
@@ -136,12 +152,55 @@ fn (mut c Checker) ident_gotodef(node_ ast.Expr) {
136152
}
137153
}
138154
ast.SelectorExpr {
155+
// Check if clicking on the field name or method
139156
sym := c.table.sym(node.expr_type)
140-
f := c.table.find_field(sym, node.field_name) or {
141-
println('failed to find field "${node.field_name}"')
142-
exit(1)
157+
if field := c.table.find_field_with_embeds(sym, node.field_name) {
158+
pos = field.pos
159+
} else {
160+
if method := c.table.find_method(sym, node.field_name) {
161+
pos = method.name_pos
162+
} else {
163+
println('failed to find field or method "${node.field_name}"')
164+
exit(1)
165+
}
166+
}
167+
}
168+
ast.EnumVal {
169+
// Go to enum field definition
170+
mut enum_name := if node.enum_name == '' && node.typ != ast.void_type {
171+
c.table.sym(node.typ).name
172+
} else {
173+
node.enum_name
174+
}
175+
if enum_decl := c.table.enum_decls[enum_name] {
176+
for field in enum_decl.fields {
177+
if field.name == node.val {
178+
pos = field.pos
179+
break
180+
}
181+
}
182+
}
183+
}
184+
ast.TypeNode {
185+
// Go to type definition
186+
typ_str := c.table.type_to_str(node.typ)
187+
if np := c.name_pos_gotodef(typ_str) {
188+
pos = np
189+
}
190+
}
191+
ast.CastExpr {
192+
// Go to type definition in cast expr
193+
if node.typname != '' {
194+
if np := c.name_pos_gotodef(node.typname) {
195+
pos = np
196+
}
197+
}
198+
if pos == token.Pos{} && node.typ != ast.void_type {
199+
typ_str := c.table.type_to_str(node.typ)
200+
if np := c.name_pos_gotodef(typ_str) {
201+
pos = np
202+
}
143203
}
144-
pos = f.pos
145204
}
146205
else {}
147206
}

‎vlib/v/tests/vls/goto_def_test.v‎

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
import os
2+
import term
3+
import v.util.diff
4+
5+
const vroot = os.real_path(@VMODROOT)
6+
const test_file = os.join_path(vroot, 'vlib', 'v', 'tests', 'vls', 'goto_def_test_data.vv')
7+
const mod1_text_file = os.join_path(vroot, 'vlib', 'v', 'tests', 'vls', 'sample_mod1',
8+
'sample.v')
9+
10+
struct TestCase {
11+
name string
12+
line int
13+
col int
14+
expected string
15+
description string
16+
}
17+
18+
const test_cases = [
19+
TestCase{
20+
name: 'method_definition'
21+
line: 27
22+
col: 13
23+
expected: '${test_file}:20:20'
24+
description: 'Go to method definition from method call'
25+
},
26+
TestCase{
27+
name: 'enum_value_qualified'
28+
line: 30
29+
col: 20
30+
expected: '${test_file}:11:1'
31+
description: 'Go to enum value definition (qualified form: LocalEnum.first)'
32+
},
33+
TestCase{
34+
name: 'enum_value_short_form'
35+
line: 33
36+
col: 3
37+
expected: '${test_file}:12:1'
38+
description: 'Go to enum value definition (short form: .second in match)'
39+
},
40+
TestCase{
41+
name: 'type_alias_cast'
42+
line: 37
43+
col: 15
44+
expected: '${test_file}:16:5'
45+
description: 'Go to type alias definition in cast expression'
46+
},
47+
TestCase{
48+
name: 'sum_type_cast'
49+
line: 39
50+
col: 13
51+
expected: '${test_file}:18:5'
52+
description: 'Go to sum type definition in cast expression'
53+
},
54+
TestCase{
55+
name: 'struct_init'
56+
line: 42
57+
col: 13
58+
expected: '${test_file}:6:7'
59+
description: 'Go to struct definition from StructInit'
60+
},
61+
TestCase{
62+
name: 'variable_reference'
63+
line: 45
64+
col: 17
65+
expected: '${test_file}:25:5'
66+
description: 'Go to variable definition from reference'
67+
},
68+
TestCase{
69+
name: 'imported_enum_value'
70+
line: 48
71+
col: 25
72+
expected: '${mod1_text_file}:25:1'
73+
description: 'Go to imported enum value definition'
74+
},
75+
TestCase{
76+
name: 'field_access'
77+
line: 51
78+
col: 16
79+
expected: '${test_file}:7:1'
80+
description: 'Go to field definition from field access'
81+
},
82+
]
83+
84+
fn test_goto_definition() {
85+
mut total_errors := 0
86+
mut passed := 0
87+
88+
for tc in test_cases {
89+
cmd := 'v -w -check -json-errors -nocolor -vls-mode -line-info "${test_file}:${tc.line}:gd^${tc.col}" ${os.quoted_path(test_file)}'
90+
res := os.execute(cmd)
91+
92+
if res.exit_code < 0 {
93+
println('${term.red('FAIL')} ${tc.name}: Command failed to execute')
94+
println(' Command: ${cmd}')
95+
total_errors++
96+
continue
97+
}
98+
99+
res_output := $if windows {
100+
res.output.replace('\r\n', '\n').trim_space()
101+
} $else {
102+
res.output.trim_space()
103+
}
104+
105+
if tc.expected != res_output {
106+
println('${term.red('FAIL')} ${tc.name}')
107+
println(' Description: ${tc.description}')
108+
println(' Line ${tc.line}, Column ${tc.col}')
109+
if diff_ := diff.compare_text(tc.expected, res_output) {
110+
println(' Difference:')
111+
println(diff_)
112+
} else {
113+
println(' Expected: ${tc.expected}')
114+
println(' Got: ${res_output}')
115+
}
116+
total_errors++
117+
} else {
118+
println('${term.green('OK ')} ${tc.name}: ${tc.description}')
119+
passed++
120+
}
121+
}
122+
123+
println('')
124+
println('${term.header('Summary:', '=')}')
125+
println('Passed: ${passed}/${test_cases.len}')
126+
if total_errors > 0 {
127+
println('${term.red('Failed:')} ${total_errors}')
128+
}
129+
130+
assert total_errors == 0, 'Some tests failed'
131+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// Test data file for goto_def_test.v
2+
module main
3+
4+
import v.tests.vls.sample_mod1 as s
5+
6+
struct LocalStruct {
7+
value int
8+
}
9+
10+
enum LocalEnum {
11+
first
12+
second
13+
third
14+
}
15+
16+
type LocalAlias = string
17+
18+
type LocalSum = int | string
19+
20+
fn (ls LocalStruct) my_method() string {
21+
return 'method'
22+
}
23+
24+
fn main() {
25+
mut obj := LocalStruct{value: 42}
26+
27+
msg := obj.my_method()
28+
println(msg)
29+
30+
e1 := LocalEnum.first
31+
32+
match e1 {
33+
.second { println('second') }
34+
else {}
35+
}
36+
37+
casted := LocalAlias('test')
38+
39+
sum_val := LocalSum(42)
40+
println(sum_val)
41+
42+
another := LocalStruct{}
43+
println(another.value)
44+
45+
yet_another := obj
46+
println(yet_another)
47+
48+
imported_e := s.PublicEnum1.a
49+
println(imported_e)
50+
51+
val := obj.value
52+
println(val)
53+
}

0 commit comments

Comments
 (0)