leo_passes/code_generation/
program.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 super::*;
18
19use leo_ast::{
20    Composite,
21    Constructor,
22    Function,
23    Location,
24    Mapping,
25    Member,
26    Mode,
27    NetworkName,
28    Program,
29    ProgramScope,
30    Type,
31    UpgradeVariant,
32    Variant,
33    snarkvm_admin_constructor,
34    snarkvm_checksum_constructor,
35    snarkvm_noupgrade_constructor,
36};
37use leo_span::{Symbol, sym};
38
39use indexmap::IndexMap;
40use itertools::Itertools;
41use snarkvm::prelude::{CanaryV0, MainnetV0, TestnetV0};
42use std::fmt::Write as _;
43
44const EXPECT_STR: &str = "Failed to write code";
45
46impl<'a> CodeGeneratingVisitor<'a> {
47    pub fn visit_program(&mut self, input: &'a Program) -> String {
48        // Accumulate instructions into a program string.
49        let mut program_string = String::new();
50
51        // Print out the dependencies of the program. Already arranged in post order by Retriever module.
52        input.stubs.iter().for_each(|(program_name, _)| {
53            writeln!(program_string, "import {program_name}.aleo;").expect(EXPECT_STR);
54        });
55
56        // Retrieve the program scope.
57        // Note that type checking guarantees that there is exactly one program scope.
58        let program_scope: &ProgramScope = input.program_scopes.values().next().unwrap();
59
60        self.program_id = Some(program_scope.program_id);
61
62        // Print the program id.
63        writeln!(program_string, "program {};", program_scope.program_id).expect(EXPECT_STR);
64
65        // Get the post-order ordering of the composite data types.
66        // Note that the unwrap is safe since type checking guarantees that the struct dependency graph is acyclic.
67        let order = self.state.struct_graph.post_order().unwrap();
68
69        let this_program = self.program_id.unwrap().name.name;
70
71        let lookup = |name: &[Symbol]| {
72            self.state
73                .symbol_table
74                .lookup_struct(name)
75                .or_else(|| self.state.symbol_table.lookup_record(&Location::new(this_program, name.to_vec())))
76        };
77
78        // Visit each `Struct` or `Record` in the post-ordering and produce an Aleo struct or record.
79        for name in order.into_iter() {
80            if let Some(struct_) = lookup(&name) {
81                program_string.push_str(&self.visit_struct_or_record(struct_, &name));
82            }
83        }
84
85        // Visit each mapping in the Leo AST and produce an Aleo mapping declaration.
86        for (_symbol, mapping) in program_scope.mappings.iter() {
87            program_string.push_str(&self.visit_mapping(mapping));
88        }
89
90        // Visit each function in the program scope and produce an Aleo function.
91        // Note that in the function inlining pass, we reorder the functions such that they are in post-order.
92        // In other words, a callee function precedes its caller function in the program scope.
93        for (_symbol, function) in program_scope.functions.iter() {
94            if function.variant != Variant::AsyncFunction {
95                let mut function_string = self.visit_function(function);
96
97                // Attach the associated finalize to async transitions.
98                if function.variant == Variant::AsyncTransition {
99                    // Set state variables.
100                    self.finalize_caller = Some(function.identifier.name);
101                    // Generate code for the associated finalize function.
102                    let finalize = &self
103                        .state
104                        .symbol_table
105                        .lookup_function(&Location::new(
106                            self.program_id.unwrap().name.name,
107                            vec![function.identifier.name], // Guaranteed to live in program scope, not in any submodule
108                        ))
109                        .unwrap()
110                        .clone()
111                        .finalizer
112                        .unwrap();
113                    // Write the finalize string.
114                    function_string.push_str(
115                        &self.visit_function_with(
116                            &program_scope
117                                .functions
118                                .iter()
119                                .find(|(name, _f)| vec![*name] == finalize.location.path)
120                                .unwrap()
121                                .1,
122                            &finalize.future_inputs,
123                        ),
124                    );
125                }
126
127                program_string.push_str(&function_string);
128            }
129        }
130
131        // If the constructor exists, visit it and produce an Aleo constructor.
132        if let Some(constructor) = program_scope.constructor.as_ref() {
133            // Generate code for the constructor.
134            program_string.push_str(&self.visit_constructor(constructor));
135        }
136
137        program_string
138    }
139
140    fn visit_struct_or_record(&mut self, struct_: &'a Composite, absolute_path: &[Symbol]) -> String {
141        if struct_.is_record {
142            self.visit_record(struct_, absolute_path)
143        } else {
144            self.visit_struct(struct_, absolute_path)
145        }
146    }
147
148    fn visit_struct(&mut self, struct_: &'a Composite, absolute_path: &[Symbol]) -> String {
149        // Add private symbol to composite types.
150        self.composite_mapping.insert(absolute_path.to_vec(), (false, String::from("private"))); // todo: private by default here.
151
152        let mut output_string = format!(
153            "\nstruct {}:\n",
154            Self::legalize_path(absolute_path).unwrap_or_else(|| panic!(
155                "path format cannot be legalized at this point: {}",
156                absolute_path.iter().join("::")
157            ))
158        ); // todo: check if this is safe from name conflicts.
159
160        // Construct and append the record variables.
161        for var in struct_.members.iter() {
162            writeln!(output_string, "    {} as {};", var.identifier, Self::visit_type(&var.type_),).expect(EXPECT_STR);
163        }
164
165        output_string
166    }
167
168    fn visit_record(&mut self, record: &'a Composite, absolute_path: &[Symbol]) -> String {
169        // Add record symbol to composite types.
170        self.composite_mapping.insert(absolute_path.to_vec(), (true, "record".into()));
171
172        let mut output_string = format!("\nrecord {}:\n", record.identifier); // todo: check if this is safe from name conflicts.
173
174        let mut members = Vec::with_capacity(record.members.len());
175        let mut member_map: IndexMap<Symbol, Member> =
176            record.members.clone().into_iter().map(|member| (member.identifier.name, member)).collect();
177
178        // Add the owner field to the beginning of the members list.
179        // Note that type checking ensures that the owner field exists.
180        members.push(member_map.shift_remove(&sym::owner).unwrap());
181
182        // Add the remaining fields to the members list.
183        members.extend(member_map.into_iter().map(|(_, member)| member));
184
185        // Construct and append the record variables.
186        for var in members.iter() {
187            let mode = match var.mode {
188                Mode::Constant => "constant",
189                Mode::Public => "public",
190                Mode::None | Mode::Private => "private",
191            };
192            writeln!(
193                output_string,
194                "    {} as {}.{mode};", // todo: CAUTION private record variables only.
195                var.identifier,
196                Self::visit_type(&var.type_)
197            )
198            .expect(EXPECT_STR);
199        }
200
201        output_string
202    }
203
204    fn visit_function_with(&mut self, function: &'a Function, futures: &[Location]) -> String {
205        // Initialize the state of `self` with the appropriate values before visiting `function`.
206        self.next_register = 0;
207        self.variable_mapping = IndexMap::new();
208        self.variant = Some(function.variant);
209        // TODO: Figure out a better way to initialize.
210        self.variable_mapping.insert(sym::SelfLower, "self".to_string());
211        self.variable_mapping.insert(sym::block, "block".to_string());
212        self.variable_mapping.insert(sym::network, "network".to_string());
213        self.current_function = Some(function);
214
215        // Construct the header of the function.
216        // If a function is a program function, generate an Aleo `function`,
217        // if it is a standard function generate an Aleo `closure`,
218        // otherwise, it is an inline function, in which case a function should not be generated.
219        let mut function_string = match function.variant {
220            Variant::Transition | Variant::AsyncTransition => format!("\nfunction {}:\n", function.identifier),
221            Variant::Function => format!("\nclosure {}:\n", function.identifier),
222            Variant::AsyncFunction => format!("\nfinalize {}:\n", self.finalize_caller.unwrap()),
223            Variant::Inline => return String::new(),
224            Variant::Script => panic!("script should not appear in native code"),
225        };
226
227        let mut futures = futures.iter();
228
229        self.internal_record_inputs.clear();
230
231        // Construct and append the input declarations of the function.
232        for input in function.input.iter() {
233            let register_string = self.next_register();
234
235            // Track all internal record inputs.
236            if let Type::Composite(comp) = &input.type_ {
237                let program = comp.program.unwrap_or(self.program_id.unwrap().name.name);
238                if let Some(record) =
239                    self.state.symbol_table.lookup_record(&Location::new(program, comp.path.absolute_path().to_vec()))
240                {
241                    if record.external.is_none() || record.external == self.program_id.map(|id| id.name.name) {
242                        self.internal_record_inputs.insert(register_string.clone());
243                    }
244                }
245            }
246
247            let type_string = {
248                self.variable_mapping.insert(input.identifier.name, register_string.clone());
249                // Note that this unwrap is safe because we set the variant at the beginning of the function.
250                let visibility = match (self.variant.unwrap(), input.mode) {
251                    (Variant::AsyncTransition, Mode::None) | (Variant::Transition, Mode::None) => Mode::Private,
252                    (Variant::AsyncFunction, Mode::None) => Mode::Public,
253                    _ => input.mode,
254                };
255                // Futures are displayed differently in the input section. `input r0 as foo.aleo/bar.future;`
256                if matches!(input.type_, Type::Future(_)) {
257                    let location = futures
258                        .next()
259                        .expect("Type checking guarantees we have future locations for each future input");
260                    let [future_name] = location.path.as_slice() else {
261                        panic!("All futures must have a single segment paths since they don't belong to submodules.")
262                    };
263                    format!("{}.aleo/{}.future", location.program, future_name)
264                } else {
265                    self.visit_type_with_visibility(&input.type_, visibility)
266                }
267            };
268
269            writeln!(function_string, "    input {register_string} as {type_string};",).expect(EXPECT_STR);
270        }
271
272        //  Construct and append the function body.
273        let block_string = self.visit_block(&function.block);
274        if matches!(self.variant.unwrap(), Variant::Function | Variant::AsyncFunction)
275            && block_string.lines().all(|line| line.starts_with("    output "))
276        {
277            // There are no real instructions, which is invalid in Aleo, so
278            // add a dummy instruction.
279            function_string.push_str("    assert.eq true true;\n");
280        }
281
282        function_string.push_str(&block_string);
283
284        function_string
285    }
286
287    fn visit_function(&mut self, function: &'a Function) -> String {
288        self.visit_function_with(function, &[])
289    }
290
291    fn visit_constructor(&mut self, constructor: &'a Constructor) -> String {
292        // Initialize the state of `self` with the appropriate values before visiting `constructor`.
293        self.next_register = 0;
294        self.variable_mapping = IndexMap::new();
295        self.variant = Some(Variant::AsyncFunction);
296        // TODO: Figure out a better way to initialize.
297        self.variable_mapping.insert(sym::SelfLower, "self".to_string());
298        self.variable_mapping.insert(sym::block, "block".to_string());
299        self.variable_mapping.insert(sym::network, "network".to_string());
300
301        // Get the upgrade variant.
302        let upgrade_variant = constructor
303            .get_upgrade_variant_with_network(self.state.network)
304            .expect("Type checking should have validated the upgrade variant");
305
306        // Construct the constructor.
307        // If the constructor is one of the standard constructors, use the hardcoded defaults.
308        let constructor = match &upgrade_variant {
309            UpgradeVariant::Admin { address } => snarkvm_admin_constructor(address),
310            UpgradeVariant::Checksum { mapping, key, .. } => {
311                if mapping.program
312                    == self.program_id.expect("Program ID should be set before traversing the program").name.name
313                {
314                    let [mapping_name] = &mapping.path[..] else {
315                        panic!("Mappings are only allowed in the top level program at this stage");
316                    };
317                    snarkvm_checksum_constructor(mapping_name, key)
318                } else {
319                    snarkvm_checksum_constructor(mapping, key)
320                }
321            }
322            UpgradeVariant::Custom => format!("\nconstructor:\n{}\n", self.visit_block(&constructor.block)),
323            UpgradeVariant::NoUpgrade => snarkvm_noupgrade_constructor(),
324        };
325
326        // Check that the constructor is well-formed.
327        if let Err(e) = match self.state.network {
328            NetworkName::MainnetV0 => check_snarkvm_constructor::<MainnetV0>(&constructor),
329            NetworkName::TestnetV0 => check_snarkvm_constructor::<TestnetV0>(&constructor),
330            NetworkName::CanaryV0 => check_snarkvm_constructor::<CanaryV0>(&constructor),
331        } {
332            panic!("Compilation produced an invalid constructor: {e}");
333        };
334
335        // Return the constructor string.
336        constructor
337    }
338
339    fn visit_mapping(&mut self, mapping: &'a Mapping) -> String {
340        // Create the prefix of the mapping string, e.g. `mapping foo:`.
341        let mut mapping_string = format!("\nmapping {}:\n", mapping.identifier);
342
343        // Helper to construct the string associated with the type.
344        let create_type = |type_: &Type| {
345            match type_ {
346                Type::Mapping(_) | Type::Tuple(_) => panic!("Mappings cannot contain mappings or tuples."),
347                Type::Identifier(identifier) => {
348                    // Lookup the type in the composite mapping.
349                    // Note that this unwrap is safe since all struct and records have been added to the composite mapping.
350                    let (is_record, _) = self.composite_mapping.get(&vec![identifier.name]).unwrap();
351                    assert!(!is_record, "Type checking guarantees that mappings cannot contain records.");
352                    self.visit_type_with_visibility(type_, Mode::Public)
353                }
354                type_ => self.visit_type_with_visibility(type_, Mode::Public),
355            }
356        };
357
358        // Create the key string, e.g. `    key as address.public`.
359        writeln!(mapping_string, "    key as {};", create_type(&mapping.key_type)).expect(EXPECT_STR);
360
361        // Create the value string, e.g. `    value as address.public`.
362        writeln!(mapping_string, "    value as {};", create_type(&mapping.value_type)).expect(EXPECT_STR);
363
364        // Add the mapping to the variable mapping.
365        self.global_mapping.insert(mapping.identifier.name, mapping.identifier.to_string());
366
367        mapping_string
368    }
369}