leo-passes 4.0.2

Compiler passes for the Leo programming language
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
// Copyright (C) 2019-2026 Provable Inc.
// This file is part of the Leo library.

// The Leo library is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// The Leo library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with the Leo library. If not, see <https://www.gnu.org/licenses/>.

use super::MonomorphizationVisitor;
use leo_ast::{
    AstReconstructor,
    Library,
    Location,
    Module,
    Program,
    ProgramReconstructor,
    ProgramScope,
    Statement,
    Stub,
    Variant,
};
use leo_span::{Symbol, sym};

impl ProgramReconstructor for MonomorphizationVisitor<'_> {
    fn reconstruct_library(&mut self, input: Library) -> Library {
        // Collect the (re-)constructed versions of library composites that were processed
        // during reconstruct_program_scope. We filter by the library's program name so that
        // only composites belonging to this library are included.
        //
        // This ensures the library stub is updated with correct field types after monomorphization
        // (e.g., `Bar.f` pointing to the consuming program's monomorphized `Foo::[42u32]`).

        // Seed `reconstructed_functions` with non-generic library module functions.
        // These functions are not processed by the main reconstruction loop (which only handles
        // generic library functions and current-program functions), so we process them here via
        // `reconstruct_function` to update any composite type references (e.g., a non-generic
        // function that constructs or returns a generic composite with concrete const arguments).
        // Without this, the composite expressions in the function body and its signature would
        // still reference the generic composite location rather than the monomorphized one.
        for (module_path, module) in &input.modules {
            for (name, f) in &module.functions {
                if f.const_parameters.is_empty() {
                    let path: Vec<Symbol> = module_path.iter().cloned().chain(std::iter::once(*name)).collect();
                    let loc = Location::new(input.name, path);
                    if !self.reconstructed_functions.contains_key(&loc) {
                        let processed = self.reconstruct_function(f.clone());
                        self.reconstructed_functions.insert(loc, processed);
                    }
                }
            }
        }

        Library {
            name: input.name,
            // Reconstruct each module, which collects its monomorphized composites and functions
            // from reconstructed_composites/reconstructed_functions via reconstruct_module.
            modules: input.modules.into_iter().map(|(id, m)| (id, self.reconstruct_module(m))).collect(),
            structs: self
                .reconstructed_composites
                .iter()
                .filter_map(|(loc, c)| {
                    loc.path
                        .split_last()
                        .filter(|(_, rest)| rest.is_empty() && loc.program == input.name)
                        .map(|(last, _)| (*last, c.clone()))
                })
                .collect(),
            consts: input
                .consts
                .into_iter()
                .map(|(i, c)| match self.reconstruct_const(c) {
                    (Statement::Const(declaration), _) => (i, declaration),
                    _ => panic!("`reconstruct_const` can only return `Statement::Const`"),
                })
                .collect(),
            // Collect top-level library function instances. Non-generic functions are kept from
            // the original input; generic functions are superseded by their reconstructed versions
            // in `reconstructed_functions`. Excluding generic originals prevents duplicates that
            // would cause TypeChecking to report "defined multiple times" errors on the next
            // iteration.
            functions: input
                .functions
                .into_iter()
                .filter(|(_, f)| f.const_parameters.is_empty())
                .chain(self.reconstructed_functions.iter().filter_map(|(loc, f)| {
                    loc.path
                        .split_last()
                        .filter(|(_, rest)| rest.is_empty() && loc.program == input.name)
                        .map(|(last, _)| (*last, f.clone()))
                }))
                .collect(),
            interfaces: input.interfaces,
        }
    }

    fn reconstruct_program_scope(&mut self, input: ProgramScope) -> ProgramScope {
        // Set the current program name from the input.
        self.program = input.program_id.as_symbol();

        // We first reconstruct all composites. Composite fields can instantiate other generic composites that we need to handle
        // first. We'll then address composite expressions and other compoiste type instantiations.
        let composite_order = self.state.composite_graph.post_order().unwrap();

        // Reconstruct composites in post-order.
        for loc in &composite_order {
            if let Some(composite) = self.composite_map.swap_remove(loc) {
                // Perform monomorphization or other reconstruction logic.
                let reconstructed_composite = self.reconstruct_composite(composite);
                // Store the reconstructed composite under its original location.
                // For library composites loc.program != self.program; preserving the library
                // program name here is what allows monomorphize_composite to find them later.
                // Both the program-scope and module output-collection loops filter by
                // `loc.program == self.program`, so library entries stay invisible to the output.
                self.reconstructed_composites.insert(loc.clone(), reconstructed_composite);
            }
        }

        // If there are some local composites left in `composite_map`, that means these are dead
        // composites since they do not show up in `composite_graph`. Therefore, they won't be
        // reconstructed, implying a change in the reconstructed program.
        if !self.composite_map.is_empty() {
            self.changed = true;
        }

        // Next, handle generic functions
        //
        // Compute a post-order traversal of the call graph. This ensures that functions are processed after all their callees.
        // Make sure to only compute the post order by considering the entry points of the program, which are `async transition`, `transition` and `function`.
        // We must consider entry points to ignore const generic inlines that have already been monomorphized but never called.
        let order = self
            .state
            .call_graph
            .post_order_with_filter(|location| {
                // Filter out locations that are not from this program.
                if location.program != self.program {
                    return false;
                }
                // Allow constructors.
                if location.program == self.program && location.path == vec![sym::constructor] {
                    return true;
                }
                self.function_map
                    .get(location)
                    .map(|f| {
                        matches!(f.variant, Variant::EntryPoint)
                            | (f.variant == Variant::Fn && f.const_parameters.is_empty())
                    })
                    .unwrap_or(false)
            })
            .unwrap() // This unwrap is safe because the type checker guarantees an acyclic graph.
            .into_iter()
            .filter(|location| {
                // Always include current-program functions.
                if location.program == self.program {
                    return true;
                }
                // Also include generic library functions reachable from the current program.
                // Non-generic library functions need no monomorphization and are handled by
                // the early-return in `reconstruct_call` (no const arguments).
                self.state.symbol_table.is_library(location.program)
                    && self.function_map.get(location).map(|f| !f.const_parameters.is_empty()).unwrap_or(false)
            })
            .collect::<Vec<_>>();

        for function_name in &order {
            // Reconstruct functions in post-order.
            if let Some(function) = self.function_map.swap_remove(function_name) {
                // Reconstruct the function.
                let reconstructed_function = self.reconstruct_function(function);
                // Add the reconstructed function to the mapping.
                self.reconstructed_functions.insert(function_name.clone(), reconstructed_function);
            }
        }

        // Now reconstruct mappings and storage variables
        let mappings =
            input.mappings.into_iter().map(|(id, mapping)| (id, self.reconstruct_mapping(mapping))).collect();
        let storage_variables = input
            .storage_variables
            .into_iter()
            .map(|(id, storage_variable)| (id, self.reconstruct_storage_variable(storage_variable)))
            .collect();

        // Then consts
        let consts = input
            .consts
            .into_iter()
            .map(|(i, c)| match self.reconstruct_const(c) {
                (Statement::Const(declaration), _) => (i, declaration),
                _ => panic!("`reconstruct_const` can only return `Statement::Const`"),
            })
            .collect();

        // Reconstruct the constructor last, as it cannot be called by any other function.
        let constructor = input.constructor.map(|c| self.reconstruct_constructor(c));

        // Now retain only functions that are either not yet monomorphized or are still referenced by calls.
        self.reconstructed_functions.retain(|l, _| {
            let is_monomorphized = self.monomorphized_functions.contains(l);
            let is_still_called = self.unresolved_calls.iter().any(|c| c.function.expect_global_location() == l);
            !is_monomorphized || is_still_called
        });

        // Move reconstructed functions into the final `ProgramScope`.
        // Make sure to place transitions before all the other functions.
        let (transitions, mut non_transitions): (Vec<_>, Vec<_>) =
            self.reconstructed_functions.clone().into_iter().partition(|(_, f)| f.variant.is_entry());

        let mut all_functions = transitions;
        all_functions.append(&mut non_transitions);

        // Return the fully reconstructed scope with updated functions.
        ProgramScope {
            program_id: input.program_id,
            parents: input.parents.into_iter().map(|(s, t)| (s, self.reconstruct_type(t).0)).collect(),
            composites: self
                .reconstructed_composites
                .iter()
                .filter_map(|(loc, c)| {
                    // Only consider composites defined at program scope within the same program.
                    loc.path
                        .split_last()
                        .filter(|(_, rest)| rest.is_empty() && loc.program == self.program)
                        .map(|(last, _)| (*last, c.clone()))
                })
                .collect(),
            mappings,
            storage_variables,
            functions: all_functions
                .iter()
                .filter_map(|(loc, f)| {
                    // Only consider functions defined at program scope within the same program.
                    loc.path
                        .split_last()
                        .filter(|(_, rest)| rest.is_empty() && loc.program == self.program)
                        .map(|(last, _)| (*last, f.clone()))
                })
                .collect(),
            interfaces: input.interfaces.into_iter().map(|(i, int)| (i, self.reconstruct_interface(int))).collect(),
            constructor,
            consts,
            span: input.span,
        }
    }

    fn reconstruct_program(&mut self, input: Program) -> Program {
        // STUB ORDERING INVARIANT
        //
        // The ordering of operations in this function is critical for correctness:
        //
        // 1. FromLeo stubs are processed FIRST (before the current program's scope).
        //    Each FromLeo stub recursively calls reconstruct_program for the dependency,
        //    which populates `reconstructed_composites` with the dependency's composites.
        //    This means that when the current program's scope is processed, any composites
        //    from FromLeo dependencies (e.g. `child.aleo::Bar`) are already available for
        //    monomorphization.
        //
        // 2. The current program's composite_map and function_map are populated AFTER
        //    FromLeo stubs are processed (since each FromLeo recursive call clears and
        //    repopulates these maps for its own scope).
        //
        // 3. Library composites (from FromLibrary stubs) are added to composite_map so
        //    they are processed in reconstruct_program_scope (their field types updated).
        //
        // 4. program_scopes are processed AFTER FromLeo stubs and after composite_map is
        //    populated for the current program.
        //
        // 5. FromLibrary/FromAleo stubs are processed AFTER program_scopes, so that
        //    reconstruct_library can collect monomorphized composites from reconstructed_composites.

        // Capture the original stub insertion order before partitioning, so we can
        // reassemble stubs in their original order after processing.  The type-checking
        // pass that runs after monomorphization depends on this order.
        let stub_key_order: Vec<_> = input.stubs.keys().cloned().collect();

        // Partition stubs early (non-consuming borrow not possible, so partition now and
        // process in two phases).
        let (from_leo_stubs, other_stubs): (Vec<_>, Vec<_>) =
            input.stubs.into_iter().partition(|(_, stub)| matches!(stub, Stub::FromLeo { .. }));

        // Pre-Phase 1: Seed composite_map with library composites BEFORE recursing into
        // FromLeo stubs. This is critical for correctness: when a FromLeo dependency
        // (e.g. `helper.aleo`) uses library structs, its inner `Program` may not carry the
        // library stubs (they are only present on the top-level compilation unit). By inserting
        // the library composites here — before the recursive call — they are already in
        // composite_map when the inner call's Phase 2 retains non-previous-program entries.
        // Entries that are already in reconstructed_composites (processed by a prior inner call)
        // will be silently skipped by the composite_order loop via `swap_remove` returning None.
        other_stubs.iter().for_each(|(_, stub)| {
            if let Stub::FromLibrary { library, .. } = stub {
                library.structs.iter().for_each(|(name, c)| {
                    self.composite_map.entry(Location::new(library.name, vec![*name])).or_insert_with(|| c.clone());
                });
                // Seed function_map with library function definitions so that reconstruct_call can
                // inline them into the consuming program under mangled names.
                library.functions.iter().for_each(|(name, f)| {
                    self.function_map.entry(Location::new(library.name, vec![*name])).or_insert_with(|| f.clone());
                });
                // Seed composite_map and function_map with items from library submodules.
                library.modules.iter().for_each(|(module_path, m)| {
                    m.composites.iter().for_each(|(name, c)| {
                        let path: Vec<Symbol> = module_path.iter().cloned().chain(std::iter::once(*name)).collect();
                        self.composite_map.entry(Location::new(library.name, path)).or_insert_with(|| c.clone());
                    });
                    m.functions.iter().for_each(|(name, f)| {
                        let path: Vec<Symbol> = module_path.iter().cloned().chain(std::iter::once(*name)).collect();
                        self.function_map.entry(Location::new(library.name, path)).or_insert_with(|| f.clone());
                    });
                });
            }
        });

        // Phase 1: Process FromLeo stubs.
        // This recursively calls reconstruct_program for each dependency, populating
        // reconstructed_composites with their composites before the current scope runs.
        let from_leo_stubs: indexmap::IndexMap<_, _> =
            from_leo_stubs.into_iter().map(|(id, stub)| (id, self.reconstruct_stub(stub))).collect();

        // Phase 2: Set up for the current program.
        // After processing FromLeo stubs, reset and populate composite_map / function_map
        // for the current program's scope.
        //
        // IMPORTANT: Instead of clearing composite_map entirely, we only remove composites
        // belonging to the previous program (self.program). Library composites (loc.program !=
        // self.program) are retained so that deeply nested FromLeo programs — whose inner
        // Program objects may not carry library stubs — can still access them.
        let prev_program = self.program;
        self.composite_map.retain(|loc, _| loc.program != prev_program);
        // Retain library functions (analogous to composite_map.retain above) so that deeply-nested
        // FromLeo programs — whose inner Program objects may not carry library stubs — can still
        // inline library functions when their scopes are processed.
        self.function_map.retain(|loc, _| self.state.symbol_table.is_library(loc.program));

        self.program =
            *input.program_scopes.first().expect("a program must have a single program scope at this stage").0;

        // Populate `self.function_map` using the functions in the program scopes and the modules.
        input
            .modules
            .iter()
            .flat_map(|(module_path, m)| {
                m.functions.iter().map(move |(name, f)| {
                    (module_path.iter().cloned().chain(std::iter::once(*name)).collect(), f.clone())
                })
            })
            .chain(
                input
                    .program_scopes
                    .iter()
                    .flat_map(|(_, scope)| scope.functions.iter().map(|(name, f)| (vec![*name], f.clone()))),
            )
            .for_each(|(full_name, f)| {
                self.function_map.insert(Location::new(self.program, full_name), f);
            });

        // Populate `self.composite_map` using the composites in the program scopes and the modules.
        input
            .modules
            .iter()
            .flat_map(|(module_path, m)| {
                m.composites.iter().map(move |(name, f)| {
                    (module_path.iter().cloned().chain(std::iter::once(*name)).collect(), f.clone())
                })
            })
            .chain(
                input
                    .program_scopes
                    .iter()
                    .flat_map(|(_, scope)| scope.composites.iter().map(|(name, f)| (vec![*name], f.clone()))),
            )
            .for_each(|(full_name, f)| {
                self.composite_map.insert(Location::new(self.program, full_name), f);
            });

        // Phase 3: Process the current program's scope.
        // composite_map now has:
        //   - the current program's own composites,
        //   - library composites from FromLibrary stubs.
        // reconstructed_composites now has composites from all FromLeo dependencies.
        let program_scopes =
            input.program_scopes.into_iter().map(|(id, scope)| (id, self.reconstruct_program_scope(scope))).collect();

        // Phase 4: Process FromLibrary/FromAleo stubs AFTER program_scopes.
        // reconstruct_library collects monomorphized composites from reconstructed_composites,
        // which is now populated with the current program's library composite instances.
        let other_stubs: indexmap::IndexMap<_, _> =
            other_stubs.into_iter().map(|(id, stub)| (id, self.reconstruct_stub(stub))).collect();

        let mut from_leo_map = from_leo_stubs;
        let mut other_map = other_stubs;
        let stubs = stub_key_order
            .into_iter()
            .map(|id| {
                let stub = from_leo_map
                    .swap_remove(&id)
                    .or_else(|| other_map.swap_remove(&id))
                    .expect("every stub key must appear in exactly one partition");
                (id, stub)
            })
            .collect();

        // Reconstruct modules after `self.reconstructed_composites` and `self.reconstructed_functions`
        // have been populated.
        Program {
            stubs,
            program_scopes,
            modules: input.modules.into_iter().map(|(id, module)| (id, self.reconstruct_module(module))).collect(),
            ..input
        }
    }

    fn reconstruct_module(&mut self, input: Module) -> Module {
        // Here we're reconstructing composites and functions from `reconstructed_functions` and
        // `reconstructed_composites` based on their paths and whether they match the module path.
        // Use `input.program_name` rather than `self.program` since `self.program` may have been
        // updated by a nested reconstruct_program call (e.g., for a Stub::FromLeo dependency).
        Module {
            composites: self
                .reconstructed_composites
                .iter()
                .filter_map(|(loc, c)| {
                    loc.path
                        .split_last()
                        .filter(|(_, rest)| *rest == input.path && loc.program == input.program_name)
                        .map(|(last, _)| (*last, c.clone()))
                })
                .collect(),

            // Collect functions for this module from `reconstructed_functions`. Non-generic
            // functions are included because the main reconstruction loop processes all
            // reachable functions (both generic and non-generic). Generic originals are
            // excluded because only their monomorphized specializations are relevant.
            functions: self
                .reconstructed_functions
                .iter()
                .filter_map(|(loc, f)| {
                    loc.path
                        .split_last()
                        .filter(|(_, rest)| *rest == input.path && loc.program == input.program_name)
                        .map(|(last, _)| (*last, f.clone()))
                })
                .collect(),
            interfaces: input.interfaces.into_iter().map(|(i, int)| (i, self.reconstruct_interface(int))).collect(),

            ..input
        }
    }
}