swamp_analyzer/
variable.rs

1/*
2 * Copyright (c) Peter Bjorklund. All rights reserved. https://github.com/swamp/swamp
3 * Licensed under the MIT License. See LICENSE in the project root for license information.
4 */
5use crate::Analyzer;
6use source_map_node::Node;
7use std::rc::Rc;
8use swamp_semantic::ScopeInfo;
9use swamp_semantic::err::ErrorKind;
10use swamp_semantic::{
11    ArgumentExpression, BlockScopeMode, Expression, ExpressionKind, Variable, VariableRef,
12    VariableType,
13};
14use swamp_symbol::{ScopedSymbolId, Symbol, SymbolKind};
15use swamp_types::prelude::*;
16
17pub const MAX_VIRTUAL_REGISTER: usize = 48;
18
19pub struct VariableSlot {
20    pub scope_index: usize,
21    pub unique_id_in_function: usize,
22    pub virtual_register: u8,
23}
24
25/// Common helper function for allocating the next available register from `ScopeInfo`
26/// This function uses high watermark approach - simply increment counter, restore on scope pop
27pub(crate) const fn allocate_next_register(scope: &mut ScopeInfo) -> Option<u8> {
28    if scope.total_scopes.current_register >= MAX_VIRTUAL_REGISTER {
29        None
30    } else {
31        scope.total_scopes.current_register += 1;
32        //info!(?scope.total_scopes.current_register, "allocated register");
33        Some(scope.total_scopes.current_register as u8)
34    }
35}
36
37impl Analyzer<'_> {
38    fn try_find_local_variable(&self, node: &Node) -> Option<&VariableRef> {
39        let current_scope = self
40            .scope
41            .active_scope
42            .block_scope_stack
43            .iter()
44            .last()
45            .expect("no scope stack available");
46
47        let variable_text = self.get_text_resolved(node).to_string();
48
49        current_scope.lookup.get(&variable_text)
50    }
51
52    #[allow(unused)]
53    pub(crate) fn find_variable(&mut self, variable: &swamp_ast::Variable) -> VariableRef {
54        if let Some(x) = self.try_find_variable(&variable.name) {
55            x
56        } else {
57            self.add_err(ErrorKind::UnknownVariable, &variable.name);
58            VariableRef::from(Variable::create_err(self.types().unit()))
59        }
60    }
61
62    pub(crate) fn try_find_variable(&self, node: &swamp_ast::Node) -> Option<VariableRef> {
63        let variable_text = self.get_text(node);
64
65        for scope in self.scope.active_scope.block_scope_stack.iter().rev() {
66            if let Some(value) = scope.lookup.get(&variable_text.to_string()) {
67                return Some(value.clone());
68            }
69            if scope.mode == BlockScopeMode::Closed {
70                break;
71            }
72        }
73
74        None
75    }
76
77    pub(crate) fn create_parameter_resolved(
78        &mut self,
79        variable: &Node,
80        is_mutable: Option<&Node>,
81        variable_type_ref: &TypeRef,
82    ) {
83        let (variable_ref, _name_str) = self.create_variable_like_resolved(
84            variable,
85            is_mutable,
86            variable_type_ref,
87            VariableType::Parameter,
88        );
89    }
90
91    pub(crate) fn create_local_variable(
92        &mut self,
93        variable: &swamp_ast::Node,
94        is_mutable: Option<&swamp_ast::Node>,
95        variable_type_ref: &TypeRef,
96        concrete_check: bool,
97    ) -> VariableRef {
98        let debug_text = self.get_text(variable);
99        if !debug_text.starts_with('_')
100            && concrete_check
101            && !variable_type_ref.can_be_stored_in_variable()
102        {
103            self.add_err(
104                ErrorKind::VariableTypeMustBeAtLeastTransient(variable_type_ref.clone()),
105                variable,
106            );
107            return Rc::new(Variable::create_err(self.types().unit()));
108        }
109        self.create_local_variable_resolved(
110            &self.to_node(variable),
111            Option::from(&self.to_node_option(is_mutable)),
112            variable_type_ref,
113        )
114    }
115
116    pub(crate) fn create_local_variable_parameter_like(
117        &mut self,
118        variable: &swamp_ast::Node,
119        is_mutable: Option<&swamp_ast::Node>,
120        variable_type_ref: &TypeRef,
121        concrete_check: bool,
122    ) -> VariableRef {
123        let debug_text = self.get_text(variable);
124        if !debug_text.starts_with('_')
125            && concrete_check
126            && !variable_type_ref.can_be_stored_in_variable()
127        {
128            self.add_err(
129                ErrorKind::VariableTypeMustBeAtLeastTransient(variable_type_ref.clone()),
130                variable,
131            );
132            return Rc::new(Variable::create_err(self.types().unit()));
133        }
134        self.create_local_variable_parameter_like_resolved(
135            &self.to_node(variable),
136            Option::from(&self.to_node_option(is_mutable)),
137            variable_type_ref,
138        )
139    }
140
141    pub(crate) fn create_variable(
142        &mut self,
143        variable: &swamp_ast::Variable,
144        variable_type_ref: &TypeRef,
145    ) -> VariableRef {
146        self.create_local_variable(
147            &variable.name,
148            Option::from(&variable.is_mutable),
149            variable_type_ref,
150            true,
151        )
152    }
153
154    pub(crate) fn create_variable_param_like(
155        &mut self,
156        variable: &swamp_ast::Variable,
157        variable_type_ref: &TypeRef,
158    ) -> VariableRef {
159        self.create_local_variable_parameter_like(
160            &variable.name,
161            Option::from(&variable.is_mutable),
162            variable_type_ref,
163            true,
164        )
165    }
166
167    pub(crate) fn create_local_variable_resolved(
168        &mut self,
169        variable: &Node,
170        is_mutable: Option<&Node>,
171        variable_type_ref: &TypeRef,
172    ) -> VariableRef {
173        let (variable_ref, variable_str) = self.create_variable_like_resolved(
174            variable,
175            is_mutable,
176            variable_type_ref,
177            VariableType::Local,
178        );
179
180        variable_ref
181    }
182
183    pub(crate) fn create_local_variable_parameter_like_resolved(
184        &mut self,
185        variable: &Node,
186        is_mutable: Option<&Node>,
187        variable_type_ref: &TypeRef,
188    ) -> VariableRef {
189        let (variable_ref, variable_str) = self.create_variable_like_resolved(
190            variable,
191            is_mutable,
192            variable_type_ref,
193            VariableType::Parameter,
194        );
195
196        variable_ref
197    }
198
199    pub fn reserve_slot(&mut self, node: &Node) -> Option<VariableSlot> {
200        let Some(virtual_register) = allocate_next_register(&mut self.scope) else {
201            self.add_err_resolved(ErrorKind::OutOfVirtualRegisters, node);
202            return None;
203        };
204
205        let scope_index = self.scope.active_scope.block_scope_stack.len() - 1;
206        let unique_id_in_function = { self.scope.active_scope.emit_variable_index() };
207
208        Some(VariableSlot {
209            scope_index,
210            unique_id_in_function,
211            virtual_register,
212        })
213    }
214
215    pub fn create_local_variable_with_reserved_slot(
216        &mut self,
217        slot: VariableSlot,
218        ast_var: swamp_ast::Variable,
219        variable_type_ref: &TypeRef,
220    ) -> (VariableRef, String) {
221        let variable = self.to_node(&ast_var.name);
222        let variable_str = self.get_text(&ast_var.name).to_string();
223        let is_mutable = ast_var.is_mutable.map(|ast_node| self.to_node(&ast_node));
224
225        let variable_type = VariableType::Local;
226        let scope_index = slot.scope_index;
227        let should_insert_in_scope = true;
228
229        let symbol_id = self.shared.state.symbol_id_allocator.alloc_scoped();
230        self.shared.state.symbols.insert_scoped(
231            symbol_id,
232            Symbol {
233                id: symbol_id.into(),
234                kind: SymbolKind::Variable,
235                source_map_node: variable.clone(),
236                name: variable.clone(),
237            },
238        );
239
240        let variables_len = self.scope.total_scopes.all_variables.len();
241
242        let resolved_variable = Variable {
243            symbol_id,
244            name: variable,
245            assigned_name: variable_str.clone(),
246            variable_type,
247            resolved_type: variable_type_ref.clone(),
248            mutable_node: is_mutable,
249            scope_index,
250            variable_index: variables_len,
251            unique_id_within_function: slot.unique_id_in_function,
252            virtual_register: slot.virtual_register,
253            is_unused: !should_insert_in_scope,
254        };
255
256        let variable_ref = Rc::new(resolved_variable);
257
258        if should_insert_in_scope {
259            self.scope
260                .active_scope
261                .block_scope_stack
262                .get_mut(scope_index)
263                .unwrap()
264                .lookup
265                .insert(variable_str.clone(), variable_ref.clone())
266                .expect("should work");
267        }
268
269        self.scope
270            .active_scope
271            .block_scope_stack
272            .get_mut(scope_index)
273            .unwrap()
274            .variables
275            .insert(slot.unique_id_in_function, variable_ref.clone())
276            .expect("should have checked earlier for variable");
277
278        self.scope
279            .total_scopes
280            .all_variables
281            .push(variable_ref.clone());
282
283        (variable_ref, variable_str)
284    }
285
286    pub(crate) fn create_variable_like_resolved(
287        &mut self,
288        variable: &Node,
289        is_mutable: Option<&Node>,
290        variable_type_ref: &TypeRef,
291        variable_type: VariableType,
292    ) -> (VariableRef, String) {
293        if let Some(_existing_variable) = self.try_find_local_variable(variable) {
294            self.add_err_resolved(ErrorKind::OverwriteVariableNotAllowedHere, variable);
295
296            let error_var_ref = VariableRef::new(Variable {
297                symbol_id: ScopedSymbolId::new_illegal(),
298                name: Default::default(),
299                assigned_name: "err".to_string(),
300                resolved_type: self.types().unit(),
301                mutable_node: None,
302                variable_type,
303                scope_index: 0,
304                variable_index: 0,
305                unique_id_within_function: 0,
306                virtual_register: 0,
307                is_unused: false,
308            });
309
310            return (error_var_ref, "err".to_string());
311        }
312        let variable_str = self.get_text_resolved(variable).to_string();
313
314        let scope_index = self.scope.active_scope.block_scope_stack.len() - 1;
315
316        let index = { self.scope.active_scope.emit_variable_index() };
317
318        let should_insert_in_scope = !variable_str.starts_with('_');
319
320        let variables_len = &mut self
321            .scope
322            .active_scope
323            .block_scope_stack
324            .last_mut()
325            .expect("block scope should have at least one scope")
326            .variables
327            .len();
328
329        // Check for unused mutable variables before incrementing register counter
330        if !should_insert_in_scope && is_mutable.is_some() {
331            self.add_err_resolved(ErrorKind::UnusedVariablesCanNotBeMut, variable);
332
333            let error_var_ref = VariableRef::new(Variable {
334                symbol_id: ScopedSymbolId::new_illegal(),
335                name: Default::default(),
336                assigned_name: "err".to_string(),
337                resolved_type: self.types().unit(),
338                mutable_node: None,
339                variable_type,
340                scope_index: 0,
341                variable_index: 0,
342                unique_id_within_function: 0,
343                virtual_register: 0,
344                is_unused: false,
345            });
346
347            return (error_var_ref, "err".to_string());
348        }
349
350        // Only increment register counter when we're actually creating a valid variable
351        let maybe_virtual_register = allocate_next_register(&mut self.scope);
352        if let Some(virtual_register) = maybe_virtual_register {
353            let symbol_id = self.shared.state.symbol_id_allocator.alloc_scoped();
354            self.shared.state.symbols.insert_scoped(
355                symbol_id,
356                Symbol {
357                    id: symbol_id.into(),
358                    kind: SymbolKind::Variable,
359                    source_map_node: variable.clone(),
360                    name: variable.clone(),
361                },
362            );
363            let resolved_variable = Variable {
364                symbol_id,
365                name: variable.clone(),
366                assigned_name: variable_str.clone(),
367                variable_type,
368                resolved_type: variable_type_ref.clone(),
369                mutable_node: is_mutable.cloned(),
370                scope_index,
371                variable_index: *variables_len,
372                unique_id_within_function: index,
373                virtual_register,
374                is_unused: !should_insert_in_scope,
375            };
376
377            let variable_ref = Rc::new(resolved_variable);
378
379            if should_insert_in_scope {
380                let lookups = &mut self
381                    .scope
382                    .active_scope
383                    .block_scope_stack
384                    .last_mut()
385                    .expect("block scope should have at least one scope")
386                    .lookup;
387                lookups
388                    .insert(variable_str.clone(), variable_ref.clone())
389                    .expect("should have checked earlier for variable");
390            }
391
392            let variables = &mut self
393                .scope
394                .active_scope
395                .block_scope_stack
396                .last_mut()
397                .expect("block scope should have at least one scope")
398                .variables;
399            variables
400                .insert(variable_ref.unique_id_within_function, variable_ref.clone())
401                .expect("should have checked earlier for variable");
402
403            self.scope
404                .total_scopes
405                .all_variables
406                .push(variable_ref.clone());
407
408            (variable_ref, variable_str)
409        } else {
410            eprintln!("variable: {variable_str}");
411            for var in &self.scope.total_scopes.all_variables {
412                eprintln!("var id:{} '{}'", var.virtual_register, var.assigned_name);
413            }
414            self.add_err_resolved(ErrorKind::OutOfVirtualRegisters, variable);
415            let resolved_variable = Variable {
416                symbol_id: ScopedSymbolId::new_illegal(),
417                name: variable.clone(),
418                assigned_name: variable_str,
419                variable_type,
420                resolved_type: variable_type_ref.clone(),
421                mutable_node: is_mutable.cloned(),
422                scope_index,
423                variable_index: *variables_len,
424                unique_id_within_function: index,
425                virtual_register: 0,
426                is_unused: true,
427            };
428
429            let variable_ref = Rc::new(resolved_variable);
430            (variable_ref, "error".to_string())
431        }
432    }
433
434    #[allow(clippy::too_many_lines)]
435    pub(crate) fn create_variable_binding_for_with(
436        &mut self,
437        ast_variable: &swamp_ast::Variable,
438        converted_expression: ArgumentExpression,
439    ) -> Expression {
440        let expression_type = converted_expression.ty();
441
442        match converted_expression {
443            ArgumentExpression::Expression(expr)
444            | ArgumentExpression::MaterializedExpression(expr) => {
445                // For immutable bindings or expressions that can't be aliased,
446                // create a regular variable definition
447                let variable_ref = self.create_local_variable(
448                    &ast_variable.name,
449                    ast_variable.is_mutable.as_ref(),
450                    &expression_type,
451                    false,
452                );
453
454                let var_def_kind = ExpressionKind::VariableDefinition(variable_ref, Box::new(expr));
455                let unit_type = self.types().unit();
456                self.create_expr(var_def_kind, unit_type, &ast_variable.name)
457            }
458            ArgumentExpression::BorrowMutableReference(loc) => {
459                // For mutable reference bindings, check if it's a scalar or aggregate type
460                if ast_variable.is_mutable.is_none() {
461                    return self.create_err(ErrorKind::VariableIsNotMutable, &ast_variable.name);
462                }
463
464                if matches!(&*expression_type.kind, TypeKind::StringView { .. }) {
465                    return self.create_err(ErrorKind::CanNotBeBorrowed, &ast_variable.name);
466                }
467
468                // Check if the type is a scalar/primitive type
469                let is_scalar = match &*expression_type.kind {
470                    TypeKind::Int | TypeKind::Float | TypeKind::Bool => true,
471                    _ => false,
472                };
473
474                if is_scalar {
475                    // For scalars, treat mutable references as copies for now
476                    // TODO: Implement copy-back mechanism later
477                    let original_expr = ExpressionKind::VariableAccess(loc.starting_variable);
478                    let original_expr_wrapped = self.create_expr(
479                        original_expr,
480                        expression_type.clone(),
481                        &ast_variable.name,
482                    );
483
484                    // Create a regular variable definition (copy)
485                    let variable_ref = self.create_local_variable(
486                        &ast_variable.name,
487                        ast_variable.is_mutable.as_ref(),
488                        &expression_type,
489                        false,
490                    );
491
492                    let var_def_kind = ExpressionKind::VariableDefinition(
493                        variable_ref,
494                        Box::new(original_expr_wrapped),
495                    );
496                    let unit_type = self.types().unit();
497                    self.create_expr(var_def_kind, unit_type, &ast_variable.name)
498                } else {
499                    // For aggregate types, create a proper alias using VariableDefinitionLValue
500                    let variable_ref = self.create_local_variable(
501                        &ast_variable.name,
502                        ast_variable.is_mutable.as_ref(),
503                        &expression_type,
504                        false,
505                    );
506
507                    // Use the VariableDefinitionLValue expression to bind the variable to the lvalue
508                    let expr_kind = ExpressionKind::VariableDefinitionLValue(variable_ref, loc);
509                    let unit_type = self.types().unit();
510                    self.create_expr(expr_kind, unit_type, &ast_variable.name)
511                }
512            }
513        }
514    }
515
516    pub const fn types(&mut self) -> &mut TypeCache {
517        &mut self.shared.state.types
518    }
519}