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, 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    Program,
30    ProgramScope,
31    ProgramVisitor,
32    Stub,
33    Type,
34    Variant,
35};
36use leo_errors::{AstError, LeoError, Result};
37use leo_span::Symbol;
38
39use indexmap::IndexSet;
40
41/// A pass to fill the SymbolTable.
42///
43/// Only creates the global data - local data will be constructed during type checking.
44pub struct SymbolTableCreation;
45
46impl Pass for SymbolTableCreation {
47    type Input = ();
48    type Output = ();
49
50    const NAME: &'static str = "SymbolTableCreation";
51
52    fn do_pass(_input: Self::Input, state: &mut CompilerState) -> Result<Self::Output> {
53        let ast = std::mem::take(&mut state.ast);
54        let mut visitor = SymbolTableCreationVisitor {
55            state,
56            structs: IndexSet::new(),
57            program_name: Symbol::intern(""),
58            module: vec![],
59            is_stub: false,
60        };
61        visitor.visit_program(ast.as_repr());
62        visitor.state.handler.last_err().map_err(|e| *e)?;
63        visitor.state.ast = ast;
64        Ok(())
65    }
66}
67
68struct SymbolTableCreationVisitor<'a> {
69    /// The state of the compiler.
70    state: &'a mut CompilerState,
71    /// The current program name.
72    program_name: Symbol,
73    /// The current module name.
74    module: Vec<Symbol>,
75    /// Whether or not traversing stub.
76    is_stub: bool,
77    /// The set of local structs that have been successfully visited.
78    structs: IndexSet<Vec<Symbol>>,
79}
80
81impl SymbolTableCreationVisitor<'_> {
82    /// Enter module scope with path `module`, execute `func`, and then return to the parent module.
83    pub fn in_module_scope<T>(&mut self, module: &[Symbol], func: impl FnOnce(&mut Self) -> T) -> T {
84        let parent_module = self.module.clone();
85        self.module = module.to_vec();
86        let result = func(self);
87        self.module = parent_module;
88        result
89    }
90}
91
92impl AstVisitor for SymbolTableCreationVisitor<'_> {
93    type AdditionalInput = ();
94    type Output = ();
95
96    fn visit_const(&mut self, input: &ConstDeclaration) {
97        // Just add the const to the symbol table without validating it; that will happen later
98        // in type checking.
99        let const_path: Vec<Symbol> = self.module.iter().cloned().chain(std::iter::once(input.place.name)).collect();
100        if let Err(err) = self.state.symbol_table.insert_variable(self.program_name, &const_path, VariableSymbol {
101            type_: input.type_.clone(),
102            span: input.place.span,
103            declaration: VariableType::Const,
104        }) {
105            self.state.handler.emit_err(err);
106        }
107    }
108}
109
110impl ProgramVisitor for SymbolTableCreationVisitor<'_> {
111    fn visit_program_scope(&mut self, input: &ProgramScope) {
112        // Set current program name
113        self.program_name = input.program_id.name.name;
114        self.is_stub = false;
115
116        // Visit the program scope
117        input.consts.iter().for_each(|(_, c)| (self.visit_const(c)));
118        input.structs.iter().for_each(|(_, c)| (self.visit_struct(c)));
119        input.mappings.iter().for_each(|(_, c)| (self.visit_mapping(c)));
120        input.functions.iter().for_each(|(_, c)| (self.visit_function(c)));
121        if let Some(c) = input.constructor.as_ref() {
122            self.visit_constructor(c);
123        }
124    }
125
126    fn visit_module(&mut self, input: &Module) {
127        self.program_name = input.program_name;
128        self.in_module_scope(&input.path.clone(), |slf| {
129            input.structs.iter().for_each(|(_, c)| (slf.visit_struct(c)));
130            input.functions.iter().for_each(|(_, c)| (slf.visit_function(c)));
131            input.consts.iter().for_each(|(_, c)| (slf.visit_const(c)));
132        })
133    }
134
135    fn visit_import(&mut self, input: &Program) {
136        self.visit_program(input)
137    }
138
139    fn visit_struct(&mut self, input: &Composite) {
140        // Allow up to one local redefinition for each external struct.
141        let full_name = self.module.iter().cloned().chain(std::iter::once(input.name())).collect::<Vec<Symbol>>();
142
143        if !input.is_record && !self.structs.insert(full_name.clone()) {
144            return self.state.handler.emit_err::<LeoError>(AstError::shadowed_struct(input.name(), input.span).into());
145        }
146        if input.is_record {
147            // While records are not allowed in submodules, we stll use their full name in the records table.
148            // We don't expect the full name to have more than a single Symbol though.
149            let program_name = input.external.unwrap_or(self.program_name);
150            if let Err(err) =
151                self.state.symbol_table.insert_record(Location::new(program_name, full_name), input.clone())
152            {
153                self.state.handler.emit_err(err);
154            }
155        } else if let Err(err) = self.state.symbol_table.insert_struct(self.program_name, &full_name, input.clone()) {
156            self.state.handler.emit_err(err);
157        }
158    }
159
160    fn visit_mapping(&mut self, input: &Mapping) {
161        // Add the variable associated with the mapping to the symbol table.
162        if let Err(err) = self.state.symbol_table.insert_global(
163            Location::new(self.program_name, vec![input.identifier.name]),
164            VariableSymbol {
165                type_: Type::Mapping(MappingType {
166                    key: Box::new(input.key_type.clone()),
167                    value: Box::new(input.value_type.clone()),
168                    program: self.program_name,
169                }),
170                span: input.span,
171                declaration: VariableType::Mut,
172            },
173        ) {
174            self.state.handler.emit_err(err);
175        }
176    }
177
178    fn visit_function(&mut self, input: &Function) {
179        let full_name = self.module.iter().cloned().chain(std::iter::once(input.name())).collect::<Vec<Symbol>>();
180        if let Err(err) =
181            self.state.symbol_table.insert_function(Location::new(self.program_name, full_name), input.clone())
182        {
183            self.state.handler.emit_err(err);
184        }
185    }
186
187    fn visit_stub(&mut self, input: &Stub) {
188        self.is_stub = true;
189        self.program_name = input.stub_id.name.name;
190        input.functions.iter().for_each(|(_, c)| (self.visit_function_stub(c)));
191        input.structs.iter().for_each(|(_, c)| (self.visit_struct_stub(c)));
192        input.mappings.iter().for_each(|(_, c)| (self.visit_mapping(c)));
193    }
194
195    fn visit_function_stub(&mut self, input: &FunctionStub) {
196        // Construct the location for the function.
197        let location = Location::new(self.program_name, vec![input.name()]);
198        // Initialize the function symbol.
199        if let Err(err) = self.state.symbol_table.insert_function(location.clone(), Function::from(input.clone())) {
200            self.state.handler.emit_err(err);
201        }
202
203        // If the `FunctionStub` is an async transition, attach the finalize logic to the function.
204        // NOTE - for an external function like this, we really only need to attach the finalizer
205        // for the use of `assert_simple_async_transition_call` in the static analyzer.
206        // In principle that could be handled differently.
207        if matches!(input.variant, Variant::AsyncTransition) {
208            // This matches the logic in the disassembler.
209            let name = Symbol::intern(&format!("finalize/{}", input.name()));
210            if let Err(err) = self.state.symbol_table.attach_finalizer(
211                location,
212                Location::new(self.program_name, vec![name]),
213                Vec::new(),
214                Vec::new(),
215            ) {
216                self.state.handler.emit_err(err);
217            }
218        }
219    }
220
221    fn visit_struct_stub(&mut self, input: &Composite) {
222        if let Some(program) = input.external {
223            assert_eq!(program, self.program_name);
224        }
225
226        if input.is_record {
227            let program_name = input.external.unwrap_or(self.program_name);
228            if let Err(err) =
229                self.state.symbol_table.insert_record(Location::new(program_name, vec![input.name()]), input.clone())
230            {
231                self.state.handler.emit_err(err);
232            }
233        } else if let Err(err) =
234            self.state.symbol_table.insert_struct(self.program_name, &[input.name()], input.clone())
235        {
236            self.state.handler.emit_err(err);
237        }
238    }
239}