gruel_air/
function_analyzer.rs

1//! Per-function analysis state and analyzer.
2//!
3//! This module contains `FunctionAnalyzer`, which holds the mutable state
4//! needed during function body analysis. Each function gets its own analyzer
5//! instance, enabling parallel analysis.
6//!
7//! # Architecture
8//!
9//! `FunctionAnalyzer` is designed to work with a shared immutable `SemaContext`.
10//! The split enables:
11//! - Parallel function body analysis (each function has independent mutable state)
12//! - Better separation of concerns (immutable type info vs mutable analysis state)
13//! - Post-analysis merging of results (strings, warnings)
14//!
15//! Array types are managed by the thread-safe `TypeInternPool` in `SemaContext`,
16//! allowing parallel creation without local buffering or post-merge remapping.
17
18use std::collections::HashMap;
19
20use gruel_error::{CompileError, CompileResult, CompileWarning, ErrorKind, PreviewFeature};
21use gruel_span::Span;
22use lasso::Spur;
23
24use crate::inference::InferType;
25use crate::sema_context::SemaContext;
26use crate::types::{ArrayTypeId, Type};
27
28/// Per-function mutable state during semantic analysis.
29///
30/// This struct contains all mutable state that is modified during function
31/// body analysis. For parallel analysis, each function gets its own instance,
32/// and results are merged afterward.
33///
34/// # Separation from SemaContext
35///
36/// `FunctionAnalyzer` holds mutable state while `SemaContext` holds immutable
37/// type information. This separation enables:
38/// - Sharing `SemaContext` across parallel function analyses
39/// - Independent mutable state per function
40/// - Post-analysis merging of results (strings, warnings)
41///
42/// Array types are created via the thread-safe `TypeInternPool` in `SemaContext`,
43/// so they don't require local buffering or post-merge remapping.
44#[derive(Debug)]
45pub struct FunctionAnalyzer<'a, 'ctx> {
46    /// Reference to the shared immutable context.
47    pub ctx: &'a SemaContext<'ctx>,
48    /// String table for deduplication.
49    string_table: HashMap<String, u32>,
50    /// String literals in order of creation.
51    strings: Vec<String>,
52    /// Warnings collected during analysis.
53    warnings: Vec<CompileWarning>,
54}
55
56/// Output from analyzing a single function.
57#[derive(Debug)]
58pub struct FunctionAnalyzerOutput {
59    /// String literals (deduplicated).
60    pub strings: Vec<String>,
61    /// Warnings collected during analysis.
62    pub warnings: Vec<CompileWarning>,
63}
64
65impl<'a, 'ctx> FunctionAnalyzer<'a, 'ctx> {
66    /// Create a new function analyzer with a reference to the shared context.
67    pub fn new(ctx: &'a SemaContext<'ctx>) -> Self {
68        Self {
69            ctx,
70            string_table: HashMap::new(),
71            strings: Vec::new(),
72            warnings: Vec::new(),
73        }
74    }
75
76    /// Consume the analyzer and return its output.
77    pub fn into_output(self) -> FunctionAnalyzerOutput {
78        FunctionAnalyzerOutput {
79            strings: self.strings,
80            warnings: self.warnings,
81        }
82    }
83
84    /// Add a string to the string table, returning its index.
85    /// Deduplicates identical strings.
86    pub fn add_string(&mut self, content: String) -> u32 {
87        use std::collections::hash_map::Entry;
88        match self.string_table.entry(content) {
89            Entry::Occupied(e) => *e.get(),
90            Entry::Vacant(e) => {
91                let id = self.strings.len() as u32;
92                self.strings.push(e.key().clone());
93                e.insert(id);
94                id
95            }
96        }
97    }
98
99    /// Add a warning.
100    pub fn add_warning(&mut self, warning: CompileWarning) {
101        self.warnings.push(warning);
102    }
103
104    /// Get or create an array type, returning its ID.
105    ///
106    /// Delegates to the thread-safe `TypeInternPool` in `SemaContext`.
107    pub fn get_or_create_array_type(&self, element_type: Type, length: u64) -> ArrayTypeId {
108        self.ctx.get_or_create_array_type(element_type, length)
109    }
110
111    /// Get an array type definition by ID.
112    ///
113    /// Returns `(element_type, length)` for the array.
114    pub fn get_array_type_def(&self, id: ArrayTypeId) -> (Type, u64) {
115        self.ctx.get_array_type_def(id)
116    }
117
118    /// Pre-create array types from a resolved InferType.
119    ///
120    /// This walks the InferType recursively and ensures all array types that will
121    /// be needed during `infer_type_to_type` conversion are created beforehand.
122    ///
123    /// With the thread-safe `TypeInternPool`, this is no longer strictly necessary
124    /// since `infer_type_to_type` can create array types on-demand. However, it's
125    /// kept for explicit documentation of intent and potential future optimizations.
126    pub fn pre_create_array_types_from_infer_type(&self, ty: &InferType) {
127        match ty {
128            InferType::Array { element, length } => {
129                // First recursively process nested array types
130                self.pre_create_array_types_from_infer_type(element);
131
132                // Convert the element type to get the concrete Type
133                let elem_ty = self.infer_type_to_type(element);
134                if elem_ty != Type::ERROR {
135                    // Pre-create this array type
136                    self.get_or_create_array_type(elem_ty, *length);
137                }
138            }
139            InferType::Concrete(_)
140            | InferType::Var(_)
141            | InferType::IntLiteral
142            | InferType::FloatLiteral => {
143                // Non-array types don't need pre-creation
144            }
145        }
146    }
147
148    /// Convert a fully-resolved InferType to a concrete Type.
149    pub fn infer_type_to_type(&self, ty: &InferType) -> Type {
150        match ty {
151            InferType::Concrete(t) => *t,
152            InferType::Var(_) => Type::ERROR,
153            InferType::IntLiteral => Type::I32,
154            InferType::FloatLiteral => Type::F64,
155            InferType::Array { element, length } => {
156                let elem_ty = self.infer_type_to_type(element);
157                if elem_ty == Type::ERROR {
158                    return Type::ERROR;
159                }
160                // Use the thread-safe registry to get or create the array type
161                let id = self.get_or_create_array_type(elem_ty, *length);
162                Type::new_array(id)
163            }
164        }
165    }
166
167    /// Check that a preview feature is enabled.
168    pub fn require_preview(
169        &self,
170        feature: PreviewFeature,
171        what: &str,
172        span: Span,
173    ) -> CompileResult<()> {
174        if self.ctx.preview_features.contains(&feature) {
175            Ok(())
176        } else {
177            Err(CompileError::new(
178                ErrorKind::PreviewFeatureRequired {
179                    feature,
180                    what: what.to_string(),
181                },
182                span,
183            )
184            .with_help(format!(
185                "use `--preview {}` to enable this feature ({})",
186                feature.name(),
187                feature.adr()
188            )))
189        }
190    }
191
192    /// Get a human-readable name for a type.
193    /// Delegates to context for most types but handles local array types.
194    pub fn format_type_name(&self, ty: Type) -> String {
195        if let Some(array_id) = ty.as_array() {
196            let (element_type, length) = self.get_array_type_def(array_id);
197            format!("[{}; {}]", self.format_type_name(element_type), length)
198        } else {
199            self.ctx.format_type_name(ty)
200        }
201    }
202
203    /// Check if a type is a Copy type.
204    /// Delegates to context for most types but handles local array types.
205    pub fn is_type_copy(&self, ty: Type) -> bool {
206        if let Some(array_id) = ty.as_array() {
207            let (element_type, _length) = self.get_array_type_def(array_id);
208            self.is_type_copy(element_type)
209        } else {
210            self.ctx.is_type_copy(ty)
211        }
212    }
213
214    /// Get the number of ABI slots required for a type.
215    /// Delegates to context for most types but handles local array types.
216    pub fn abi_slot_count(&self, ty: Type) -> u32 {
217        if let Some(array_id) = ty.as_array() {
218            let (element_type, length) = self.get_array_type_def(array_id);
219            let element_slots = self.abi_slot_count(element_type);
220            element_slots * length as u32
221        } else {
222            self.ctx.abi_slot_count(ty)
223        }
224    }
225
226    /// Resolve a type symbol to a Type.
227    pub fn resolve_type(&mut self, type_sym: Spur, span: Span) -> CompileResult<Type> {
228        let type_name = self.ctx.interner.resolve(&type_sym);
229
230        // Check primitive types
231        match type_name {
232            "i8" => return Ok(Type::I8),
233            "i16" => return Ok(Type::I16),
234            "i32" => return Ok(Type::I32),
235            "i64" => return Ok(Type::I64),
236            "u8" => return Ok(Type::U8),
237            "u16" => return Ok(Type::U16),
238            "u32" => return Ok(Type::U32),
239            "u64" => return Ok(Type::U64),
240            "bool" => return Ok(Type::BOOL),
241            "()" => return Ok(Type::UNIT),
242            "!" => return Ok(Type::NEVER),
243            _ => {}
244        }
245
246        if let Some(struct_id) = self.ctx.get_struct(type_sym) {
247            Ok(Type::new_struct(struct_id))
248        } else if let Some(enum_id) = self.ctx.get_enum(type_sym) {
249            Ok(Type::new_enum(enum_id))
250        } else {
251            // Check for array type syntax: [T; N]
252            if let Some((element_type, length)) = crate::types::parse_array_type_syntax(type_name) {
253                // Resolve the element type first
254                let element_sym = self.ctx.interner.get_or_intern(&element_type);
255                let element_ty = self.resolve_type(element_sym, span)?;
256                // Get or create the array type
257                let array_type_id = self.get_or_create_array_type(element_ty, length);
258                Ok(Type::new_array(array_type_id))
259            } else if let Some((pointee_type, mutability)) =
260                crate::types::parse_pointer_type_syntax(type_name)
261            {
262                // Resolve the pointee type first
263                let pointee_sym = self.ctx.interner.get_or_intern(&pointee_type);
264                let pointee_ty = self.resolve_type(pointee_sym, span)?;
265                // Create the pointer type
266                match mutability {
267                    crate::types::PtrMutability::Const => {
268                        let ptr_id = self.ctx.get_or_create_ptr_const_type(pointee_ty);
269                        Ok(Type::new_ptr_const(ptr_id))
270                    }
271                    crate::types::PtrMutability::Mut => {
272                        let ptr_id = self.ctx.get_or_create_ptr_mut_type(pointee_ty);
273                        Ok(Type::new_ptr_mut(ptr_id))
274                    }
275                }
276            } else {
277                Err(CompileError::new(
278                    ErrorKind::UnknownType(type_name.to_string()),
279                    span,
280                ))
281            }
282        }
283    }
284
285    /// Access the warnings collected during analysis.
286    pub fn warnings(&self) -> &[CompileWarning] {
287        &self.warnings
288    }
289
290    /// Access the strings collected during analysis.
291    pub fn strings(&self) -> &[String] {
292        &self.strings
293    }
294}
295
296/// Merge multiple function analyzer outputs into a single result.
297///
298/// This is used after parallel analysis to combine results from all functions.
299/// Array types are managed by the thread-safe `TypeInternPool` in `SemaContext`,
300/// so only strings and warnings need merging.
301#[derive(Debug, Default)]
302pub struct MergedFunctionOutput {
303    /// All string literals (deduplicated).
304    pub strings: Vec<String>,
305    /// Mapping from string content to final index.
306    pub string_map: HashMap<String, u32>,
307    /// All warnings from all functions.
308    pub warnings: Vec<CompileWarning>,
309}
310
311impl MergedFunctionOutput {
312    /// Create a new empty merged output.
313    pub fn new() -> Self {
314        Self::default()
315    }
316
317    /// Merge a function's output into this merged result.
318    ///
319    /// Returns a remapping for string indices so the function's AIR
320    /// can be updated with the final IDs.
321    pub fn merge_function_output(
322        &mut self,
323        output: FunctionAnalyzerOutput,
324    ) -> FunctionOutputRemapping {
325        let mut string_remap = HashMap::new();
326
327        // Merge strings (deduplicate by content)
328        for (idx, content) in output.strings.into_iter().enumerate() {
329            let old_id = idx as u32;
330            let new_id = if let Some(&id) = self.string_map.get(&content) {
331                id
332            } else {
333                let id = self.strings.len() as u32;
334                self.strings.push(content.clone());
335                self.string_map.insert(content, id);
336                id
337            };
338            if old_id != new_id {
339                string_remap.insert(old_id, new_id);
340            }
341        }
342
343        // Merge warnings
344        self.warnings.extend(output.warnings);
345
346        FunctionOutputRemapping { string_remap }
347    }
348}
349
350/// Remapping information for updating AIR after merging.
351#[derive(Debug, Default)]
352pub struct FunctionOutputRemapping {
353    /// Mapping from old string index to new string index.
354    pub string_remap: HashMap<u32, u32>,
355}
356
357impl FunctionOutputRemapping {
358    /// Check if any remapping is needed.
359    pub fn is_empty(&self) -> bool {
360        self.string_remap.is_empty()
361    }
362
363    /// Remap a string index if needed.
364    pub fn remap_string(&self, id: u32) -> u32 {
365        self.string_remap.get(&id).copied().unwrap_or(id)
366    }
367}