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