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                    && (record.external.is_none() || record.external == self.program_id.map(|id| id.name.name))
241                {
242                    self.internal_record_inputs.insert(register_string.clone());
243                }
244            }
245
246            let type_string = {
247                self.variable_mapping.insert(input.identifier.name, register_string.clone());
248                // Note that this unwrap is safe because we set the variant at the beginning of the function.
249                let visibility = match (self.variant.unwrap(), input.mode) {
250                    (Variant::AsyncTransition, Mode::None) | (Variant::Transition, Mode::None) => Mode::Private,
251                    (Variant::AsyncFunction, Mode::None) => Mode::Public,
252                    _ => input.mode,
253                };
254                // Futures are displayed differently in the input section. `input r0 as foo.aleo/bar.future;`
255                if matches!(input.type_, Type::Future(_)) {
256                    let location = futures
257                        .next()
258                        .expect("Type checking guarantees we have future locations for each future input");
259                    let [future_name] = location.path.as_slice() else {
260                        panic!("All futures must have a single segment paths since they don't belong to submodules.")
261                    };
262                    format!("{}.aleo/{}.future", location.program, future_name)
263                } else {
264                    self.visit_type_with_visibility(&input.type_, visibility)
265                }
266            };
267
268            writeln!(function_string, "    input {register_string} as {type_string};",).expect(EXPECT_STR);
269        }
270
271        //  Construct and append the function body.
272        let block_string = self.visit_block(&function.block);
273        if matches!(self.variant.unwrap(), Variant::Function | Variant::AsyncFunction)
274            && block_string.lines().all(|line| line.starts_with("    output "))
275        {
276            // There are no real instructions, which is invalid in Aleo, so
277            // add a dummy instruction.
278            function_string.push_str("    assert.eq true true;\n");
279        }
280
281        function_string.push_str(&block_string);
282
283        function_string
284    }
285
286    fn visit_function(&mut self, function: &'a Function) -> String {
287        self.visit_function_with(function, &[])
288    }
289
290    fn visit_constructor(&mut self, constructor: &'a Constructor) -> String {
291        // Initialize the state of `self` with the appropriate values before visiting `constructor`.
292        self.next_register = 0;
293        self.variable_mapping = IndexMap::new();
294        self.variant = Some(Variant::AsyncFunction);
295        // TODO: Figure out a better way to initialize.
296        self.variable_mapping.insert(sym::SelfLower, "self".to_string());
297        self.variable_mapping.insert(sym::block, "block".to_string());
298        self.variable_mapping.insert(sym::network, "network".to_string());
299
300        // Get the upgrade variant.
301        let upgrade_variant = constructor
302            .get_upgrade_variant_with_network(self.state.network)
303            .expect("Type checking should have validated the upgrade variant");
304
305        // Construct the constructor.
306        // If the constructor is one of the standard constructors, use the hardcoded defaults.
307        let constructor = match &upgrade_variant {
308            UpgradeVariant::Admin { address } => snarkvm_admin_constructor(address),
309            UpgradeVariant::Checksum { mapping, key, .. } => {
310                if mapping.program
311                    == self.program_id.expect("Program ID should be set before traversing the program").name.name
312                {
313                    let [mapping_name] = &mapping.path[..] else {
314                        panic!("Mappings are only allowed in the top level program at this stage");
315                    };
316                    snarkvm_checksum_constructor(mapping_name, key)
317                } else {
318                    snarkvm_checksum_constructor(mapping, key)
319                }
320            }
321            UpgradeVariant::Custom => format!("\nconstructor:\n{}\n", self.visit_block(&constructor.block)),
322            UpgradeVariant::NoUpgrade => snarkvm_noupgrade_constructor(),
323        };
324
325        // Check that the constructor is well-formed.
326        if let Err(e) = match self.state.network {
327            NetworkName::MainnetV0 => check_snarkvm_constructor::<MainnetV0>(&constructor),
328            NetworkName::TestnetV0 => check_snarkvm_constructor::<TestnetV0>(&constructor),
329            NetworkName::CanaryV0 => check_snarkvm_constructor::<CanaryV0>(&constructor),
330        } {
331            panic!("Compilation produced an invalid constructor: {e}");
332        };
333
334        // Return the constructor string.
335        constructor
336    }
337
338    fn visit_mapping(&mut self, mapping: &'a Mapping) -> String {
339        let legalized_mapping_name = Self::legalize_path(&[mapping.identifier.name]);
340        // Create the prefix of the mapping string, e.g. `mapping foo:`.
341        let mut mapping_string = format!(
342            "\nmapping {}:\n",
343            legalized_mapping_name
344                .clone()
345                .unwrap_or_else(|| panic!("path format cannot be legalized at this point: {}", mapping.identifier))
346        );
347
348        // Helper to construct the string associated with the type.
349        let create_type = |type_: &Type| {
350            match type_ {
351                Type::Mapping(_) | Type::Tuple(_) => panic!("Mappings cannot contain mappings or tuples."),
352                Type::Identifier(identifier) => {
353                    // Lookup the type in the composite mapping.
354                    // Note that this unwrap is safe since all struct and records have been added to the composite mapping.
355                    let (is_record, _) = self.composite_mapping.get(&vec![identifier.name]).unwrap();
356                    assert!(!is_record, "Type checking guarantees that mappings cannot contain records.");
357                    self.visit_type_with_visibility(type_, Mode::Public)
358                }
359                type_ => self.visit_type_with_visibility(type_, Mode::Public),
360            }
361        };
362
363        // Create the key string, e.g. `    key as address.public`.
364        writeln!(mapping_string, "    key as {};", create_type(&mapping.key_type)).expect(EXPECT_STR);
365
366        // Create the value string, e.g. `    value as address.public`.
367        writeln!(mapping_string, "    value as {};", create_type(&mapping.value_type)).expect(EXPECT_STR);
368
369        // Add the mapping to the variable mapping.
370        self.global_mapping.insert(
371            mapping.identifier.name,
372            legalized_mapping_name
373                .unwrap_or_else(|| panic!("path format cannot be legalized at this point: {}", mapping.identifier)),
374        );
375
376        mapping_string
377    }
378}