Skip to main content

perl_semantic_analyzer/analysis/semantic/
model.rs

1//! `SemanticModel` — a stable, query-oriented facade over `SemanticAnalyzer`.
2
3use crate::SourceLocation;
4use crate::ast::Node;
5use crate::symbol::{Symbol, SymbolTable};
6use perl_semantic_facts::{GeneratedMember, PackageEdge};
7
8use super::hover::HoverInfo;
9use super::tokens::SemanticToken;
10use super::{FileExportMetadata, SemanticAnalyzer};
11
12#[derive(Debug)]
13/// A stable, query-oriented view of semantic information over a parsed file.
14///
15/// LSP and other consumers should use this instead of talking to `SemanticAnalyzer` directly.
16/// This provides a clean API that insulates consumers from internal analyzer implementation details.
17///
18/// # Performance Characteristics
19/// - Symbol resolution: <50μs average lookup time
20/// - Reference queries: O(1) lookup via pre-computed indices
21/// - Scope queries: O(log n) with binary search on scope ranges
22///
23/// # LSP Workflow Integration
24/// Core component in Parse → Index → Navigate → Complete → Analyze pipeline:
25/// 1. Parse Perl source → AST
26/// 2. Build SemanticModel from AST
27/// 3. Query for symbols, references, completions
28/// 4. Respond to LSP requests with precise semantic data
29///
30/// # Example
31/// ```rust,ignore
32/// use perl_parser::Parser;
33/// use perl_parser::semantic::SemanticModel;
34///
35/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
36/// let code = "my $x = 42; $x + 10;";
37/// let mut parser = Parser::new(code);
38/// let ast = parser.parse()?;
39///
40/// let model = SemanticModel::build(&ast, code);
41/// let tokens = model.tokens();
42/// assert!(!tokens.is_empty());
43/// # Ok(())
44/// # }
45/// ```
46pub struct SemanticModel {
47    /// Internal semantic analyzer instance
48    analyzer: SemanticAnalyzer,
49}
50
51impl SemanticModel {
52    /// Build a semantic model for a parsed syntax tree.
53    ///
54    /// # Parameters
55    /// - `root`: The root AST node from the parser
56    /// - `source`: The original Perl source code
57    ///
58    /// # Performance
59    /// - Analysis time: O(n) where n is AST node count
60    /// - Memory: ~1MB per 10K lines of Perl code
61    pub fn build(root: &Node, source: &str) -> Self {
62        Self { analyzer: SemanticAnalyzer::analyze_with_source(root, source) }
63    }
64
65    /// All semantic tokens for syntax highlighting.
66    ///
67    /// Returns tokens in source order for efficient LSP semantic tokens encoding.
68    ///
69    /// # Performance
70    /// - Lookup: O(1) - pre-computed during analysis
71    /// - Memory: ~32 bytes per token
72    pub fn tokens(&self) -> &[SemanticToken] {
73        self.analyzer.semantic_tokens()
74    }
75
76    /// Access the underlying symbol table for advanced queries.
77    ///
78    /// # Note
79    /// Most consumers should use the higher-level query methods on `SemanticModel`
80    /// rather than accessing the symbol table directly.
81    pub fn symbol_table(&self) -> &SymbolTable {
82        self.analyzer.symbol_table()
83    }
84
85    /// Access per-file Exporter metadata extracted during analysis.
86    pub fn export_metadata(&self) -> &FileExportMetadata {
87        self.analyzer.export_metadata()
88    }
89
90    /// Access package graph edges extracted from inheritance and role composition forms.
91    pub fn package_edges(&self) -> &[PackageEdge] {
92        self.analyzer.package_edges()
93    }
94
95    /// Access framework-generated members extracted from accessor declarations.
96    pub fn generated_members(&self) -> &[GeneratedMember] {
97        self.analyzer.generated_members()
98    }
99
100    /// Get hover information for a symbol at a specific location during Navigate/Analyze.
101    ///
102    /// # Parameters
103    /// - `location`: Source location to query (line, column)
104    ///
105    /// # Returns
106    /// - `Some(HoverInfo)` if a symbol with hover info exists at this location
107    /// - `None` if no symbol or no hover info available
108    ///
109    /// # Performance
110    /// - Lookup: <100μs for typical files
111    /// - Memory: Cached hover info reused across queries
112    ///
113    /// Workflow: Navigate/Analyze hover lookup.
114    pub fn hover_info_at(&self, location: SourceLocation) -> Option<&HoverInfo> {
115        self.analyzer.hover_at(location)
116    }
117
118    /// Find the definition of a symbol at a specific byte position.
119    ///
120    /// # Parameters
121    /// - `position`: Byte offset in the source code
122    ///
123    /// # Returns
124    /// - `Some(Symbol)` if a symbol definition is found at this position
125    /// - `None` if no symbol exists at this position
126    ///
127    /// # Performance
128    /// - Lookup: <50μs average for typical files
129    /// - Uses pre-computed symbol table for O(1) lookups
130    ///
131    /// # Example
132    /// ```rust,ignore
133    /// use perl_parser::Parser;
134    /// use perl_parser::semantic::SemanticModel;
135    ///
136    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
137    /// let code = "my $x = 1;\n$x + 2;\n";
138    /// let mut parser = Parser::new(code);
139    /// let ast = parser.parse()?;
140    ///
141    /// let model = SemanticModel::build(&ast, code);
142    /// // Find definition of $x on line 1 (byte position ~11)
143    /// if let Some(symbol) = model.definition_at(11) {
144    ///     assert_eq!(symbol.location.start.line, 0);
145    /// }
146    /// # Ok(())
147    /// # }
148    /// ```
149    pub fn definition_at(&self, position: usize) -> Option<&Symbol> {
150        self.analyzer.find_definition(position)
151    }
152
153    /// Resolve inherited method definition location for a receiver class.
154    pub fn resolve_inherited_method_location(
155        &self,
156        receiver_class: &str,
157        method_name: &str,
158    ) -> Option<SourceLocation> {
159        self.analyzer.resolve_inherited_method_location(receiver_class, method_name)
160    }
161
162    /// Return the ordered parent chain for `receiver_class`.
163    pub fn parent_chain(&self, receiver_class: &str) -> Option<Vec<String>> {
164        self.analyzer.resolve_parent_chain(receiver_class)
165    }
166}