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