solang/sema/
symtable.rs

1// SPDX-License-Identifier: Apache-2.0
2
3use indexmap::IndexMap;
4use solang_parser::diagnostics::{ErrorType, Level, Note};
5use std::collections::HashMap;
6use std::str;
7use std::sync::Arc;
8
9use super::ast::{Diagnostic, Namespace, Type};
10use super::expression::ExprContext;
11use crate::sema::ast::Expression;
12use solang_parser::pt;
13
14#[derive(Clone, Debug)]
15pub struct Variable {
16    pub id: pt::Identifier,
17    pub ty: Type,
18    pub pos: usize,
19    pub slice: bool,
20    pub assigned: bool,
21    pub read: bool,
22    pub usage_type: VariableUsage,
23    pub initializer: VariableInitializer,
24    pub storage_location: Option<pt::StorageLocation>,
25}
26
27#[derive(Clone, Debug)]
28pub enum VariableInitializer {
29    Solidity(Option<Arc<Expression>>),
30    Yul(bool),
31}
32
33impl VariableInitializer {
34    pub fn has_initializer(&self) -> bool {
35        match self {
36            VariableInitializer::Solidity(expr) => expr.is_some(),
37            VariableInitializer::Yul(initialized) => *initialized,
38        }
39    }
40}
41
42impl Variable {
43    pub fn is_reference(&self, ns: &Namespace) -> bool {
44        // If the variable has the memory or storage keyword, it can be a reference to another variable.
45        // In this case, an assigment may change the value of the variable it is referencing.
46        if matches!(
47            self.storage_location,
48            Some(pt::StorageLocation::Memory(_)) | Some(pt::StorageLocation::Storage(_)) | None
49        ) && self.ty.is_reference_type(ns)
50        {
51            if let VariableInitializer::Solidity(Some(expr)) = &self.initializer {
52                // If the initializer is an array allocation, a constructor or a struct literal,
53                // the variable is not a reference to another.
54                return !matches!(
55                    **expr,
56                    Expression::AllocDynamicBytes { .. }
57                        | Expression::ArrayLiteral { .. }
58                        | Expression::Constructor { .. }
59                        | Expression::StructLiteral { .. }
60                );
61            }
62        }
63
64        false
65    }
66}
67
68#[derive(Clone, Debug)]
69pub enum VariableUsage {
70    Parameter,
71    ReturnVariable,
72    AnonymousReturnVariable,
73    LocalVariable,
74    DestructureVariable,
75    TryCatchReturns,
76    TryCatchErrorString,
77    TryCatchErrorBytes,
78    YulLocalVariable,
79}
80
81#[derive(Debug, Clone)]
82pub struct VarScope {
83    pub loc: Option<pt::Loc>,
84    pub names: HashMap<String, usize>,
85}
86
87#[derive(Default, Debug, Clone)]
88pub struct Symtable {
89    pub vars: IndexMap<usize, Variable>,
90    pub arguments: Vec<Option<usize>>,
91    pub returns: Vec<usize>,
92    pub scopes: Vec<VarScope>,
93}
94
95impl Symtable {
96    pub fn add(
97        &mut self,
98        id: &pt::Identifier,
99        ty: Type,
100        ns: &mut Namespace,
101        initializer: VariableInitializer,
102        usage_type: VariableUsage,
103        storage_location: Option<pt::StorageLocation>,
104        context: &mut ExprContext,
105    ) -> Option<usize> {
106        let pos = ns.next_id;
107        ns.next_id += 1;
108
109        self.vars.insert(
110            pos,
111            Variable {
112                id: id.clone(),
113                ty,
114                pos,
115                slice: false,
116                initializer,
117                assigned: false,
118                usage_type,
119                read: false,
120                storage_location,
121            },
122        );
123
124        // the variable has no name, like unnamed return or parameters values
125        if !id.name.is_empty() {
126            if let Some(prev) = self.find(context, &id.name) {
127                ns.diagnostics.push(Diagnostic::error_with_note(
128                    id.loc,
129                    format!("{} is already declared", id.name),
130                    prev.id.loc,
131                    "location of previous declaration".to_string(),
132                ));
133                return None;
134            }
135
136            context
137                .active_scopes
138                .last_mut()
139                .unwrap()
140                .names
141                .insert(id.name.to_string(), pos);
142        }
143
144        Some(pos)
145    }
146
147    pub fn exclusive_add(
148        &mut self,
149        id: &pt::Identifier,
150        ty: Type,
151        ns: &mut Namespace,
152        initializer: VariableInitializer,
153        usage_type: VariableUsage,
154        storage_location: Option<pt::StorageLocation>,
155        context: &mut ExprContext,
156    ) -> Option<usize> {
157        if let Some(var) = self.find(context, &id.name) {
158            ns.diagnostics.push(Diagnostic {
159                level: Level::Error,
160                ty: ErrorType::DeclarationError,
161                loc: id.loc,
162                message: format!("variable name '{}' already used in this scope", id.name),
163                notes: vec![Note {
164                    loc: var.id.loc,
165                    message: "found previous declaration here".to_string(),
166                }],
167            });
168            return None;
169        }
170
171        self.add(
172            id,
173            ty,
174            ns,
175            initializer,
176            usage_type,
177            storage_location,
178            context,
179        )
180    }
181
182    pub fn find(&self, context: &mut ExprContext, name: &str) -> Option<&Variable> {
183        for scope in context.active_scopes.iter().rev() {
184            if let Some(n) = scope.names.get(name) {
185                return self.vars.get(n);
186            }
187        }
188
189        None
190    }
191
192    pub fn get_name(&self, pos: usize) -> &str {
193        &self.vars[&pos].id.name
194    }
195}
196
197pub struct LoopScope {
198    pub no_breaks: usize,
199    pub no_continues: usize,
200}
201
202pub struct LoopScopes(Vec<LoopScope>);
203
204impl Default for LoopScopes {
205    fn default() -> Self {
206        LoopScopes::new()
207    }
208}
209
210impl LoopScopes {
211    pub fn new() -> Self {
212        LoopScopes(Vec::new())
213    }
214
215    pub fn enter_scope(&mut self) {
216        self.0.push(LoopScope {
217            no_breaks: 0,
218            no_continues: 0,
219        })
220    }
221
222    pub fn leave_scope(&mut self) -> LoopScope {
223        self.0.pop().unwrap()
224    }
225
226    pub fn do_break(&mut self) -> bool {
227        match self.0.last_mut() {
228            Some(scope) => {
229                scope.no_breaks += 1;
230                true
231            }
232            None => false,
233        }
234    }
235
236    pub fn in_a_loop(&self) -> bool {
237        !self.0.is_empty()
238    }
239
240    pub fn do_continue(&mut self) -> bool {
241        match self.0.last_mut() {
242            Some(scope) => {
243                scope.no_continues += 1;
244                true
245            }
246            None => false,
247        }
248    }
249}