leo_passes/symbol_table_creation/
mod.rs

1// Copyright (C) 2019-2025 Provable Inc.
2// This file is part of the Leo library.
3
4// The Leo library is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8
9// The Leo library is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13
14// You should have received a copy of the GNU General Public License
15// along with the Leo library. If not, see <https://www.gnu.org/licenses/>.
16
17use crate::{CompilerState, Pass, SymbolTable, VariableSymbol, VariableType};
18
19use leo_ast::{
20    AstVisitor,
21    Composite,
22    ConstDeclaration,
23    Function,
24    FunctionStub,
25    Location,
26    Mapping,
27    MappingType,
28    Module,
29    OptionalType,
30    Program,
31    ProgramScope,
32    ProgramVisitor,
33    StorageVariable,
34    Stub,
35    Type,
36    Variant,
37};
38use leo_errors::Result;
39use leo_span::{Span, Symbol};
40
41use indexmap::IndexMap;
42
43/// A pass to fill the SymbolTable.
44///
45/// Only creates the global data - local data will be constructed during type checking.
46pub struct SymbolTableCreation;
47
48impl Pass for SymbolTableCreation {
49    type Input = ();
50    type Output = ();
51
52    const NAME: &'static str = "SymbolTableCreation";
53
54    fn do_pass(_input: Self::Input, state: &mut CompilerState) -> Result<Self::Output> {
55        let ast = std::mem::take(&mut state.ast);
56        let mut visitor = SymbolTableCreationVisitor {
57            state,
58            structs: IndexMap::new(),
59            program_name: Symbol::intern(""),
60            module: vec![],
61            is_stub: false,
62        };
63        visitor.visit_program(ast.as_repr());
64        visitor.state.handler.last_err()?;
65        visitor.state.ast = ast;
66        Ok(())
67    }
68}
69
70struct SymbolTableCreationVisitor<'a> {
71    /// The state of the compiler.
72    state: &'a mut CompilerState,
73    /// The current program name.
74    program_name: Symbol,
75    /// The current module name.
76    module: Vec<Symbol>,
77    /// Whether or not traversing stub.
78    is_stub: bool,
79    /// The set of local structs that have been successfully visited.
80    structs: IndexMap<Vec<Symbol>, Span>,
81}
82
83impl SymbolTableCreationVisitor<'_> {
84    /// Enter module scope with path `module`, execute `func`, and then return to the parent module.
85    pub fn in_module_scope<T>(&mut self, module: &[Symbol], func: impl FnOnce(&mut Self) -> T) -> T {
86        let parent_module = self.module.clone();
87        self.module = module.to_vec();
88        let result = func(self);
89        self.module = parent_module;
90        result
91    }
92}
93
94impl AstVisitor for SymbolTableCreationVisitor<'_> {
95    type AdditionalInput = ();
96    type Output = ();
97
98    fn visit_const(&mut self, input: &ConstDeclaration) {
99        // Just add the const to the symbol table without validating it; that will happen later
100        // in type checking.
101        let const_path: Vec<Symbol> = self.module.iter().cloned().chain(std::iter::once(input.place.name)).collect();
102        if let Err(err) = self.state.symbol_table.insert_variable(self.program_name, &const_path, VariableSymbol {
103            type_: input.type_.clone(),
104            span: input.place.span,
105            declaration: VariableType::Const,
106        }) {
107            self.state.handler.emit_err(err);
108        }
109    }
110}
111
112impl ProgramVisitor for SymbolTableCreationVisitor<'_> {
113    fn visit_program_scope(&mut self, input: &ProgramScope) {
114        // Set current program name
115        self.program_name = input.program_id.name.name;
116        self.is_stub = false;
117
118        // Visit the program scope
119        input.consts.iter().for_each(|(_, c)| self.visit_const(c));
120        input.structs.iter().for_each(|(_, c)| self.visit_struct(c));
121        input.mappings.iter().for_each(|(_, c)| self.visit_mapping(c));
122        input.storage_variables.iter().for_each(|(_, c)| self.visit_storage_variable(c));
123        input.functions.iter().for_each(|(_, c)| self.visit_function(c));
124        if let Some(c) = input.constructor.as_ref() {
125            self.visit_constructor(c);
126        }
127    }
128
129    fn visit_module(&mut self, input: &Module) {
130        self.program_name = input.program_name;
131        self.in_module_scope(&input.path.clone(), |slf| {
132            input.structs.iter().for_each(|(_, c)| slf.visit_struct(c));
133            input.functions.iter().for_each(|(_, c)| slf.visit_function(c));
134            input.consts.iter().for_each(|(_, c)| slf.visit_const(c));
135        })
136    }
137
138    fn visit_import(&mut self, input: &Program) {
139        self.visit_program(input)
140    }
141
142    fn visit_struct(&mut self, input: &Composite) {
143        // Allow up to one local redefinition for each external struct.
144        let full_name = self.module.iter().cloned().chain(std::iter::once(input.name())).collect::<Vec<Symbol>>();
145
146        if !input.is_record {
147            if let Some(prev_span) = self.structs.get(&full_name) {
148                // The struct already existed
149                return self.state.handler.emit_err(SymbolTable::emit_shadow_error(
150                    input.identifier.name,
151                    input.identifier.span,
152                    *prev_span,
153                ));
154            }
155
156            self.structs.insert(full_name.clone(), input.identifier.span);
157        }
158
159        if input.is_record {
160            // While records are not allowed in submodules, we stll use their full name in the records table.
161            // We don't expect the full name to have more than a single Symbol though.
162            let program_name = input.external.unwrap_or(self.program_name);
163            if let Err(err) =
164                self.state.symbol_table.insert_record(Location::new(program_name, full_name), input.clone())
165            {
166                self.state.handler.emit_err(err);
167            }
168        } else if let Err(err) = self.state.symbol_table.insert_struct(self.program_name, &full_name, input.clone()) {
169            self.state.handler.emit_err(err);
170        }
171    }
172
173    fn visit_mapping(&mut self, input: &Mapping) {
174        // Add the variable associated with the mapping to the symbol table.
175        if let Err(err) = self.state.symbol_table.insert_global(
176            Location::new(self.program_name, vec![input.identifier.name]),
177            VariableSymbol {
178                type_: Type::Mapping(MappingType {
179                    key: Box::new(input.key_type.clone()),
180                    value: Box::new(input.value_type.clone()),
181                    program: self.program_name,
182                }),
183                span: input.span,
184                declaration: VariableType::Storage,
185            },
186        ) {
187            self.state.handler.emit_err(err);
188        }
189    }
190
191    fn visit_storage_variable(&mut self, input: &StorageVariable) {
192        // Add the variable associated with the mapping to the symbol table.
193
194        // The type of non-vector storage variables is implicitly wrapped in an optional.
195        let type_ = match input.type_ {
196            Type::Vector(_) => input.type_.clone(),
197            _ => Type::Optional(OptionalType { inner: Box::new(input.type_.clone()) }),
198        };
199
200        if let Err(err) = self.state.symbol_table.insert_global(
201            Location::new(self.program_name, vec![input.identifier.name]),
202            VariableSymbol { type_, span: input.span, declaration: VariableType::Storage },
203        ) {
204            self.state.handler.emit_err(err);
205        }
206    }
207
208    fn visit_function(&mut self, input: &Function) {
209        let full_name = self.module.iter().cloned().chain(std::iter::once(input.name())).collect::<Vec<Symbol>>();
210        if let Err(err) =
211            self.state.symbol_table.insert_function(Location::new(self.program_name, full_name), input.clone())
212        {
213            self.state.handler.emit_err(err);
214        }
215    }
216
217    fn visit_stub(&mut self, input: &Stub) {
218        self.is_stub = true;
219        self.program_name = input.stub_id.name.name;
220        input.functions.iter().for_each(|(_, c)| self.visit_function_stub(c));
221        input.structs.iter().for_each(|(_, c)| self.visit_struct_stub(c));
222        input.mappings.iter().for_each(|(_, c)| self.visit_mapping(c));
223    }
224
225    fn visit_function_stub(&mut self, input: &FunctionStub) {
226        // Construct the location for the function.
227        let location = Location::new(self.program_name, vec![input.name()]);
228        // Initialize the function symbol.
229        if let Err(err) = self.state.symbol_table.insert_function(location.clone(), Function::from(input.clone())) {
230            self.state.handler.emit_err(err);
231        }
232
233        // If the `FunctionStub` is an async transition, attach the finalize logic to the function.
234        // NOTE - for an external function like this, we really only need to attach the finalizer
235        // for the use of `assert_simple_async_transition_call` in the static analyzer.
236        // In principle that could be handled differently.
237        if matches!(input.variant, Variant::AsyncTransition) {
238            // This matches the logic in the disassembler.
239            let name = Symbol::intern(&format!("finalize/{}", input.name()));
240            if let Err(err) = self.state.symbol_table.attach_finalizer(
241                location,
242                Location::new(self.program_name, vec![name]),
243                Vec::new(),
244                Vec::new(),
245            ) {
246                self.state.handler.emit_err(err);
247            }
248        }
249    }
250
251    fn visit_struct_stub(&mut self, input: &Composite) {
252        if let Some(program) = input.external {
253            assert_eq!(program, self.program_name);
254        }
255
256        if input.is_record {
257            let program_name = input.external.unwrap_or(self.program_name);
258            if let Err(err) =
259                self.state.symbol_table.insert_record(Location::new(program_name, vec![input.name()]), input.clone())
260            {
261                self.state.handler.emit_err(err);
262            }
263        } else if let Err(err) =
264            self.state.symbol_table.insert_struct(self.program_name, &[input.name()], input.clone())
265        {
266            self.state.handler.emit_err(err);
267        }
268    }
269}