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::err::{Error, ErrorKind};
6use crate::{Analyzer, BlockScopeMode};
7use std::rc::Rc;
8use swamp_script_node::Node;
9use swamp_script_semantic::{
10    Expression, ExpressionKind, MutOrImmutableExpression, Variable, VariableRef,
11};
12use swamp_script_types::prelude::*;
13use tracing::{error, info};
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 variables = &mut self
87            .scope
88            .block_scope_stack
89            .last_mut()
90            .expect("block scope should have at least one scope")
91            .variables;
92        let variable_index = variables.len();
93
94        let resolved_variable = Variable {
95            name,
96            resolved_type: variable_type_ref.clone(),
97            mutable_node,
98            scope_index,
99            variable_index,
100        };
101
102        let variable_ref = Rc::new(resolved_variable);
103
104        {
105            variables
106                .insert(variable_name_str, variable_ref.clone())
107                .expect("should have checked earlier for variable");
108        }
109
110        Ok((variable_ref, false))
111    }
112    pub(crate) fn create_local_variable(
113        &mut self,
114        variable: &swamp_script_ast::Node,
115        is_mutable: Option<&swamp_script_ast::Node>,
116        variable_type_ref: &Type,
117    ) -> Result<VariableRef, Error> {
118        if variable_type_ref == &Type::Unit {
119            let debug_text = self.get_text(variable);
120            error!(
121                ?debug_text,
122                "panic, tries to create a local variable as a unit"
123            );
124        }
125        assert_ne!(*variable_type_ref, Type::Unit);
126        self.create_local_variable_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_script_ast::Variable,
136        variable_type_ref: &Type,
137    ) -> Result<VariableRef, Error> {
138        self.create_local_variable(
139            &variable.name,
140            Option::from(&variable.is_mutable),
141            variable_type_ref,
142        )
143    }
144
145    pub(crate) fn create_local_variable_resolved(
146        &mut self,
147        variable: &Node,
148        is_mutable: Option<&Node>,
149        variable_type_ref: &Type,
150    ) -> Result<VariableRef, Error> {
151        if let Some(_existing_variable) = self.try_find_local_variable(variable) {
152            return Err(
153                self.create_err_resolved(ErrorKind::OverwriteVariableNotAllowedHere, variable)
154            );
155        }
156        let variable_str = self.get_text_resolved(variable).to_string();
157
158        let scope_index = self.scope.block_scope_stack.len() - 1;
159
160        let variables = &mut self
161            .scope
162            .block_scope_stack
163            .last_mut()
164            .expect("block scope should have at least one scope")
165            .variables;
166
167        let resolved_variable = Variable {
168            name: variable.clone(),
169            resolved_type: variable_type_ref.clone(),
170            mutable_node: is_mutable.cloned(),
171            scope_index,
172            variable_index: variables.len(),
173        };
174
175        let variable_ref = Rc::new(resolved_variable);
176
177        let should_insert_in_scope = !variable_str.starts_with('_');
178        if !should_insert_in_scope && is_mutable.is_some() {
179            return Err(self.create_err_resolved(ErrorKind::UnusedVariablesCanNotBeMut, variable));
180        }
181        if should_insert_in_scope {
182            variables
183                .insert(variable_str, variable_ref.clone())
184                .expect("should have checked earlier for variable");
185        }
186
187        Ok(variable_ref)
188    }
189
190    #[allow(clippy::unnecessary_wraps)]
191    pub(crate) fn create_local_variable_generated(
192        &mut self,
193        variable_str: &str,
194        is_mutable: bool,
195        variable_type_ref: &Type,
196    ) -> Result<VariableRef, Error> {
197        let scope_index = self.scope.block_scope_stack.len() - 1;
198
199        let variables = &mut self
200            .scope
201            .block_scope_stack
202            .last_mut()
203            .expect("block scope should have at least one scope")
204            .variables;
205
206        let resolved_variable = Variable {
207            name: Node::default(),
208            resolved_type: variable_type_ref.clone(),
209            mutable_node: if is_mutable {
210                Some(Node::default())
211            } else {
212                None
213            },
214            scope_index,
215            variable_index: variables.len(),
216        };
217
218        let variable_ref = Rc::new(resolved_variable);
219
220        variables
221            .insert(variable_str.to_string(), variable_ref.clone())
222            .expect("should have checked earlier for variable");
223
224        Ok(variable_ref)
225    }
226
227    pub(crate) fn create_variable_binding_for_with(
228        &mut self,
229        ast_variable: &swamp_script_ast::Variable,
230        converted_expression: MutOrImmutableExpression,
231    ) -> Result<Expression, Error> {
232        let expression_type = converted_expression.ty().clone();
233        let (variable_ref, _is_reassignment) =
234            self.set_or_overwrite_variable_with_type(ast_variable, &expression_type)?;
235        let expr_kind =
236            ExpressionKind::VariableDefinition(variable_ref, Box::from(converted_expression));
237
238        let expr = self.create_expr(expr_kind, expression_type, &ast_variable.name);
239
240        Ok(expr)
241    }
242}