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