Skip to main content

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(&current_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}