1use gruel_parser::ast::{
15 AssignTarget, Ast, BlockExpr, Expr, Function, Ident, Item, MatchArm, Method, Pattern,
16 Statement, TypeExpr,
17};
18use gruel_util::{FileId, Span};
19use lasso::ThreadedRodeo;
20
21pub fn definition_at(
23 ast: &Ast,
24 interner: &ThreadedRodeo,
25 file_id: FileId,
26 byte: u32,
27) -> Option<Span> {
28 let target = find_ident_at(ast, file_id, byte)?;
29 resolve_definition(ast, interner, target)
30}
31
32fn find_ident_at(ast: &Ast, file_id: FileId, byte: u32) -> Option<Ident> {
34 let mut finder = IdentFinder::new(file_id, byte);
35 for item in &ast.items {
36 finder.visit_item(item);
37 }
38 finder.result
39}
40
41struct IdentFinder {
42 file_id: FileId,
43 byte: u32,
44 result: Option<Ident>,
45 best_size: u32,
46}
47
48impl IdentFinder {
49 fn new(file_id: FileId, byte: u32) -> Self {
50 Self {
51 file_id,
52 byte,
53 result: None,
54 best_size: u32::MAX,
55 }
56 }
57
58 fn consider(&mut self, ident: Ident) {
59 let span = ident.span;
60 if span.file_id != self.file_id {
61 return;
62 }
63 if self.byte < span.start || self.byte >= span.end {
64 return;
65 }
66 let size = span.end.saturating_sub(span.start);
67 if size <= self.best_size {
68 self.best_size = size;
69 self.result = Some(ident);
70 }
71 }
72
73 fn visit_item(&mut self, item: &Item) {
74 match item {
75 Item::Function(f) => {
76 self.consider(f.name);
77 for p in &f.params {
78 self.consider(p.name);
79 self.visit_type(&p.ty);
80 }
81 if let Some(rt) = &f.return_type {
82 self.visit_type(rt);
83 }
84 self.visit_expr(&f.body);
85 }
86 Item::Struct(s) => {
87 self.consider(s.name);
88 for field in &s.fields {
89 self.consider(field.name);
90 self.visit_type(&field.ty);
91 }
92 for m in &s.methods {
93 self.visit_method(m);
94 }
95 }
96 Item::Enum(e) => {
97 self.consider(e.name);
98 for v in &e.variants {
99 self.consider(v.name);
100 }
101 for m in &e.methods {
102 self.visit_method(m);
103 }
104 }
105 Item::Interface(i) => {
106 self.consider(i.name);
107 for sig in &i.methods {
108 self.consider(sig.name);
109 for p in &sig.params {
110 self.consider(p.name);
111 self.visit_type(&p.ty);
112 }
113 if let Some(rt) = &sig.return_type {
114 self.visit_type(rt);
115 }
116 }
117 }
118 Item::Derive(d) => {
119 self.consider(d.name);
120 for m in &d.methods {
121 self.visit_method(m);
122 }
123 }
124 Item::Const(c) => {
125 self.consider(c.name);
126 if let Some(ty) = &c.ty {
127 self.visit_type(ty);
128 }
129 self.visit_expr(&c.init);
130 }
131 Item::LinkExtern(b) => {
132 for ext in &b.items {
133 self.consider(ext.name);
134 for p in &ext.params {
135 self.consider(p.name);
136 self.visit_type(&p.ty);
137 }
138 if let Some(rt) = &ext.return_type {
139 self.visit_type(rt);
140 }
141 }
142 }
143 Item::Error(_) => {}
144 }
145 }
146
147 fn visit_method(&mut self, m: &Method) {
148 self.consider(m.name);
149 for p in &m.params {
150 self.consider(p.name);
151 self.visit_type(&p.ty);
152 }
153 if let Some(rt) = &m.return_type {
154 self.visit_type(rt);
155 }
156 self.visit_expr(&m.body);
157 }
158
159 fn visit_type(&mut self, ty: &TypeExpr) {
160 match ty {
161 TypeExpr::Named(ident) => self.consider(*ident),
162 TypeExpr::TypeCall { callee, args, .. } => {
163 self.consider(*callee);
164 for a in args {
165 self.visit_type(a);
166 }
167 }
168 TypeExpr::Array { element, .. } => self.visit_type(element),
169 TypeExpr::Tuple { elems, .. } => {
170 for e in elems {
171 self.visit_type(e);
172 }
173 }
174 _ => {}
175 }
176 }
177
178 fn visit_expr(&mut self, expr: &Expr) {
179 match expr {
180 Expr::Ident(ident) => self.consider(*ident),
181 Expr::Block(b) => self.visit_block(b),
182 Expr::Call(c) => {
183 self.consider(c.name);
184 for arg in &c.args {
185 self.visit_expr(&arg.expr);
186 }
187 }
188 Expr::MethodCall(m) => {
189 self.visit_expr(&m.receiver);
190 self.consider(m.method);
191 for arg in &m.args {
192 self.visit_expr(&arg.expr);
193 }
194 }
195 Expr::Field(f) => {
196 self.visit_expr(&f.base);
197 self.consider(f.field);
198 }
199 Expr::Binary(b) => {
200 self.visit_expr(&b.left);
201 self.visit_expr(&b.right);
202 }
203 Expr::Unary(u) => self.visit_expr(&u.operand),
204 Expr::Paren(p) => self.visit_expr(&p.inner),
205 Expr::If(i) => {
206 self.visit_expr(&i.cond);
207 self.visit_block(&i.then_block);
208 if let Some(b) = &i.else_block {
209 self.visit_block(b);
210 }
211 }
212 Expr::While(w) => {
213 self.visit_expr(&w.cond);
214 self.visit_block(&w.body);
215 }
216 Expr::For(f) => {
217 self.consider(f.binding);
218 self.visit_expr(&f.iterable);
219 self.visit_block(&f.body);
220 }
221 Expr::Loop(l) => self.visit_block(&l.body),
222 Expr::Match(m) => {
223 self.visit_expr(&m.scrutinee);
224 for arm in &m.arms {
225 self.visit_match_arm(arm);
226 }
227 }
228 Expr::Return(r) => {
229 if let Some(e) = &r.value {
230 self.visit_expr(e);
231 }
232 }
233 Expr::Tuple(t) => {
234 for e in &t.elems {
235 self.visit_expr(e);
236 }
237 }
238 Expr::TupleIndex(t) => self.visit_expr(&t.base),
239 Expr::Index(i) => {
240 self.visit_expr(&i.base);
241 self.visit_expr(&i.index);
242 }
243 Expr::StructLit(s) => {
244 if let Some(base) = &s.base {
245 self.visit_expr(base);
246 }
247 self.consider(s.name);
248 for fi in &s.fields {
249 self.consider(fi.name);
250 self.visit_expr(&fi.value);
251 }
252 }
253 Expr::EnumStructLit(_) => {}
254 Expr::Path(p) => {
255 if let Some(b) = &p.base {
256 self.visit_expr(b);
257 }
258 self.consider(p.type_name);
259 self.consider(p.variant);
260 }
261 Expr::AssocFnCall(_) => {}
262 Expr::IntrinsicCall(c) => {
263 self.consider(c.name);
264 for a in &c.args {
265 if let gruel_parser::ast::IntrinsicArg::Expr(e) = a {
266 self.visit_expr(e);
267 }
268 }
269 }
270 Expr::ArrayLit(a) => {
271 for e in &a.elements {
272 self.visit_expr(e);
273 }
274 }
275 Expr::Range(r) => {
276 if let Some(e) = &r.lo {
277 self.visit_expr(e);
278 }
279 if let Some(e) = &r.hi {
280 self.visit_expr(e);
281 }
282 }
283 Expr::AnonFn(f) => {
284 for p in &f.params {
285 self.consider(p.name);
286 self.visit_type(&p.ty);
287 }
288 self.visit_block(&f.body);
289 }
290 Expr::Comptime(_) => {}
291 Expr::ComptimeUnrollFor(_) => {}
292 Expr::Checked(_) => {}
293 _ => {}
294 }
295 }
296
297 fn visit_block(&mut self, b: &BlockExpr) {
298 for stmt in &b.statements {
299 self.visit_statement(stmt);
300 }
301 self.visit_expr(&b.expr);
302 }
303
304 fn visit_statement(&mut self, stmt: &Statement) {
305 match stmt {
306 Statement::Let(l) => {
307 self.consider_pattern(&l.pattern);
308 if let Some(ty) = &l.ty {
309 self.visit_type(ty);
310 }
311 self.visit_expr(&l.init);
312 }
313 Statement::Assign(a) => {
314 match &a.target {
315 AssignTarget::Var(i) => self.consider(*i),
316 AssignTarget::Field(f) => {
317 self.visit_expr(&f.base);
318 self.consider(f.field);
319 }
320 AssignTarget::Index(i) => {
321 self.visit_expr(&i.base);
322 self.visit_expr(&i.index);
323 }
324 }
325 self.visit_expr(&a.value);
326 }
327 Statement::Expr(e) => self.visit_expr(e),
328 }
329 }
330
331 fn consider_pattern(&mut self, p: &Pattern) {
332 if let Pattern::Ident { name, .. } = p {
333 self.consider(*name);
334 }
335 }
336
337 fn visit_match_arm(&mut self, arm: &MatchArm) {
338 self.visit_expr(&arm.body);
339 }
340}
341
342fn resolve_definition(ast: &Ast, _interner: &ThreadedRodeo, target: Ident) -> Option<Span> {
345 for item in &ast.items {
347 let span = match item {
348 Item::Function(f) if f.name.name == target.name => Some(f.name.span),
349 Item::Struct(s) if s.name.name == target.name => Some(s.name.span),
350 Item::Enum(e) if e.name.name == target.name => Some(e.name.span),
351 Item::Interface(i) if i.name.name == target.name => Some(i.name.span),
352 Item::Derive(d) if d.name.name == target.name => Some(d.name.span),
353 Item::Const(c) if c.name.name == target.name => Some(c.name.span),
354 _ => None,
355 };
356 if let Some(s) = span {
357 return Some(s);
358 }
359 }
360 if let Some(span) = resolve_in_function_bodies(ast, target) {
362 return Some(span);
363 }
364 None
365}
366
367fn resolve_in_function_bodies(ast: &Ast, target: Ident) -> Option<Span> {
368 for item in &ast.items {
369 match item {
370 Item::Function(f) => {
371 if let Some(s) = resolve_in_function(f, target) {
372 return Some(s);
373 }
374 }
375 Item::Struct(s) => {
376 for m in &s.methods {
377 if let Some(s) = resolve_in_method(m, target) {
378 return Some(s);
379 }
380 }
381 }
382 Item::Enum(e) => {
383 for m in &e.methods {
384 if let Some(s) = resolve_in_method(m, target) {
385 return Some(s);
386 }
387 }
388 }
389 Item::Derive(d) => {
390 for m in &d.methods {
391 if let Some(s) = resolve_in_method(m, target) {
392 return Some(s);
393 }
394 }
395 }
396 _ => {}
397 }
398 }
399 None
400}
401
402fn resolve_in_function(f: &Function, target: Ident) -> Option<Span> {
403 if !expr_contains_target(&f.body, target) {
404 return None;
405 }
406 for p in &f.params {
407 if p.name.name == target.name {
408 return Some(p.name.span);
409 }
410 }
411 find_local_def_in_expr(&f.body, target)
412}
413
414fn resolve_in_method(m: &Method, target: Ident) -> Option<Span> {
415 if !expr_contains_target(&m.body, target) {
416 return None;
417 }
418 for p in &m.params {
419 if p.name.name == target.name {
420 return Some(p.name.span);
421 }
422 }
423 find_local_def_in_expr(&m.body, target)
424}
425
426#[allow(dead_code)]
427fn block_contains(b: &BlockExpr, target: Ident) -> bool {
428 target.span.start >= b.span.start && target.span.end <= b.span.end
429}
430
431fn expr_contains_target(e: &Expr, target: Ident) -> bool {
432 let span = match e {
433 Expr::Block(b) => b.span,
434 _ => return false,
435 };
436 target.span.start >= span.start && target.span.end <= span.end
437}
438
439fn find_local_def_in_expr(expr: &Expr, target: Ident) -> Option<Span> {
440 let mut found: Option<Span> = None;
441 walk_expr_for_let(expr, target, &mut found);
442 found
443}
444
445fn walk_block_for_let(block: &BlockExpr, target: Ident, found: &mut Option<Span>) {
446 for stmt in &block.statements {
447 match stmt {
448 Statement::Let(l) => {
449 if let Pattern::Ident { name, .. } = &l.pattern {
450 if name.name == target.name && name.span.start <= target.span.start {
451 *found = Some(name.span);
452 }
453 }
454 walk_expr_for_let(&l.init, target, found);
455 }
456 Statement::Assign(a) => {
457 walk_expr_for_let(&a.value, target, found);
458 }
459 Statement::Expr(e) => {
460 walk_expr_for_let(e, target, found);
461 }
462 }
463 }
464 walk_expr_for_let(&block.expr, target, found);
465}
466
467fn walk_expr_for_let(expr: &Expr, target: Ident, found: &mut Option<Span>) {
468 match expr {
469 Expr::Block(b) => walk_block_for_let(b, target, found),
470 Expr::If(i) => {
471 walk_block_for_let(&i.then_block, target, found);
472 if let Some(b) = &i.else_block {
473 walk_block_for_let(b, target, found);
474 }
475 }
476 Expr::While(w) => walk_block_for_let(&w.body, target, found),
477 Expr::For(f) => {
478 if f.binding.name == target.name && f.binding.span.start <= target.span.start {
479 *found = Some(f.binding.span);
480 }
481 walk_block_for_let(&f.body, target, found);
482 }
483 Expr::Loop(l) => walk_block_for_let(&l.body, target, found),
484 Expr::Match(m) => {
485 for arm in &m.arms {
486 walk_expr_for_let(&arm.body, target, found);
487 }
488 }
489 _ => {}
490 }
491}
492
493#[cfg(test)]
494mod tests {
495 use super::*;
496 use gruel_compiler::{
497 PreviewFeatures, SourceFile, merge_symbols, parse_all_files_with_preview,
498 };
499
500 fn parse(source: &str) -> (Ast, ThreadedRodeo) {
501 let sources = vec![SourceFile::new("main.gruel", source, FileId::new(1))];
502 let parsed = parse_all_files_with_preview(&sources, &PreviewFeatures::default()).unwrap();
503 let merged = merge_symbols(parsed).unwrap();
504 (merged.ast, merged.interner)
505 }
506
507 #[test]
508 fn goto_function_reference() {
509 let src = "fn foo() -> i32 { 0 }\nfn main() -> i32 { foo() }";
510 let (ast, interner) = parse(src);
511 let foo_call = src.rfind("foo").unwrap() as u32;
512 let def = definition_at(&ast, &interner, FileId::new(1), foo_call + 1).unwrap();
513 assert_eq!(def.start, src.find("foo").unwrap() as u32);
514 }
515
516 #[test]
517 fn goto_struct_reference_in_type_position() {
518 let src = "struct Point { x: i32 }\nfn make() -> Point { Point { x: 0 } }";
519 let (ast, interner) = parse(src);
520 let pos = src.find("-> Point").unwrap() as u32 + 3;
521 let def = definition_at(&ast, &interner, FileId::new(1), pos).unwrap();
522 assert_eq!(def.start, src.find("Point").unwrap() as u32);
523 }
524
525 #[test]
526 fn goto_local_variable() {
527 let src = "fn main() -> i32 { let x = 42; x }";
528 let (ast, interner) = parse(src);
529 let pos = src.rfind('x').unwrap() as u32;
530 let def = definition_at(&ast, &interner, FileId::new(1), pos).unwrap();
531 assert_eq!(def.start, src.find("x =").unwrap() as u32);
533 }
534
535 #[test]
536 fn goto_function_param() {
537 let src = "fn id(x: i32) -> i32 { x }";
538 let (ast, interner) = parse(src);
539 let pos = src.rfind('x').unwrap() as u32;
540 let def = definition_at(&ast, &interner, FileId::new(1), pos).unwrap();
541 assert_eq!(def.start, src.find("x:").unwrap() as u32);
542 }
543}