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::CodeGeneratingVisitor;
18
19use leo_ast::{Composite, Function, Location, Mapping, Member, Mode, Program, ProgramScope, Type, Variant};
20use leo_span::{Symbol, sym};
21
22use indexmap::IndexMap;
23use std::fmt::Write as _;
24
25const EXPECT_STR: &str = "Failed to write code";
26
27impl<'a> CodeGeneratingVisitor<'a> {
28    pub fn visit_program(&mut self, input: &'a Program) -> String {
29        // Accumulate instructions into a program string.
30        let mut program_string = String::new();
31
32        // Print out the dependencies of the program. Already arranged in post order by Retriever module.
33        input.stubs.iter().for_each(|(program_name, _)| {
34            writeln!(program_string, "import {}.aleo;", program_name).expect(EXPECT_STR);
35        });
36
37        // Retrieve the program scope.
38        // Note that type checking guarantees that there is exactly one program scope.
39        let program_scope: &ProgramScope = input.program_scopes.values().next().unwrap();
40
41        self.program_id = Some(program_scope.program_id);
42
43        // Print the program id.
44        writeln!(program_string, "program {};", program_scope.program_id).expect(EXPECT_STR);
45
46        // Get the post-order ordering of the composite data types.
47        // Note that the unwrap is safe since type checking guarantees that the struct dependency graph is acyclic.
48        let order = self.state.struct_graph.post_order().unwrap();
49
50        let this_program = self.program_id.unwrap().name.name;
51
52        let lookup = |name: Symbol| {
53            self.state
54                .symbol_table
55                .lookup_struct(name)
56                .or_else(|| self.state.symbol_table.lookup_record(Location::new(this_program, name)))
57        };
58
59        // Visit each `Struct` or `Record` in the post-ordering and produce an Aleo struct or record.
60        for name in order.into_iter() {
61            if let Some(struct_) = lookup(name) {
62                program_string.push_str(&self.visit_struct_or_record(struct_));
63            }
64        }
65
66        // Visit each mapping in the Leo AST and produce an Aleo mapping declaration.
67        for (_symbol, mapping) in program_scope.mappings.iter() {
68            program_string.push_str(&self.visit_mapping(mapping));
69        }
70
71        // Visit each function in the program scope and produce an Aleo function.
72        // Note that in the function inlining pass, we reorder the functions such that they are in post-order.
73        // In other words, a callee function precedes its caller function in the program scope.
74        for (_symbol, function) in program_scope.functions.iter() {
75            if function.variant != Variant::AsyncFunction {
76                let mut function_string = self.visit_function(function);
77
78                // Attach the associated finalize to async transitions.
79                if function.variant == Variant::AsyncTransition {
80                    // Set state variables.
81                    self.finalize_caller = Some(function.identifier.name);
82                    // Generate code for the associated finalize function.
83                    let finalize = &self
84                        .state
85                        .symbol_table
86                        .lookup_function(Location::new(self.program_id.unwrap().name.name, function.identifier.name))
87                        .unwrap()
88                        .clone()
89                        .finalizer
90                        .unwrap();
91                    // Write the finalize string.
92                    function_string.push_str(&self.visit_function_with(
93                        &program_scope.functions.iter().find(|(name, _f)| name == &finalize.location.name).unwrap().1,
94                        &finalize.future_inputs,
95                    ));
96                }
97
98                program_string.push_str(&function_string);
99            }
100        }
101
102        program_string
103    }
104
105    fn visit_struct_or_record(&mut self, struct_: &'a Composite) -> String {
106        if struct_.is_record { self.visit_record(struct_) } else { self.visit_struct(struct_) }
107    }
108
109    fn visit_struct(&mut self, struct_: &'a Composite) -> String {
110        // Add private symbol to composite types.
111        self.composite_mapping.insert(struct_.identifier.name, (false, String::from("private"))); // todo: private by default here.
112
113        let mut output_string = format!("\nstruct {}:\n", struct_.identifier); // todo: check if this is safe from name conflicts.
114
115        // Construct and append the record variables.
116        for var in struct_.members.iter() {
117            writeln!(output_string, "    {} as {};", var.identifier, Self::visit_type(&var.type_),).expect(EXPECT_STR);
118        }
119
120        output_string
121    }
122
123    fn visit_record(&mut self, record: &'a Composite) -> String {
124        // Add record symbol to composite types.
125        self.composite_mapping.insert(record.identifier.name, (true, "record".into()));
126
127        let mut output_string = format!("\nrecord {}:\n", record.identifier); // todo: check if this is safe from name conflicts.
128
129        let mut members = Vec::with_capacity(record.members.len());
130        let mut member_map: IndexMap<Symbol, Member> =
131            record.members.clone().into_iter().map(|member| (member.identifier.name, member)).collect();
132
133        // Add the owner field to the beginning of the members list.
134        // Note that type checking ensures that the owner field exists.
135        members.push(member_map.shift_remove(&sym::owner).unwrap());
136
137        // Add the remaining fields to the members list.
138        members.extend(member_map.into_iter().map(|(_, member)| member));
139
140        // Construct and append the record variables.
141        for var in members.iter() {
142            let mode = match var.mode {
143                Mode::Constant => "constant",
144                Mode::Public => "public",
145                Mode::None | Mode::Private => "private",
146            };
147            writeln!(
148                output_string,
149                "    {} as {}.{mode};", // todo: CAUTION private record variables only.
150                var.identifier,
151                Self::visit_type(&var.type_)
152            )
153            .expect(EXPECT_STR);
154        }
155
156        output_string
157    }
158
159    fn visit_function_with(&mut self, function: &'a Function, futures: &[Location]) -> String {
160        // Initialize the state of `self` with the appropriate values before visiting `function`.
161        self.next_register = 0;
162        self.variable_mapping = IndexMap::new();
163        self.variant = Some(function.variant);
164        // TODO: Figure out a better way to initialize.
165        self.variable_mapping.insert(sym::SelfLower, "self".to_string());
166        self.variable_mapping.insert(sym::block, "block".to_string());
167        self.variable_mapping.insert(sym::network, "network".to_string());
168        self.current_function = Some(function);
169
170        // Construct the header of the function.
171        // If a function is a program function, generate an Aleo `function`,
172        // if it is a standard function generate an Aleo `closure`,
173        // otherwise, it is an inline function, in which case a function should not be generated.
174        let mut function_string = match function.variant {
175            Variant::Transition | Variant::AsyncTransition => format!("\nfunction {}:\n", function.identifier),
176            Variant::Function => format!("\nclosure {}:\n", function.identifier),
177            Variant::AsyncFunction => format!("\nfinalize {}:\n", self.finalize_caller.unwrap()),
178            Variant::Inline => return String::new(),
179            Variant::Script => panic!("script should not appear in native code"),
180        };
181
182        let mut futures = futures.iter();
183
184        self.internal_record_inputs.clear();
185
186        // Construct and append the input declarations of the function.
187        for input in function.input.iter() {
188            let register_string = self.next_register();
189
190            // Track all internal record inputs.
191            if let Type::Composite(comp) = &input.type_ {
192                let program = comp.program.unwrap_or(self.program_id.unwrap().name.name);
193                if let Some(record) = self.state.symbol_table.lookup_record(Location::new(program, comp.id.name)) {
194                    if record.external.is_none() || record.external == self.program_id.map(|id| id.name.name) {
195                        self.internal_record_inputs.insert(register_string.clone());
196                    }
197                }
198            }
199
200            let type_string = {
201                self.variable_mapping.insert(input.identifier.name, register_string.clone());
202                // Note that this unwrap is safe because we set the variant at the beginning of the function.
203                let visibility = match (self.variant.unwrap(), input.mode) {
204                    (Variant::AsyncTransition, Mode::None) | (Variant::Transition, Mode::None) => Mode::Private,
205                    (Variant::AsyncFunction, Mode::None) => Mode::Public,
206                    _ => input.mode,
207                };
208                // Futures are displayed differently in the input section. `input r0 as foo.aleo/bar.future;`
209                if matches!(input.type_, Type::Future(_)) {
210                    let location = *futures
211                        .next()
212                        .expect("Type checking guarantees we have future locations for each future input");
213                    format!("{}.aleo/{}.future", location.program, location.name)
214                } else {
215                    self.visit_type_with_visibility(&input.type_, visibility)
216                }
217            };
218
219            writeln!(function_string, "    input {register_string} as {type_string};",).expect(EXPECT_STR);
220        }
221
222        //  Construct and append the function body.
223        let block_string = self.visit_block(&function.block);
224        if matches!(self.variant.unwrap(), Variant::Function | Variant::AsyncFunction)
225            && block_string.lines().all(|line| line.starts_with("    output "))
226        {
227            // There are no real instructions, which is invalid in Aleo, so
228            // add a dummy instruction.
229            function_string.push_str("    assert.eq true true;\n");
230        }
231
232        function_string.push_str(&block_string);
233
234        function_string
235    }
236
237    fn visit_function(&mut self, function: &'a Function) -> String {
238        self.visit_function_with(function, &[])
239    }
240
241    fn visit_mapping(&mut self, mapping: &'a Mapping) -> String {
242        // Create the prefix of the mapping string, e.g. `mapping foo:`.
243        let mut mapping_string = format!("\nmapping {}:\n", mapping.identifier);
244
245        // Helper to construct the string associated with the type.
246        let create_type = |type_: &Type| {
247            match type_ {
248                Type::Mapping(_) | Type::Tuple(_) => panic!("Mappings cannot contain mappings or tuples."),
249                Type::Identifier(identifier) => {
250                    // Lookup the type in the composite mapping.
251                    // Note that this unwrap is safe since all struct and records have been added to the composite mapping.
252                    let (is_record, _) = self.composite_mapping.get(&identifier.name).unwrap();
253                    assert!(!is_record, "Type checking guarantees that mappings cannot contain records.");
254                    self.visit_type_with_visibility(type_, Mode::Public)
255                }
256                type_ => self.visit_type_with_visibility(type_, Mode::Public),
257            }
258        };
259
260        // Create the key string, e.g. `    key as address.public`.
261        writeln!(mapping_string, "    key as {};", create_type(&mapping.key_type)).expect(EXPECT_STR);
262
263        // Create the value string, e.g. `    value as address.public`.
264        writeln!(mapping_string, "    value as {};", create_type(&mapping.value_type)).expect(EXPECT_STR);
265
266        // Add the mapping to the variable mapping.
267        self.global_mapping.insert(mapping.identifier.name, mapping.identifier.to_string());
268
269        mapping_string
270    }
271}