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