1#[derive(Debug, Clone, serde::Serialize)]
7pub struct IrDiagnostic {
8 pub function_name: String,
9 pub message: String,
10 pub suggestion: Option<String>,
11 pub code: Option<String>,
12}
13
14impl IrDiagnostic {
15 pub fn display(&self) -> String {
17 let mut out = format!("function '{}': {}", self.function_name, self.message);
18 if let Some(ref suggestion) = self.suggestion {
19 out.push_str(&format!("\n help: {}", suggestion));
20 }
21 out
22 }
23}
24
25struct LoweringContext<'a> {
26 function_name: String,
27 is_safe: bool,
28 param_index_map: HashMap<String, usize>,
29 param_types: &'a [ValueType],
30 return_slots: Vec<Option<usize>>,
31 return_types: Vec<ValueType>,
32 state_variables: &'a [StateVariableMetadata],
33 state_index_map: &'a HashMap<String, usize>,
34 state_types: &'a [ValueType],
35 defined_struct_types: &'a [ValueType],
40 event_index_map: &'a HashMap<String, usize>,
41 event_signature_map: &'a HashMap<String, Vec<ManifestType>>,
42 enum_variant_map: &'a HashMap<String, HashMap<String, u64>>,
43 contract_types: &'a HashSet<String>,
44 selector_registry: &'a SelectorRegistry,
45 function_names: &'a HashSet<String>,
46 function_overloads: &'a HashMap<(String, usize), String>,
47 function_param_names: &'a HashMap<(String, usize), Vec<String>>,
50 void_functions: &'a HashSet<String>,
53 super_method_map: &'a HashMap<String, String>,
56 local_index_map: HashMap<String, Vec<usize>>,
57 local_types: HashMap<usize, ValueType>,
58 scope_stack: Vec<Vec<String>>,
59 storage_aliases: HashMap<String, StorageReference>,
60 call_data_locals: HashMap<usize, String>,
61 local_count: u16,
62 label_counter: usize,
63 loop_stack: Vec<LoopLabels>,
64 errors: Vec<IrDiagnostic>,
65}
66
67impl<'a> LoweringContext<'a> {
68 #[allow(clippy::too_many_arguments)]
69 fn new(
70 function_name: &str,
71 is_safe: bool,
72 param_index_map: HashMap<String, usize>,
73 param_types: &'a [ValueType],
74 state_variables: &'a [StateVariableMetadata],
75 state_index_map: &'a HashMap<String, usize>,
76 state_types: &'a [ValueType],
77 defined_struct_types: &'a [ValueType],
78 event_index_map: &'a HashMap<String, usize>,
79 event_signature_map: &'a HashMap<String, Vec<ManifestType>>,
80 enum_variant_map: &'a HashMap<String, HashMap<String, u64>>,
81 contract_types: &'a HashSet<String>,
82 selector_registry: &'a SelectorRegistry,
83 function_names: &'a HashSet<String>,
84 function_overloads: &'a HashMap<(String, usize), String>,
85 function_param_names: &'a HashMap<(String, usize), Vec<String>>,
86 void_functions: &'a HashSet<String>,
87 super_method_map: &'a HashMap<String, String>,
88 ) -> Self {
89 Self {
90 function_name: function_name.to_string(),
91 is_safe,
92 param_index_map,
93 param_types,
94 return_slots: Vec::new(),
95 return_types: Vec::new(),
96 state_variables,
97 state_index_map,
98 state_types,
99 defined_struct_types,
100 event_index_map,
101 event_signature_map,
102 enum_variant_map,
103 contract_types,
104 selector_registry,
105 function_names,
106 function_overloads,
107 function_param_names,
108 void_functions,
109 super_method_map,
110 local_index_map: HashMap::new(),
111 local_types: HashMap::new(),
112 scope_stack: vec![Vec::new()],
113 storage_aliases: HashMap::new(),
114 call_data_locals: HashMap::new(),
115 local_count: 0,
116 label_counter: 0,
117 loop_stack: Vec::new(),
118 errors: Vec::new(),
119 }
120 }
121
122 fn set_return_info(&mut self, slots: Vec<Option<usize>>, types: Vec<ValueType>) {
123 self.return_slots = slots;
124 self.return_types = types;
125 }
126
127 fn return_slots(&self) -> &[Option<usize>] {
128 &self.return_slots
129 }
130
131 fn return_types(&self) -> &[ValueType] {
132 &self.return_types
133 }
134
135 fn next_label(&mut self) -> usize {
136 let label = self.label_counter;
137 self.label_counter += 1;
138 label
139 }
140
141 fn push_loop(&mut self, continue_label: usize, break_label: usize) {
142 self.loop_stack.push(LoopLabels {
143 continue_label,
144 break_label,
145 });
146 }
147
148 fn pop_loop(&mut self) {
149 self.loop_stack.pop();
150 }
151
152 fn break_target(&self) -> Option<usize> {
153 self.loop_stack.last().map(|labels| labels.break_label)
154 }
155
156 fn continue_target(&self) -> Option<usize> {
157 self.loop_stack.last().map(|labels| labels.continue_label)
158 }
159
160 fn record_error(&mut self, message: impl Into<String>) {
161 self.errors.push(IrDiagnostic {
162 function_name: self.function_name.clone(),
163 message: message.into(),
164 suggestion: None,
165 code: None,
166 });
167 }
168
169 fn record_error_with_suggestion(
170 &mut self,
171 message: impl Into<String>,
172 suggestion: impl Into<String>,
173 ) {
174 self.errors.push(IrDiagnostic {
175 function_name: self.function_name.clone(),
176 message: message.into(),
177 suggestion: Some(suggestion.into()),
178 code: None,
179 });
180 }
181
182 fn set_call_data_local(&mut self, local_index: usize, method: String) {
183 self.call_data_locals.insert(local_index, method);
184 }
185
186 fn clear_call_data_local(&mut self, local_index: usize) {
187 self.call_data_locals.remove(&local_index);
188 }
189
190 fn call_data_method_for_local(&self, local_index: usize) -> Option<&str> {
191 self.call_data_locals
192 .get(&local_index)
193 .map(|method| method.as_str())
194 }
195
196 fn is_contract_type_name(&self, name: &str) -> bool {
197 self.contract_types.contains(name)
198 }
199
200 fn type_method_selectors(&self, type_name: &str, method_name: &str) -> Option<&Vec<[u8; 4]>> {
201 self.selector_registry
202 .type_method_selectors
203 .get(type_name)
204 .and_then(|methods| methods.get(method_name))
205 }
206
207 fn is_interface_type_name(&self, name: &str) -> bool {
208 self.selector_registry.interface_types.contains(name)
209 }
210
211 fn interface_id_for_type(&self, type_name: &str) -> Option<[u8; 4]> {
212 let methods = self.selector_registry.type_method_selectors.get(type_name)?;
213 let mut selectors: HashSet<[u8; 4]> = HashSet::new();
214 for overloads in methods.values() {
215 for selector in overloads {
216 selectors.insert(*selector);
217 }
218 }
219
220 let mut interface_id = [0u8; 4];
221 for selector in selectors {
222 for (idx, byte) in selector.iter().enumerate() {
223 interface_id[idx] ^= byte;
224 }
225 }
226 Some(interface_id)
227 }
228
229 fn state_type(&self, index: usize) -> Option<&ValueType> {
230 self.state_types.get(index)
231 }
232
233 fn state_metadata(&self, index: usize) -> Option<&StateVariableMetadata> {
234 self.state_variables.get(index)
235 }
236
237 fn can_write_state(&self, state_index: usize) -> bool {
238 let Some(meta) = self.state_metadata(state_index) else {
239 return true;
240 };
241
242 if !meta.is_immutable {
243 return true;
244 }
245
246 self.function_name == "constructor" || self.function_name == "_deploy"
247 }
248
249 fn ensure_state_writable(&mut self, state_index: usize) -> bool {
250 if self.can_write_state(state_index) {
251 return true;
252 }
253
254 let variable_name = self
255 .state_metadata(state_index)
256 .and_then(|meta| meta.name.as_deref())
257 .unwrap_or("<unnamed>")
258 .to_string();
259
260 self.record_error_with_suggestion(
261 format!(
262 "cannot assign to immutable state variable '{}' outside constructor/deploy initialization",
263 variable_name
264 ),
265 "initialize immutable values in the declaration or constructor only",
266 );
267 false
268 }
269
270 fn parameter_type(&self, name: &str) -> Option<&ValueType> {
271 self.param_index_map
272 .get(name)
273 .and_then(|idx| self.param_types.get(*idx))
274 }
275
276 fn local_type(&self, index: usize) -> Option<&ValueType> {
277 self.local_types.get(&index)
278 }
279
280 fn variable_type(&self, name: &str) -> Option<ValueType> {
281 if let Some(reference) = self.storage_alias(name) {
282 return Some(reference.value_type.clone());
283 }
284 if let Some(index) = self.state_index_map.get(name) {
285 if let Some(ty) = self.state_type(*index) {
286 return Some(ty.clone());
287 }
288 }
289 if let Some(ty) = self.parameter_type(name) {
290 return Some(ty.clone());
291 }
292 if let Some(local_index) = self.resolve_local(name) {
293 if let Some(ty) = self.local_type(local_index) {
294 return Some(ty.clone());
295 }
296 }
297 None
298 }
299
300 fn neo_function_name(&self, name: &str, arg_count: usize) -> Option<String> {
301 self.function_overloads
302 .get(&(name.to_string(), arg_count))
303 .cloned()
304 }
305
306 fn get_function_param_names(&self, name: &str, arg_count: usize) -> Option<&[String]> {
308 self.function_param_names
309 .get(&(name.to_string(), arg_count))
310 .map(|v| v.as_slice())
311 }
312
313 fn is_void_function(&self, name: &str) -> bool {
315 self.void_functions.contains(name)
316 }
317
318 fn super_method_name(&self, method_name: &str) -> Option<&str> {
320 self.super_method_map.get(method_name).map(|s| s.as_str())
321 }
322
323 fn event_signature(&self, event_name: &str) -> Option<&[ManifestType]> {
324 self.event_signature_map
325 .get(event_name)
326 .map(|sig| sig.as_slice())
327 }
328
329 fn allocate_local(&mut self, name: String, value_type: Option<ValueType>) -> usize {
330 let index = self.local_count as usize;
331 self.local_count = self.local_count.checked_add(1).unwrap_or(self.local_count);
332 if let Some(scope) = self.scope_stack.last_mut() {
333 scope.push(name.clone());
334 }
335 self.local_index_map.entry(name).or_default().push(index);
336 if let Some(ty) = value_type {
337 self.local_types.insert(index, ty);
338 }
339 index
340 }
341
342 fn resolve_local(&self, name: &str) -> Option<usize> {
343 self.local_index_map
344 .get(name)
345 .and_then(|stack| stack.last().copied())
346 }
347
348 fn ensure_local(&mut self, name: &str) -> usize {
349 if let Some(index) = self.resolve_local(name) {
350 index
351 } else {
352 self.allocate_local(name.to_string(), None)
353 }
354 }
355
356 fn enter_scope(&mut self) {
357 self.scope_stack.push(Vec::new());
358 }
359
360 fn exit_scope(&mut self) {
361 if let Some(names) = self.scope_stack.pop() {
362 for name in names {
363 if let Some(stack) = self.local_index_map.get_mut(&name) {
364 if let Some(index) = stack.pop() {
365 self.local_types.remove(&index);
366 }
367 if stack.is_empty() {
368 self.local_index_map.remove(&name);
369 }
370 }
371 self.storage_aliases.remove(&name);
372 }
373 }
374 }
375
376 fn is_local_in_current_scope(&self, name: &str) -> bool {
377 self.scope_stack
378 .last()
379 .is_some_and(|scope| scope.iter().any(|existing| existing == name))
380 }
381
382 fn set_storage_alias(&mut self, name: String, alias: StorageReference) {
383 self.storage_aliases.insert(name, alias);
384 }
385
386 fn storage_alias(&self, name: &str) -> Option<&StorageReference> {
387 self.storage_aliases.get(name)
388 }
389}