gruel_air/analysis_state.rs
1//! Per-function mutable state for semantic analysis.
2//!
3//! This module contains state that is mutated during function analysis.
4//! Each function can have its own `FunctionAnalysisState`, which is then
5//! merged after parallel analysis completes.
6//!
7//! # Array Type Handling (ADR-0024)
8//!
9//! Array types are handled by the shared `TypeInternPool` in `SemaContext`,
10//! which is thread-safe and handles deduplication automatically. Per-function
11//! array tracking has been removed - array types created during function analysis
12//! go directly to the shared pool.
13
14use rustc_hash::FxHashMap as HashMap;
15
16use gruel_util::CompileWarning;
17
18/// Per-function mutable state during semantic analysis.
19///
20/// This struct contains all mutable state that is modified during function
21/// body analysis. For parallel analysis, each function gets its own instance,
22/// and results are merged afterward.
23///
24/// # Contents
25///
26/// - String literals encountered
27/// - Warnings generated
28///
29/// # Note on Array Types
30///
31/// Array types are handled by the shared `TypeInternPool` in `SemaContext`.
32/// They are no longer tracked per-function.
33///
34/// # Merging
35///
36/// After parallel analysis, use `merge_into` to combine results:
37/// - Strings are deduplicated
38/// - Warnings are concatenated
39#[derive(Debug, Default)]
40pub struct FunctionAnalysisState {
41 /// String table for deduplication.
42 pub string_table: HashMap<String, u32>,
43 /// String literals in order of creation.
44 pub strings: Vec<String>,
45 /// Byte-blob literals (from `@embed_file`) in order of creation. Not
46 /// deduplicated — see `AnalysisContext::add_local_bytes`.
47 pub bytes: Vec<Vec<u8>>,
48 /// Warnings collected during analysis.
49 pub warnings: Vec<CompileWarning>,
50}
51
52impl FunctionAnalysisState {
53 /// Create a new empty analysis state.
54 pub fn new() -> Self {
55 Self::default()
56 }
57
58 /// Add a string to the string table, returning its index.
59 /// Deduplicates identical strings.
60 pub fn add_string(&mut self, content: String) -> u32 {
61 use std::collections::hash_map::Entry;
62 match self.string_table.entry(content) {
63 Entry::Occupied(e) => *e.get(),
64 Entry::Vacant(e) => {
65 let id = self.strings.len() as u32;
66 self.strings.push(e.key().clone());
67 e.insert(id);
68 id
69 }
70 }
71 }
72
73 /// Add a warning.
74 pub fn add_warning(&mut self, warning: CompileWarning) {
75 self.warnings.push(warning);
76 }
77}
78
79/// Merged state from multiple function analyses.
80///
81/// This is the result of merging all `FunctionAnalysisState` instances
82/// after parallel analysis completes.
83///
84/// # Note on Array Types
85///
86/// Array types are handled by the shared `TypeInternPool` in `SemaContext`.
87/// They are no longer merged here.
88#[derive(Debug, Default)]
89pub struct MergedAnalysisState {
90 /// All string literals (deduplicated).
91 pub strings: Vec<String>,
92 /// Mapping from string content to final index.
93 pub string_map: HashMap<String, u32>,
94 /// All byte-blob literals (concatenated, never deduplicated).
95 pub bytes: Vec<Vec<u8>>,
96 /// All warnings from all functions.
97 pub warnings: Vec<CompileWarning>,
98}
99
100impl MergedAnalysisState {
101 /// Create a new empty merged state.
102 pub fn new() -> Self {
103 Self::default()
104 }
105
106 /// Merge a function's analysis state into this merged state.
107 ///
108 /// Returns a remapping for string indices so the function's AIR
109 /// can be updated with the final IDs.
110 ///
111 /// # Note
112 ///
113 /// Array type merging is no longer needed.
114 /// Array types go directly to the shared `TypeInternPool`.
115 pub fn merge_function_state(&mut self, state: FunctionAnalysisState) -> AnalysisStateRemapping {
116 let mut string_remap = HashMap::default();
117
118 // Merge strings (deduplicate by content)
119 for (content, old_id) in state.string_table {
120 let new_id = if let Some(&id) = self.string_map.get(&content) {
121 id
122 } else {
123 let id = self.strings.len() as u32;
124 self.strings.push(content.clone());
125 self.string_map.insert(content, id);
126 id
127 };
128 if old_id != new_id {
129 string_remap.insert(old_id, new_id);
130 }
131 }
132
133 // Merge bytes (no deduplication — embed_file is rare and each call
134 // gets a fresh entry). Local IDs shift by the current pool size.
135 let mut bytes_remap = HashMap::default();
136 let bytes_offset = self.bytes.len() as u32;
137 for (local_id, blob) in state.bytes.into_iter().enumerate() {
138 let new_id = bytes_offset + local_id as u32;
139 self.bytes.push(blob);
140 if (local_id as u32) != new_id {
141 bytes_remap.insert(local_id as u32, new_id);
142 }
143 }
144
145 // Merge warnings (no deduplication needed)
146 self.warnings.extend(state.warnings);
147
148 AnalysisStateRemapping {
149 string_remap,
150 bytes_remap,
151 }
152 }
153}
154
155/// Remapping information for updating AIR after merging.
156///
157/// When function analysis states are merged, IDs may change due to
158/// deduplication. This struct provides the mapping from old to new IDs.
159///
160/// # Note
161///
162/// Array type remapping is no longer needed.
163/// Array types use the shared `TypeInternPool` which handles deduplication.
164#[derive(Debug, Default)]
165pub struct AnalysisStateRemapping {
166 /// Mapping from old string index to new string index.
167 /// Only contains entries where the index changed.
168 pub string_remap: HashMap<u32, u32>,
169 /// Mapping from old byte-blob index to new byte-blob index.
170 pub bytes_remap: HashMap<u32, u32>,
171}
172
173impl AnalysisStateRemapping {
174 /// Check if any remapping is needed.
175 pub fn is_empty(&self) -> bool {
176 self.string_remap.is_empty() && self.bytes_remap.is_empty()
177 }
178
179 /// Remap a string index if needed.
180 pub fn remap_string(&self, id: u32) -> u32 {
181 self.string_remap.get(&id).copied().unwrap_or(id)
182 }
183
184 /// Remap a byte-blob index if needed.
185 pub fn remap_bytes(&self, id: u32) -> u32 {
186 self.bytes_remap.get(&id).copied().unwrap_or(id)
187 }
188}