swamp_script_analyzer/
variable.rs

1/*
2 * Copyright (c) Peter Bjorklund. All rights reserved. https://github.com/swamp/script
3 * Licensed under the MIT License. See LICENSE in the project root for license information.
4 */
5use crate::Analyzer;
6use crate::err::{Error, ErrorKind};
7use std::rc::Rc;
8use swamp_script_node::Node;
9use swamp_script_semantic::{
10    BlockScopeMode, Expression, ExpressionKind, MutOrImmutableExpression, Variable, VariableRef,
11};
12use swamp_script_types::prelude::*;
13use tracing::error;
14impl Analyzer<'_> {
15    fn try_find_local_variable(&self, node: &Node) -> Option<&VariableRef> {
16        let current_scope = self
17            .scope
18            .block_scope_stack
19            .iter()
20            .last()
21            .expect("no scope stack available");
22
23        let variable_text = self.get_text_resolved(node).to_string();
24
25        current_scope.variables.get(&variable_text)
26    }
27
28    #[allow(unused)]
29    pub(crate) fn find_variable(
30        &self,
31        variable: &swamp_script_ast::Variable,
32    ) -> Result<VariableRef, Error> {
33        self.try_find_variable(&variable.name).map_or_else(
34            || Err(self.create_err(ErrorKind::UnknownVariable, &variable.name)),
35            Ok,
36        )
37    }
38
39    pub(crate) fn try_find_variable(&self, node: &swamp_script_ast::Node) -> Option<VariableRef> {
40        let variable_text = self.get_text(node);
41
42        for scope in self.scope.block_scope_stack.iter().rev() {
43            if let Some(value) = scope.variables.get(&variable_text.to_string()) {
44                return Some(value.clone());
45            }
46            if scope.mode == BlockScopeMode::Closed {
47                break;
48            }
49        }
50
51        None
52    }
53
54    pub(crate) fn set_or_overwrite_variable_with_type(
55        &mut self,
56        variable: &swamp_script_ast::Variable,
57        variable_type_ref: &Type,
58    ) -> Result<(VariableRef, bool), Error> {
59        if let Some(existing_variable) = self.try_find_variable(&variable.name) {
60            // Check type compatibility
61            if !&existing_variable
62                .resolved_type
63                .assignable_type(variable_type_ref)
64            {
65                return Err(
66                    self.create_err(ErrorKind::OverwriteVariableWithAnotherType, &variable.name)
67                );
68            }
69
70            // For reassignment, check if the EXISTING variable is mutable
71            if !existing_variable.is_mutable() {
72                return Err(
73                    self.create_err(ErrorKind::CanOnlyOverwriteVariableWithMut, &variable.name)
74                );
75            }
76
77            return Ok((existing_variable, true));
78        }
79
80        // For first assignment, create new variable with the mutability from the assignment
81        let scope_index = self.scope.block_scope_stack.len() - 1;
82        let name = self.to_node(&variable.name);
83        let mutable_node = self.to_node_option(Option::from(&variable.is_mutable));
84        let variable_name_str = self.get_text_resolved(&name).to_string();
85
86        let index_within_function = { self.scope.gen_variable_index() };
87        let variables = &mut self
88            .scope
89            .block_scope_stack
90            .last_mut()
91            .expect("block scope should have at least one scope")
92            .variables;
93        let variable_index = variables.len();
94        let should_insert_in_scope = !variable_name_str.starts_with('_');
95
96        let resolved_variable = Variable {
97            name,
98            assigned_name: variable_name_str.clone(),
99            resolved_type: variable_type_ref.clone(),
100            mutable_node,
101            scope_index,
102            variable_index,
103            unique_id_within_function: index_within_function,
104            is_unused: !should_insert_in_scope,
105        };
106
107        let variable_ref = Rc::new(resolved_variable);
108
109        {
110            variables
111                .insert(variable_name_str, variable_ref.clone())
112                .expect("should have checked earlier for variable");
113        }
114
115        Ok((variable_ref, false))
116    }
117    pub(crate) fn create_local_variable(
118        &mut self,
119        variable: &swamp_script_ast::Node,
120        is_mutable: Option<&swamp_script_ast::Node>,
121        variable_type_ref: &Type,
122    ) -> Result<VariableRef, Error> {
123        if variable_type_ref == &Type::Unit {
124            let debug_text = self.get_text(variable);
125            error!(
126                ?debug_text,
127                "panic, tries to create a local variable as a unit"
128            );
129        }
130        assert_ne!(*variable_type_ref, Type::Unit);
131        self.create_local_variable_resolved(
132            &self.to_node(variable),
133            Option::from(&self.to_node_option(is_mutable)),
134            variable_type_ref,
135        )
136    }
137
138    pub(crate) fn create_variable(
139        &mut self,
140        variable: &swamp_script_ast::Variable,
141        variable_type_ref: &Type,
142    ) -> Result<VariableRef, Error> {
143        self.create_local_variable(
144            &variable.name,
145            Option::from(&variable.is_mutable),
146            variable_type_ref,
147        )
148    }
149
150    pub(crate) fn create_local_variable_resolved(
151        &mut self,
152        variable: &Node,
153        is_mutable: Option<&Node>,
154        variable_type_ref: &Type,
155    ) -> Result<VariableRef, Error> {
156        if let Some(_existing_variable) = self.try_find_local_variable(variable) {
157            return Err(
158                self.create_err_resolved(ErrorKind::OverwriteVariableNotAllowedHere, variable)
159            );
160        }
161        let variable_str = self.get_text_resolved(variable).to_string();
162
163        let scope_index = self.scope.block_scope_stack.len() - 1;
164
165        let index = { self.scope.gen_variable_index() };
166
167        let should_insert_in_scope = !variable_str.starts_with('_');
168        let variables = &mut self
169            .scope
170            .block_scope_stack
171            .last_mut()
172            .expect("block scope should have at least one scope")
173            .variables;
174
175        let resolved_variable = Variable {
176            name: variable.clone(),
177            assigned_name: variable_str.clone(),
178
179            resolved_type: variable_type_ref.clone(),
180            mutable_node: is_mutable.cloned(),
181            scope_index,
182            variable_index: variables.len(),
183            unique_id_within_function: index,
184            is_unused: !should_insert_in_scope,
185        };
186
187        let variable_ref = Rc::new(resolved_variable);
188
189        if !should_insert_in_scope && is_mutable.is_some() {
190            return Err(self.create_err_resolved(ErrorKind::UnusedVariablesCanNotBeMut, variable));
191        }
192        if should_insert_in_scope {
193            variables
194                .insert(variable_str, variable_ref.clone())
195                .expect("should have checked earlier for variable");
196            self.function_variables.push(variable_ref.clone());
197        }
198
199        Ok(variable_ref)
200    }
201
202    #[allow(clippy::unnecessary_wraps)]
203    pub(crate) fn create_local_variable_generated(
204        &mut self,
205        variable_str: &str,
206        is_mutable: bool,
207        variable_type_ref: &Type,
208    ) -> Result<VariableRef, Error> {
209        let scope_index = self.scope.block_scope_stack.len() - 1;
210
211        let index_within_function = self.scope.gen_variable_index();
212
213        let variables = &mut self
214            .scope
215            .block_scope_stack
216            .last_mut()
217            .expect("block scope should have at least one scope")
218            .variables;
219
220        let should_insert_in_scope = !variable_str.starts_with('_');
221
222        let resolved_variable = Variable {
223            name: Node::default(),
224            assigned_name: variable_str.to_string(),
225            resolved_type: variable_type_ref.clone(),
226            mutable_node: if is_mutable {
227                Some(Node::default())
228            } else {
229                None
230            },
231            scope_index,
232            variable_index: variables.len(),
233            unique_id_within_function: index_within_function,
234            is_unused: !should_insert_in_scope,
235        };
236
237        let variable_ref = Rc::new(resolved_variable);
238
239        if should_insert_in_scope {
240            variables
241                .insert(variable_str.to_string(), variable_ref.clone())
242                .expect("should have checked earlier for variable");
243        }
244
245        Ok(variable_ref)
246    }
247
248    pub(crate) fn create_variable_binding_for_with(
249        &mut self,
250        ast_variable: &swamp_script_ast::Variable,
251        converted_expression: MutOrImmutableExpression,
252    ) -> Result<Expression, Error> {
253        let expression_type = converted_expression.ty().clone();
254        let variable_ref = self.create_local_variable(
255            &ast_variable.name,
256            ast_variable.is_mutable.as_ref(),
257            &expression_type,
258        )?;
259        let expr_kind =
260            ExpressionKind::VariableDefinition(variable_ref, Box::from(converted_expression));
261
262        let expr = self.create_expr(expr_kind, expression_type, &ast_variable.name);
263
264        Ok(expr)
265    }
266}