Skip to main content

leo_passes/
global_items_collection.rs

1// Copyright (C) 2019-2026 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
17//! Collects all *global items* into the symbol table after path resolution.
18//!
19//! This pass is responsible for registering globally visible program items,
20//! including functions, structs, records, mappings, constants, and storage
21//! variables, along with their associated types. It operates only on *resolved*
22//! paths and assumes that global variables and local scopes have already been
23//! established by earlier passes.
24//!
25//! Unlike earlier pipeline stages, this pass does **not** create scopes,
26//! resolve names, or insert local symbols. Its sole responsibility is to
27//! populate the global portion of the symbol table with fully qualified
28//! `Location`s and to attach type information where applicable.
29//!
30//! This pass runs after `GlobalVarsCollection` and `PathResolution`, and
31//! before type checking. After it completes, the symbol table is guaranteed
32//! to contain all globally defined items, enabling subsequent passes to
33//! perform type checking and validation without mutating symbol structure.
34
35use crate::{CompilerState, Pass};
36
37use leo_ast::{
38    AleoProgram,
39    AstVisitor,
40    Composite,
41    ConstDeclaration,
42    Function,
43    FunctionStub,
44    Interface,
45    Library,
46    Location,
47    Mapping,
48    MappingType,
49    Module,
50    OptionalType,
51    ProgramScope,
52    ProgramVisitor,
53    StorageVariable,
54    Type,
55};
56use leo_errors::Result;
57use leo_span::Symbol;
58
59/// A pass to fill the SymbolTable.
60///
61/// Only creates the global data - local data will be constructed during type checking.
62pub struct GlobalItemsCollection;
63
64impl Pass for GlobalItemsCollection {
65    type Input = ();
66    type Output = ();
67
68    const NAME: &'static str = "GlobalItemsCollection";
69
70    fn do_pass(_input: Self::Input, state: &mut CompilerState) -> Result<Self::Output> {
71        let ast = std::mem::take(&mut state.ast);
72        let mut visitor = GlobalItemsCollectionVisitor { state, program_name: Symbol::intern(""), module: vec![] };
73
74        ast.visit(
75            |program| visitor.visit_program(program),
76            |library| {
77                // no-op for libraries
78                let _ = library;
79            },
80        );
81
82        visitor.state.handler.last_err()?;
83        visitor.state.ast = ast;
84        Ok(())
85    }
86}
87
88struct GlobalItemsCollectionVisitor<'a> {
89    /// The state of the compiler.
90    state: &'a mut CompilerState,
91    /// The current program name.
92    program_name: Symbol,
93    /// The current module name.
94    module: Vec<Symbol>,
95}
96
97impl GlobalItemsCollectionVisitor<'_> {
98    /// Enter module scope with path `module`, execute `func`, and then return to the parent module.
99    pub fn in_module_scope<T>(&mut self, module: &[Symbol], func: impl FnOnce(&mut Self) -> T) -> T {
100        let parent_module = self.module.clone();
101        self.module = module.to_vec();
102        let result = func(self);
103        self.module = parent_module;
104        result
105    }
106}
107
108impl AstVisitor for GlobalItemsCollectionVisitor<'_> {
109    type AdditionalInput = ();
110    type Output = ();
111
112    fn visit_const(&mut self, input: &ConstDeclaration) {
113        // Just set the type of the const in the symbol table.
114        let const_path: Vec<Symbol> = self.module.iter().cloned().chain(std::iter::once(input.place.name)).collect();
115        self.state.symbol_table.set_global_type(&Location::new(self.program_name, const_path), input.type_.clone());
116    }
117}
118
119impl ProgramVisitor for GlobalItemsCollectionVisitor<'_> {
120    fn visit_program_scope(&mut self, input: &ProgramScope) {
121        // Set current program name
122        self.program_name = input.program_id.as_symbol();
123
124        // Visit the program scope
125        input.consts.iter().for_each(|(_, c)| self.visit_const(c));
126        input.composites.iter().for_each(|(_, c)| self.visit_composite(c));
127        input.mappings.iter().for_each(|(_, c)| self.visit_mapping(c));
128        input.storage_variables.iter().for_each(|(_, c)| self.visit_storage_variable(c));
129        input.functions.iter().for_each(|(_, c)| self.visit_function(c));
130        input.interfaces.iter().for_each(|(_, c)| self.visit_interface(c));
131        if let Some(c) = input.constructor.as_ref() {
132            self.visit_constructor(c);
133        }
134    }
135
136    fn visit_module(&mut self, input: &Module) {
137        self.program_name = input.program_name;
138        self.in_module_scope(&input.path.clone(), |slf| {
139            input.composites.iter().for_each(|(_, c)| slf.visit_composite(c));
140            input.functions.iter().for_each(|(_, c)| slf.visit_function(c));
141            input.consts.iter().for_each(|(_, c)| slf.visit_const(c));
142            input.interfaces.iter().for_each(|(_, c)| slf.visit_interface(c));
143        })
144    }
145
146    fn visit_composite(&mut self, input: &Composite) {
147        let full_name = self.module.iter().cloned().chain(std::iter::once(input.name())).collect::<Vec<Symbol>>();
148
149        if input.is_record {
150            // While records are not allowed in submodules, we stll use their full name in the records table.
151            // We don't expect the full name to have more than a single Symbol though.
152            if let Err(err) =
153                self.state.symbol_table.insert_record(Location::new(self.program_name, full_name), input.clone())
154            {
155                self.state.handler.emit_err(err);
156            }
157        } else if let Err(err) =
158            self.state.symbol_table.insert_struct(Location::new(self.program_name, full_name.clone()), input.clone())
159        {
160            self.state.handler.emit_err(err);
161        }
162    }
163
164    fn visit_mapping(&mut self, input: &Mapping) {
165        // Set the type of the variable associated with the mapping in the symbol table.
166        self.state.symbol_table.set_global_type(
167            &Location::new(self.program_name, vec![input.identifier.name]),
168            Type::Mapping(MappingType {
169                key: Box::new(input.key_type.clone()),
170                value: Box::new(input.value_type.clone()),
171            }),
172        );
173    }
174
175    fn visit_storage_variable(&mut self, input: &StorageVariable) {
176        // Set the type of the storage variable in the symbol table.
177
178        // The type of non-vector storage variables is implicitly wrapped in an optional.
179        let type_ = match input.type_ {
180            Type::Vector(_) => input.type_.clone(),
181            _ => Type::Optional(OptionalType { inner: Box::new(input.type_.clone()) }),
182        };
183
184        self.state.symbol_table.set_global_type(&Location::new(self.program_name, vec![input.identifier.name]), type_);
185    }
186
187    fn visit_function(&mut self, input: &Function) {
188        let full_name = self.module.iter().cloned().chain(std::iter::once(input.name())).collect::<Vec<Symbol>>();
189        let loc = Location::new(self.program_name, full_name);
190        if let Err(err) = self.state.symbol_table.insert_function(loc, input.clone()) {
191            self.state.handler.emit_err(err);
192        }
193    }
194
195    fn visit_interface(&mut self, input: &Interface) {
196        let full_name = self.module.iter().cloned().chain(std::iter::once(input.name())).collect::<Vec<Symbol>>();
197        if let Err(err) =
198            self.state.symbol_table.insert_interface(Location::new(self.program_name, full_name), input.clone())
199        {
200            self.state.handler.emit_err(err);
201        }
202    }
203
204    fn visit_library(&mut self, input: &Library) {
205        self.program_name = input.name;
206
207        input.interfaces.iter().for_each(|(_, i)| self.visit_interface(i));
208        input.structs.iter().for_each(|(_, s)| self.visit_composite(s));
209        input.consts.iter().for_each(|(_, c)| self.visit_const(c));
210        input.functions.iter().for_each(|(_, f)| self.visit_function(f));
211        input.modules.values().for_each(|m| {
212            self.visit_module(m);
213        });
214    }
215
216    fn visit_aleo_program(&mut self, input: &AleoProgram) {
217        self.program_name = input.stub_id.as_symbol();
218
219        input.functions.iter().for_each(|(_, c)| self.visit_function_stub(c));
220        input.composites.iter().for_each(|(_, c)| self.visit_composite_stub(c));
221        input.mappings.iter().for_each(|(_, c)| self.visit_mapping(c));
222    }
223
224    fn visit_function_stub(&mut self, input: &FunctionStub) {
225        // Construct the location for the function.
226        let location = Location::new(self.program_name, vec![input.name()]);
227        // Initialize the function symbol.
228        if let Err(err) = self.state.symbol_table.insert_function(location.clone(), Function::from(input.clone())) {
229            self.state.handler.emit_err(err);
230        }
231
232        // If the `FunctionStub` is an async transition, attach the finalize logic to the function.
233        // NOTE - for an external function like this, we really only need to attach the finalizer
234        // for the use of `assert_simple_async_transition_call` in the static analyzer.
235        // In principle that could be handled differently.
236        if input.has_final_output() {
237            // This matches the logic in the disassembler.
238            let name = Symbol::intern(&format!("finalize/{}", input.name()));
239            if let Err(err) = self.state.symbol_table.attach_finalizer(
240                location,
241                Location::new(self.program_name, vec![name]),
242                Vec::new(),
243                Vec::new(),
244            ) {
245                self.state.handler.emit_err(err);
246            }
247        }
248    }
249
250    fn visit_composite_stub(&mut self, input: &Composite) {
251        if input.is_record {
252            if let Err(err) = self
253                .state
254                .symbol_table
255                .insert_record(Location::new(self.program_name, vec![input.name()]), input.clone())
256            {
257                self.state.handler.emit_err(err);
258            }
259        } else if let Err(err) =
260            self.state.symbol_table.insert_struct(Location::new(self.program_name, vec![input.name()]), input.clone())
261        {
262            self.state.handler.emit_err(err);
263        }
264    }
265}