perl_semantic_analyzer/analysis/semantic/references.rs
1//! Reference resolution and symbol visibility for Navigate/Analyze workflows.
2
3use crate::SourceLocation;
4use crate::symbol::{ScopeId, Symbol, SymbolKind};
5
6use super::SemanticAnalyzer;
7
8impl SemanticAnalyzer {
9 /// Resolve a reference to its symbol definitions, handling cross-package lookups.
10 pub(super) fn resolve_reference_to_symbols(
11 &self,
12 reference: &crate::symbol::SymbolReference,
13 ) -> Vec<&Symbol> {
14 // Handle qualified names like Foo::bar
15 if let Some((pkg, name)) = reference.name.rsplit_once("::") {
16 if let Some(pkg_syms) = self.symbol_table.symbols.get(pkg) {
17 let mut results = Vec::new();
18 for sym in pkg_syms {
19 if sym.kind == SymbolKind::Package {
20 // Find the scope associated with this package symbol
21 let pkg_scope = self
22 .symbol_table
23 .scopes
24 .values()
25 .find(|s| {
26 s.kind == crate::symbol::ScopeKind::Package
27 && s.location.start == sym.location.start
28 && s.location.end == sym.location.end
29 })
30 .map(|s| s.id)
31 .unwrap_or(sym.scope_id);
32 // Symbols may live in an inner block scope
33 let search_scope = self
34 .symbol_table
35 .scopes
36 .values()
37 .find(|s| s.parent == Some(pkg_scope))
38 .map(|s| s.id)
39 .unwrap_or(pkg_scope);
40 results.extend(self.symbol_table.find_symbol(
41 name,
42 search_scope,
43 reference.kind,
44 ));
45 }
46 }
47 results
48 } else {
49 self.symbol_table.find_symbol(name, reference.scope_id, reference.kind)
50 }
51 } else {
52 self.symbol_table.find_symbol(&reference.name, reference.scope_id, reference.kind)
53 }
54 }
55
56 /// Find all references to a symbol at a given position for Navigate/Analyze workflows.
57 pub fn find_all_references(
58 &self,
59 position: usize,
60 include_declaration: bool,
61 ) -> Vec<SourceLocation> {
62 // First find the symbol at this position (either definition or reference)
63 let symbol = if let Some(def) = self.find_definition(position) {
64 Some(def)
65 } else {
66 // Check if we're on a reference
67 for refs in self.symbol_table.references.values() {
68 for reference in refs {
69 if reference.location.start <= position && reference.location.end >= position {
70 // Found a reference, get its definition to get the symbol ID
71 let symbols = self.symbol_table.find_symbol(
72 &reference.name,
73 reference.scope_id,
74 reference.kind,
75 );
76 if let Some(first_symbol) = symbols.first() {
77 return self
78 .find_all_references_for_symbol(first_symbol, include_declaration);
79 }
80 }
81 }
82 }
83 None
84 };
85
86 if let Some(symbol) = symbol {
87 return self.find_all_references_for_symbol(symbol, include_declaration);
88 }
89
90 Vec::new()
91 }
92
93 /// Find all references for a specific symbol.
94 pub(super) fn find_all_references_for_symbol(
95 &self,
96 symbol: &Symbol,
97 include_declaration: bool,
98 ) -> Vec<SourceLocation> {
99 let mut locations = Vec::new();
100
101 // Include the declaration if requested
102 if include_declaration {
103 locations.push(symbol.location);
104 }
105
106 // Find all references to this symbol by name
107 if let Some(refs) = self.symbol_table.references.get(&symbol.name) {
108 for reference in refs {
109 // Only include references of the same kind and in scope where the symbol is visible
110 if reference.kind == symbol.kind {
111 // Check if the symbol is visible from this reference's scope
112 if self.is_symbol_visible(symbol, reference.scope_id) {
113 locations.push(reference.location);
114 }
115 }
116 }
117 }
118
119 locations
120 }
121
122 /// Check if a symbol is visible from a given scope.
123 pub(super) fn is_symbol_visible(&self, symbol: &Symbol, scope_id: ScopeId) -> bool {
124 // For now, simple visibility check:
125 // - Symbols in the same scope are visible
126 // - Symbols in parent scopes are visible
127 // - Package-level symbols are visible from package scopes
128
129 if symbol.scope_id == scope_id {
130 return true;
131 }
132
133 // Check if scope_id is a descendant of symbol.scope_id
134 let mut current_scope = scope_id;
135 while let Some(scope) = self.symbol_table.scopes.get(¤t_scope) {
136 if scope.parent == Some(symbol.scope_id) {
137 return true;
138 }
139 if let Some(parent) = scope.parent {
140 current_scope = parent;
141 } else {
142 break;
143 }
144 }
145
146 // For package-level symbols (scope_id 0), always visible
147 symbol.scope_id == 0
148 }
149}