1use std::collections::HashMap;
2use logicaffeine_base::Interner;
3use logicaffeine_language::{
4 analysis::TypeRegistry,
5 token::{Token, TokenType, BlockType, Span},
6};
7use crate::pipeline::OwnedStmt;
8
9#[derive(Debug, Clone, Default)]
11pub struct ScopeInfo {
12 pub block_idx: Option<usize>,
14 pub depth: u32,
16}
17
18#[derive(Debug, Clone)]
20pub struct Definition {
21 pub name: String,
22 pub kind: DefinitionKind,
23 pub span: Span,
24 pub detail: Option<String>,
25 pub scope: ScopeInfo,
26 pub mutable: Option<bool>,
28 pub doc: Option<String>,
31}
32
33#[derive(Debug, Clone, PartialEq, Eq)]
34pub enum DefinitionKind {
35 Variable,
36 Function,
37 Struct,
38 Enum,
39 Field,
40 Parameter,
41 Block,
42 Variant,
43 Theorem,
44}
45
46#[derive(Debug, Clone)]
48pub struct Reference {
49 pub name: String,
50 pub span: Span,
51 pub definition_idx: Option<usize>,
52}
53
54#[derive(Debug, Clone)]
56pub struct CallSite {
57 pub caller: Option<usize>,
61 pub callee: usize,
63 pub span: Span,
64}
65
66#[derive(Debug, Clone, Default)]
68pub struct SymbolIndex {
69 pub definitions: Vec<Definition>,
70 pub references: Vec<Reference>,
71 pub name_to_defs: HashMap<String, Vec<usize>>,
72 pub block_spans: Vec<(String, BlockType, Span)>,
74 pub statement_spans: Vec<(String, Span)>,
76 pub call_sites: Vec<CallSite>,
78}
79
80impl SymbolIndex {
81 pub fn build(
84 stmts: &[OwnedStmt],
85 tokens: &[Token],
86 type_registry: &TypeRegistry,
87 interner: &Interner,
88 source: &str,
89 ) -> Self {
90 let mut index = SymbolIndex::default();
91
92 index.index_statements(stmts, tokens, interner);
94
95 index.index_type_registry(type_registry, tokens, interner);
97
98 index.index_tokens(tokens, interner);
100
101 index.index_docs(source);
103
104 index.index_references(tokens, interner);
106
107 index.compute_scopes();
109
110 index.index_call_sites(tokens, interner);
112
113 index
114 }
115
116 fn index_docs(&mut self, source: &str) {
119 for def in &mut self.definitions {
120 if !matches!(
121 def.kind,
122 DefinitionKind::Function
123 | DefinitionKind::Struct
124 | DefinitionKind::Enum
125 | DefinitionKind::Theorem
126 ) {
127 continue;
128 }
129 let anchor = def.span.start.min(source.len());
130 let line_start = source[..anchor].rfind('\n').map(|i| i + 1).unwrap_or(0);
131 let is_header_line = source[line_start..]
132 .lines()
133 .next()
134 .is_some_and(|l| l.trim_start().starts_with("## "));
135 if is_header_line {
136 def.doc =
137 logicaffeine_language::teach::doc_for_header_at(source, line_start);
138 }
139 }
140 }
141
142 fn claim_let_binding(
146 tokens: &[Token],
147 name: &str,
148 interner: &Interner,
149 taken: &mut std::collections::HashSet<usize>,
150 ) -> Option<Span> {
151 for (i, token) in tokens.iter().enumerate() {
152 if !matches!(token.kind, TokenType::Let) || taken.contains(&i) {
153 continue;
154 }
155 for candidate in tokens.iter().skip(i + 1).take(3) {
158 if matches!(candidate.kind, TokenType::Be | TokenType::Colon) {
159 break;
160 }
161 if resolve_token_name(candidate, interner).map(|n| n == name).unwrap_or(false) {
162 taken.insert(i);
163 return Some(candidate.span);
164 }
165 }
166 }
167 None
168 }
169
170 fn add_definition(&mut self, def: Definition) -> usize {
171 let idx = self.definitions.len();
172 self.name_to_defs
173 .entry(def.name.clone())
174 .or_default()
175 .push(idx);
176 self.definitions.push(def);
177 idx
178 }
179
180 fn index_statements(&mut self, stmts: &[OwnedStmt], tokens: &[Token], interner: &Interner) {
181 let mut taken_lets: std::collections::HashSet<usize> = std::collections::HashSet::new();
182 for stmt in stmts {
183 match stmt {
184 OwnedStmt::FunctionDef { name, params, return_type } => {
185 let span = find_token_span_for_name(tokens, name, interner)
186 .unwrap_or(Span::default());
187 let detail = {
188 let param_str: Vec<String> = params
189 .iter()
190 .map(|(n, t)| format!("{}: {}", n, t))
191 .collect();
192 let ret = return_type.as_deref().unwrap_or("Unit");
193 Some(format!("To {}({}) -> {}", name, param_str.join(", "), ret))
194 };
195 self.add_definition(Definition {
196 name: name.clone(),
197 kind: DefinitionKind::Function,
198 span,
199 detail,
200 scope: ScopeInfo::default(),
201 mutable: None,
202 doc: None,
203 });
204
205 let mut search_after = span.end;
207 for (param_name, param_type) in params {
208 let param_span = find_token_span_for_name_after(
209 tokens, param_name, interner, search_after,
210 ).unwrap_or(span);
211 if param_span != span {
212 search_after = param_span.end;
213 }
214 self.add_definition(Definition {
215 name: param_name.clone(),
216 kind: DefinitionKind::Parameter,
217 span: param_span,
218 detail: Some(format!("{}: {}", param_name, param_type)),
219 scope: ScopeInfo::default(),
220 mutable: None,
221 doc: None,
222 });
223 }
224 }
225 OwnedStmt::StructDef { name, fields } => {
226 let span = find_token_span_for_name(tokens, name, interner)
227 .unwrap_or(Span::default());
228 self.add_definition(Definition {
229 name: name.clone(),
230 kind: DefinitionKind::Struct,
231 span,
232 detail: Some(format!("{} (struct)", name)),
233 scope: ScopeInfo::default(),
234 mutable: None,
235 doc: None,
236 });
237 let mut field_search_after = span.end;
238 for (field_name, field_type) in fields {
239 let field_span = find_token_span_for_name_after(
240 tokens, field_name, interner, field_search_after,
241 ).unwrap_or(span);
242 if field_span != span {
243 field_search_after = field_span.end;
244 }
245 self.add_definition(Definition {
246 name: field_name.clone(),
247 kind: DefinitionKind::Field,
248 span: field_span,
249 detail: Some(format!("{}: {}", field_name, field_type)),
250 scope: ScopeInfo::default(),
251 mutable: None,
252 doc: None,
253 });
254 }
255 }
256 OwnedStmt::Let { name, ty, inferred_type, mutable } => {
257 let span = Self::claim_let_binding(tokens, name, interner, &mut taken_lets)
262 .or_else(|| find_token_span_for_name(tokens, name, interner))
263 .unwrap_or(Span::default());
264 let prefix = if *mutable { "mut " } else { "" };
265 let detail = if let Some(explicit_ty) = ty {
266 format!("Let {}{}: {}", prefix, name, explicit_ty)
267 } else if let Some(inferred) = inferred_type {
268 format!("Let {}{}: {} (inferred)", prefix, name, inferred)
269 } else {
270 format!("Let {}{}: auto (inferred)", prefix, name)
271 };
272 self.add_definition(Definition {
273 name: name.clone(),
274 kind: DefinitionKind::Variable,
275 span,
276 detail: Some(detail),
277 scope: ScopeInfo::default(),
278 mutable: Some(*mutable),
279 doc: None,
280 });
281 }
282 OwnedStmt::Theorem { name } => {
283 let span = find_token_span_for_name(tokens, name, interner)
284 .unwrap_or(Span::default());
285 self.add_definition(Definition {
286 name: name.clone(),
287 kind: DefinitionKind::Theorem,
288 span,
289 detail: Some(format!("Theorem {}", name)),
290 scope: ScopeInfo::default(),
291 mutable: None,
292 doc: None,
293 });
294 }
295 OwnedStmt::Block { name, kind } => {
296 self.add_definition(Definition {
297 name: name.clone(),
298 kind: DefinitionKind::Block,
299 span: Span::default(),
300 detail: Some(format!("{} {}", kind, name)),
301 scope: ScopeInfo::default(),
302 mutable: None,
303 doc: None,
304 });
305 }
306 OwnedStmt::Other => {}
307 }
308 }
309 }
310
311 fn index_type_registry(
312 &mut self,
313 type_registry: &TypeRegistry,
314 tokens: &[Token],
315 interner: &Interner,
316 ) {
317 for (sym, typedef) in type_registry.iter_types() {
318 let name = interner.resolve(*sym).to_string();
319 if matches!(typedef, logicaffeine_language::analysis::TypeDef::Primitive) {
321 continue;
322 }
323 if self.name_to_defs.contains_key(&name) {
325 continue;
326 }
327
328 let span = find_token_span_for_name(tokens, &name, interner)
329 .unwrap_or(Span::default());
330
331 match typedef {
332 logicaffeine_language::analysis::TypeDef::Struct { fields, .. } => {
333 self.add_definition(Definition {
334 name: name.clone(),
335 kind: DefinitionKind::Struct,
336 span,
337 detail: Some(format!("{} (struct)", name)),
338 scope: ScopeInfo::default(),
339 mutable: None,
340 doc: None,
341 });
342 let mut field_search_after = span.end;
343 for field in fields {
344 let field_name = interner.resolve(field.name).to_string();
345 let field_span = find_token_span_for_name_after(
349 tokens, &field_name, interner, field_search_after,
350 )
351 .unwrap_or(span);
352 if field_span != span {
353 field_search_after = field_span.end;
354 }
355 self.add_definition(Definition {
356 name: field_name.clone(),
357 kind: DefinitionKind::Field,
358 span: field_span,
359 detail: Some(format!("{}.{}", name, field_name)),
360 scope: ScopeInfo::default(),
361 mutable: None,
362 doc: None,
363 });
364 }
365 }
366 logicaffeine_language::analysis::TypeDef::Enum { variants, .. } => {
367 self.add_definition(Definition {
368 name: name.clone(),
369 kind: DefinitionKind::Enum,
370 span,
371 detail: Some(format!("{} (enum)", name)),
372 scope: ScopeInfo::default(),
373 mutable: None,
374 doc: None,
375 });
376 for variant in variants {
377 let variant_name = interner.resolve(variant.name).to_string();
378 let variant_span = find_token_span_for_name(tokens, &variant_name, interner)
379 .unwrap_or(span);
380 self.add_definition(Definition {
381 name: variant_name.clone(),
382 kind: DefinitionKind::Variant,
383 span: variant_span,
384 detail: Some(format!("{}::{}", name, variant_name)),
385 scope: ScopeInfo::default(),
386 mutable: None,
387 doc: None,
388 });
389 }
390 }
391 _ => {}
392 }
393 }
394 }
395
396 fn index_tokens(&mut self, tokens: &[Token], interner: &Interner) {
397 let mut i = 0;
398 while i < tokens.len() {
399 match &tokens[i].kind {
400 TokenType::BlockHeader { block_type } => {
401 let start = tokens[i].span.start;
403 let mut end = tokens.last().map(|t| t.span.end).unwrap_or(start);
404 for j in (i + 1)..tokens.len() {
405 if matches!(tokens[j].kind, TokenType::BlockHeader { .. }) {
406 end = tokens[j].span.start;
407 break;
408 }
409 }
410
411 let name = interner.resolve(tokens[i].lexeme).to_string();
412 self.block_spans.push((
413 name,
414 *block_type,
415 Span::new(start, end),
416 ));
417 }
418 _ => {}
419 }
420 i += 1;
421 }
422
423 self.index_statement_spans(tokens, interner);
425 }
426
427 fn index_statement_spans(&mut self, tokens: &[Token], interner: &Interner) {
428 let mut i = 0;
429 while i < tokens.len() {
430 let is_stmt_keyword = matches!(
431 tokens[i].kind,
432 TokenType::Let
433 | TokenType::Set
434 | TokenType::If
435 | TokenType::While
436 | TokenType::Repeat
437 | TokenType::Return
438 | TokenType::Show
439 | TokenType::Give
440 | TokenType::Push
441 | TokenType::Pop
442 | TokenType::Call
443 | TokenType::Inspect
444 | TokenType::Check
445 | TokenType::Assert
446 | TokenType::Trust
447 | TokenType::Require
448 | TokenType::Requires
449 | TokenType::Ensures
450 | TokenType::Escape
451 | TokenType::Read
452 | TokenType::Write
453 | TokenType::Spawn
454 | TokenType::Send
455 | TokenType::Await
456 | TokenType::Sleep
457 | TokenType::Merge
458 | TokenType::Increase
459 | TokenType::Decrease
460 | TokenType::Listen
461 | TokenType::Sync
462 | TokenType::Mount
463 | TokenType::Launch
464 | TokenType::Receive
465 | TokenType::Stop
466 );
467 if is_stmt_keyword {
468 let start = tokens[i].span.start;
469 let keyword = interner.resolve(tokens[i].lexeme).to_string();
470 let mut end = tokens[i].span.end;
472 for j in (i + 1)..tokens.len() {
473 end = tokens[j].span.end;
474 if matches!(tokens[j].kind, TokenType::Period | TokenType::Dedent) {
475 break;
476 }
477 }
478 self.statement_spans.push((keyword, Span::new(start, end)));
479 }
480 i += 1;
481 }
482 }
483
484 fn index_references(&mut self, tokens: &[Token], interner: &Interner) {
485 for token in tokens {
486 if let Some(resolved) = resolve_token_name(token, interner) {
487 if matches!(token.kind, TokenType::BlockHeader { .. }) {
489 continue;
490 }
491 let name = resolved.to_string();
492 let ref_block = self.block_for_offset(token.span.start);
494 let def_idx = self.nearest_def(&name, ref_block);
495 self.references.push(Reference {
496 name,
497 span: token.span,
498 definition_idx: def_idx,
499 });
500 }
501 }
502 }
503
504 fn index_call_sites(&mut self, tokens: &[Token], interner: &Interner) {
508 for (i, token) in tokens.iter().enumerate() {
509 let Some(name) = resolve_token_name(token, interner) else { continue };
510
511 let is_call_form = matches!(
512 tokens.get(i + 1).map(|t| &t.kind),
513 Some(TokenType::LParen)
514 ) || matches!(
515 i.checked_sub(1).and_then(|p| tokens.get(p)).map(|t| &t.kind),
516 Some(TokenType::Call)
517 );
518 if !is_call_form {
519 continue;
520 }
521
522 let Some(callee) = self
523 .name_to_defs
524 .get(name)
525 .and_then(|indices| {
526 indices
527 .iter()
528 .copied()
529 .find(|&ix| self.definitions[ix].kind == DefinitionKind::Function)
530 })
531 else {
532 continue;
533 };
534 if self.definitions[callee].span == token.span {
535 continue; }
537
538 self.call_sites.push(CallSite {
539 caller: self.enclosing_function(token.span.start),
540 callee,
541 span: token.span,
542 });
543 }
544 }
545
546 pub fn enclosing_function(&self, offset: usize) -> Option<usize> {
548 let block = self
549 .block_spans
550 .iter()
551 .filter(|(_, block_type, span)| {
552 *block_type == BlockType::Function && span.start <= offset && offset < span.end
553 })
554 .min_by_key(|(_, _, span)| span.end - span.start)?;
555 let block_span = block.2;
556 self.definitions.iter().position(|d| {
557 d.kind == DefinitionKind::Function
558 && block_span.start <= d.span.start
559 && d.span.start < block_span.end
560 })
561 }
562
563 fn compute_scopes(&mut self) {
565 for i in 0..self.definitions.len() {
566 let def_start = self.definitions[i].span.start;
567 if self.definitions[i].span == Span::default() {
568 continue;
569 }
570 let mut best_block: Option<usize> = None;
571 let mut best_size = usize::MAX;
572 for (bi, (_name, _bt, bspan)) in self.block_spans.iter().enumerate() {
573 if def_start >= bspan.start && def_start < bspan.end {
574 let size = bspan.end - bspan.start;
575 if size < best_size {
576 best_size = size;
577 best_block = Some(bi);
578 }
579 }
580 }
581 self.definitions[i].scope = ScopeInfo {
582 block_idx: best_block,
583 depth: if best_block.is_some() { 1 } else { 0 },
584 };
585 }
586 }
587
588 fn block_for_offset(&self, offset: usize) -> Option<usize> {
590 let mut best: Option<usize> = None;
591 let mut best_size = usize::MAX;
592 for (bi, (_name, _bt, bspan)) in self.block_spans.iter().enumerate() {
593 if offset >= bspan.start && offset < bspan.end {
594 let size = bspan.end - bspan.start;
595 if size < best_size {
596 best_size = size;
597 best = Some(bi);
598 }
599 }
600 }
601 best
602 }
603
604 fn nearest_def(&self, name: &str, ref_block: Option<usize>) -> Option<usize> {
606 let indices = self.name_to_defs.get(name)?;
607 if indices.len() == 1 {
608 return Some(indices[0]);
609 }
610 if let Some(block_idx) = ref_block {
612 for &idx in indices {
613 if self.definitions[idx].scope.block_idx == Some(block_idx) {
614 return Some(idx);
615 }
616 }
617 }
618 indices.first().copied()
620 }
621
622 pub fn definition_at(&self, offset: usize) -> Option<&Definition> {
624 for reference in &self.references {
626 if offset >= reference.span.start && offset < reference.span.end {
627 if let Some(idx) = reference.definition_idx {
628 return self.definitions.get(idx);
629 }
630 }
631 }
632 for def in &self.definitions {
634 if offset >= def.span.start && offset < def.span.end {
635 return Some(def);
636 }
637 }
638 None
639 }
640
641 pub fn definition_at_scoped(&self, name: &str, offset: usize) -> Option<&Definition> {
644 let ref_block = self.block_for_offset(offset);
645 let idx = self.nearest_def(name, ref_block)?;
646 self.definitions.get(idx)
647 }
648
649 pub fn references_to(&self, name: &str) -> Vec<&Reference> {
651 self.references
652 .iter()
653 .filter(|r| r.name == name)
654 .collect()
655 }
656
657 pub fn references_in_scope(&self, name: &str, def_offset: usize) -> Vec<&Reference> {
659 let def_block = self.block_for_offset(def_offset);
660 self.references
661 .iter()
662 .filter(|r| {
663 if r.name != name {
664 return false;
665 }
666 let ref_block = self.block_for_offset(r.span.start);
667 ref_block == def_block
668 })
669 .collect()
670 }
671
672 pub fn definitions_of(&self, name: &str) -> Vec<&Definition> {
674 self.name_to_defs
675 .get(name)
676 .map(|indices| {
677 indices
678 .iter()
679 .filter_map(|&idx| self.definitions.get(idx))
680 .collect()
681 })
682 .unwrap_or_default()
683 }
684
685 pub fn block_name(&self, block_idx: usize) -> Option<&str> {
687 self.block_spans.get(block_idx).map(|(name, _, _)| name.as_str())
688 }
689}
690
691#[cfg(test)]
692mod tests {
693 use super::*;
694 use crate::pipeline::analyze;
695
696 #[test]
697 fn let_binding_has_nondefault_span() {
698 let result = analyze("## Main\n Let x be 5.\n");
699 assert!(result.errors.is_empty(), "Errors: {:?}", result.errors);
700 let defs = result.symbol_index.definitions_of("x");
701 assert_eq!(defs.len(), 1, "Expected 1 def for 'x', got {:?}", defs);
702 assert_ne!(defs[0].span, Span::default(),
703 "Definition span should not be default after fix");
704 }
705
706 #[test]
707 fn let_binding_span_points_to_source() {
708 let source = "## Main\n Let x be 5.\n";
709 let result = analyze(source);
710 assert!(result.errors.is_empty(), "Errors: {:?}", result.errors);
711 let defs = result.symbol_index.definitions_of("x");
712 assert_eq!(defs.len(), 1);
713 let span = defs[0].span;
714 let text = &source[span.start..span.end];
715 assert_eq!(text, "x", "Span should point to 'x' in source, got '{}'", text);
716 }
717
718 #[test]
719 fn definition_at_finds_variable() {
720 let source = "## Main\n Let x be 5.\n";
721 let result = analyze(source);
722 assert!(result.errors.is_empty(), "Errors: {:?}", result.errors);
723 let defs = result.symbol_index.definitions_of("x");
724 assert!(!defs.is_empty());
725 let span = defs[0].span;
726 let def = result.symbol_index.definition_at(span.start);
727 assert!(def.is_some(), "definition_at should find 'x' at its span");
728 assert_eq!(def.unwrap().name, "x");
729 }
730
731 #[test]
732 fn definitions_of_returns_correct_kind() {
733 let result = analyze("## Main\n Let x be 5.\n");
734 assert!(result.errors.is_empty(), "Errors: {:?}", result.errors);
735 let defs = result.symbol_index.definitions_of("x");
736 assert_eq!(defs[0].kind, DefinitionKind::Variable);
737 }
738
739 #[test]
740 fn definitions_of_unknown_returns_empty() {
741 let result = analyze("## Main\n Let x be 5.\n");
742 let defs = result.symbol_index.definitions_of("nonexistent");
743 assert!(defs.is_empty());
744 }
745
746 #[test]
747 fn references_to_finds_usages() {
748 let result = analyze("## Main\n Let x be 5.\n Show x.\n");
749 assert!(result.errors.is_empty(), "Errors: {:?}", result.errors);
750 let refs = result.symbol_index.references_to("x");
751 assert!(refs.len() >= 1, "Expected refs to 'x', got {}", refs.len());
752 }
753
754 #[test]
755 fn reference_linked_to_definition() {
756 let result = analyze("## Main\n Let x be 5.\n Show x.\n");
757 assert!(result.errors.is_empty(), "Errors: {:?}", result.errors);
758 let refs = result.symbol_index.references_to("x");
759 let linked: Vec<_> = refs.iter().filter(|r| r.definition_idx.is_some()).collect();
760 assert!(!linked.is_empty(), "At least one reference should link to a definition");
761 }
762
763 #[test]
764 fn let_binding_has_detail() {
765 let result = analyze("## Main\n Let x be 5.\n");
766 assert!(result.errors.is_empty(), "Errors: {:?}", result.errors);
767 let defs = result.symbol_index.definitions_of("x");
768 assert!(defs[0].detail.is_some(), "Definition should have detail");
769 assert!(defs[0].detail.as_ref().unwrap().contains("Let"),
770 "Detail should mention Let: {:?}", defs[0].detail);
771 }
772
773 #[test]
774 fn block_spans_populated() {
775 let result = analyze("## Main\n Let x be 5.\n");
776 assert!(!result.symbol_index.block_spans.is_empty(),
777 "block_spans should have at least one entry");
778 }
779
780 #[test]
781 fn multiple_variables_indexed() {
782 let result = analyze("## Main\n Let a be 1.\n Let b be 2.\n Let c be 3.\n");
783 assert!(result.errors.is_empty(), "Errors: {:?}", result.errors);
784 assert_eq!(result.symbol_index.definitions_of("a").len(), 1);
785 assert_eq!(result.symbol_index.definitions_of("b").len(), 1);
786 assert_eq!(result.symbol_index.definitions_of("c").len(), 1);
787 }
788
789 #[test]
790 fn definition_at_whitespace_returns_none() {
791 let source = "## Main\n Let x be 5.\n";
792 let result = analyze(source);
793 let past_end = source.len() + 5;
798 let def = result.symbol_index.definition_at(past_end);
799 assert!(def.is_none(), "definition_at beyond source should return None");
800 }
801
802 #[test]
803 fn references_to_exact_count() {
804 let source = "## Main\n Let x be 5.\n Show x.\n Set x to x + 1.\n";
805 let result = analyze(source);
806 assert!(result.errors.is_empty(), "Errors: {:?}", result.errors);
807 let refs = result.symbol_index.references_to("x");
808 assert!(refs.len() >= 3,
811 "Expected at least 3 references to 'x', got {}: {:?}",
812 refs.len(),
813 refs.iter().map(|r| (r.span.start, r.span.end)).collect::<Vec<_>>()
814 );
815 }
816
817 #[test]
818 fn definitions_of_function() {
819 let source = "## To greet (name: Text):\n Show name.\n## Main\n Call greet with \"Alice\".\n";
820 let result = analyze(source);
821 let defs = result.symbol_index.definitions_of("greet");
822 let func_defs: Vec<_> = defs.iter().filter(|d| d.kind == DefinitionKind::Function).collect();
823 assert!(!func_defs.is_empty(), "Expected a Function definition for 'greet', got defs: {:?}", defs);
824 assert_eq!(func_defs[0].kind, DefinitionKind::Function);
825 }
826
827 #[test]
828 fn parameter_has_own_span() {
829 let source = "## To greet (name: Text):\n Show name.\n";
830 let result = analyze(source);
831 assert!(result.errors.is_empty(), "Errors: {:?}", result.errors);
832
833 let func_defs = result.symbol_index.definitions_of("greet");
834 let param_defs = result.symbol_index.definitions_of("name");
835
836 assert!(!func_defs.is_empty(), "Should have function def for 'greet'");
837 assert!(!param_defs.is_empty(), "Should have param def for 'name'");
838
839 let func_span = func_defs.iter()
840 .find(|d| d.kind == DefinitionKind::Function)
841 .map(|d| d.span);
842 let param_span = param_defs.iter()
843 .find(|d| d.kind == DefinitionKind::Parameter)
844 .map(|d| d.span);
845
846 if let (Some(fs), Some(ps)) = (func_span, param_span) {
847 if fs != Span::default() && ps != Span::default() {
848 assert_ne!(fs, ps,
849 "Parameter 'name' span {:?} should differ from function 'greet' span {:?}",
850 ps, fs);
851 let param_text = &source[ps.start..ps.end];
852 assert_eq!(param_text, "name",
853 "Parameter span should point to 'name' in source, got '{}'", param_text);
854 }
855 }
856 }
857
858 #[test]
859 fn second_occurrence_found() {
860 let source = "## Main\n Let x be 5.\n Let x be 10.\n";
861 let result = analyze(source);
862 let defs = result.symbol_index.definitions_of("x");
864 assert!(!defs.is_empty(), "Expected at least one definition for 'x'");
866 }
867}
868
869pub fn resolve_token_name<'a>(token: &Token, interner: &'a Interner) -> Option<&'a str> {
871 match &token.kind {
872 TokenType::Identifier => Some(interner.resolve(token.lexeme)),
873 TokenType::ProperName(sym) => Some(interner.resolve(*sym)),
874 TokenType::Noun(sym) => Some(interner.resolve(*sym)),
875 TokenType::Adjective(sym) => Some(interner.resolve(*sym)),
876 TokenType::BlockHeader { .. } => Some(interner.resolve(token.lexeme)),
877 TokenType::Verb { .. } => Some(interner.resolve(token.lexeme)),
881 TokenType::Ambiguous { .. } => Some(interner.resolve(token.lexeme)),
884 _ => None,
885 }
886}
887
888fn find_token_span_for_name(tokens: &[Token], name: &str, interner: &Interner) -> Option<Span> {
891 find_token_span_for_name_after(tokens, name, interner, 0)
892}
893
894pub fn find_token_span_for_name_pub(tokens: &[Token], name: &str, interner: &Interner) -> Option<Span> {
896 find_token_span_for_name(tokens, name, interner)
897}
898
899pub fn find_last_token_span_for_name(
903 tokens: &[Token],
904 name: &str,
905 interner: &Interner,
906) -> Option<Span> {
907 tokens
908 .iter()
909 .rev()
910 .find(|t| {
911 !matches!(t.kind, TokenType::BlockHeader { .. })
912 && resolve_token_name(t, interner).map(|n| n == name).unwrap_or(false)
913 })
914 .map(|t| t.span)
915}
916
917fn find_token_span_for_name_after(
919 tokens: &[Token],
920 name: &str,
921 interner: &Interner,
922 after_offset: usize,
923) -> Option<Span> {
924 for token in tokens {
925 if token.span.start < after_offset {
926 continue;
927 }
928 if let Some(resolved) = resolve_token_name(token, interner) {
929 if resolved == name {
930 return Some(token.span);
931 }
932 }
933 }
934 None
935}
936
937pub fn find_keyword_span_before_name(
946 tokens: &[Token],
947 keyword: TokenType,
948 variable_name: &str,
949 interner: &Interner,
950) -> Option<Span> {
951 find_cause_keyword_span(tokens, keyword, variable_name, interner, usize::MAX)
952}
953
954pub fn find_cause_keyword_span(
956 tokens: &[Token],
957 keyword: TokenType,
958 variable_name: &str,
959 interner: &Interner,
960 before_offset: usize,
961) -> Option<Span> {
962 let discriminant = std::mem::discriminant(&keyword);
963 let mut best: Option<Span> = None;
964 for (i, token) in tokens.iter().enumerate() {
965 if std::mem::discriminant(&token.kind) != discriminant {
966 continue;
967 }
968 if token.span.start >= before_offset {
969 break;
970 }
971 let object_matches = tokens
972 .get(i + 1)
973 .and_then(|t| resolve_token_name(t, interner))
974 .map(|resolved| resolved == variable_name)
975 .unwrap_or(false);
976 if object_matches {
977 best = Some(token.span);
978 }
979 }
980 best
981}