leo_passes/code_generation/visitor.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 crate::CompilerState;
18
19use leo_ast::{Function, Program, ProgramId, Variant};
20use leo_span::Symbol;
21
22use snarkvm::prelude::Network;
23
24use indexmap::{IndexMap, IndexSet};
25use itertools::Itertools;
26use std::str::FromStr;
27
28pub struct CodeGeneratingVisitor<'a> {
29 pub state: &'a CompilerState,
30 /// A counter to track the next available register.
31 pub next_register: u64,
32 /// Reference to the current function.
33 pub current_function: Option<&'a Function>,
34 /// Mapping of local variables to registers.
35 /// Because these are local, we can identify them using only a `Symbol` (i.e. a path is not necessary here).
36 pub variable_mapping: IndexMap<Symbol, String>,
37 /// Mapping of composite names to a tuple containing metadata associated with the name.
38 /// The first element of the tuple indicate whether the composite is a record or not.
39 /// The second element of the tuple is a string modifier used for code generation.
40 pub composite_mapping: IndexMap<Vec<Symbol>, (bool, String)>,
41 /// Mapping of global identifiers to their associated names.
42 /// Because we only allow mappings in the top level program scope at this stage, we can identify them using only a
43 /// `Symbol` (i.e. a path is not necessary here currently).
44 pub global_mapping: IndexMap<Symbol, String>,
45 /// The variant of the function we are currently traversing.
46 pub variant: Option<Variant>,
47 /// A reference to program. This is needed to look up external programs.
48 pub program: &'a Program,
49 /// The program ID of the current program.
50 pub program_id: Option<ProgramId>,
51 /// A reference to the finalize caller.
52 /// Because `async transition`s can only appear in the top level program scope at this stage,
53 /// it's safe to keep this a `Symbol` (i.e. a path is not necessary).
54 pub finalize_caller: Option<Symbol>,
55 /// A counter to track the next available label.
56 pub next_label: u64,
57 /// The depth of the current conditional block.
58 pub conditional_depth: u64,
59 /// Internal record input registers of the current function.
60 /// This is necessary as if we output them, we need to clone them.
61 pub internal_record_inputs: IndexSet<String>,
62}
63
64/// This function checks whether the constructor is well-formed.
65/// If an upgrade configuration is provided, it checks that the constructor matches the configuration.
66pub(crate) fn check_snarkvm_constructor<N: Network>(actual: &str) -> snarkvm::prelude::Result<()> {
67 use snarkvm::synthesizer::program::Constructor as SVMConstructor;
68 // Parse the constructor as a snarkVM constructor.
69 SVMConstructor::<N>::from_str(actual.trim())?;
70
71 Ok(())
72}
73
74impl CodeGeneratingVisitor<'_> {
75 pub(crate) fn next_register(&mut self) -> String {
76 self.next_register += 1;
77 format!("r{}", self.next_register - 1)
78 }
79
80 /// Converts a path into a legal Aleo identifier, if possible.
81 ///
82 /// # Behavior
83 /// - If the path is a single valid Leo identifier (`[a-zA-Z][a-zA-Z0-9_]*`), it's returned as-is.
84 /// - If the last segment matches `Name::[args]` (e.g. `Vec3::[3, 4]`), it's converted to a legal identifier using hashing.
85 /// - If the path has multiple segments, and all segments are valid identifiers except the last one (which may be `Name::[args]`),
86 /// it's hashed using the last segment as base.
87 /// - Returns `None` if:
88 /// - The path is empty
89 /// - Any segment other than the last is not a valid identifier
90 /// - The last segment is invalid and not legalizable
91 ///
92 /// # Parameters
93 /// - `path`: A slice of `Symbol`s representing a path to an item.
94 ///
95 /// # Returns
96 /// - `Some(String)`: A valid Leo identifier.
97 /// - `None`: If the path is invalid or cannot be legalized.
98 pub(crate) fn legalize_path(path: &[Symbol]) -> Option<String> {
99 /// Checks if a string is a legal Leo identifier: [a-zA-Z][a-zA-Z0-9_]*
100 fn is_legal_identifier(s: &str) -> bool {
101 let mut chars = s.chars();
102 matches!(chars.next(), Some(c) if c.is_ascii_alphabetic())
103 && chars.all(|c| c.is_ascii_alphanumeric() || c == '_')
104 }
105
106 /// Generates a hashed Leo identifier from the full path, using the given base segment.
107 fn generate_hashed_name(path: &[Symbol], base: &str) -> String {
108 use base62::encode;
109 use sha2::{Digest, Sha256};
110 use std::fmt::Write;
111
112 let full_path = path.iter().format("::").to_string();
113
114 let mut hasher = Sha256::new();
115 hasher.update(full_path.as_bytes());
116 let hash = hasher.finalize();
117
118 let hash_number = u64::from_be_bytes(hash[..8].try_into().unwrap());
119 let hash_base62 = encode(hash_number);
120
121 let fixed_suffix_len = 2 + hash_base62.len(); // "__" + hash
122 let max_ident_len = 31 - fixed_suffix_len;
123
124 let mut result = String::new();
125 write!(&mut result, "{base:.max_ident_len$}__{hash_base62}").unwrap();
126 result
127 }
128
129 let last = path.last()?.to_string();
130
131 // Validate all segments except the last
132 if path.len() > 1 && !path[..path.len() - 1].iter().all(|sym| is_legal_identifier(&sym.to_string())) {
133 return None;
134 }
135
136 // === Case 1: Single, legal identifier ===
137 if path.len() == 1 && is_legal_identifier(&last) {
138 return Some(last);
139 }
140
141 // === Case 2: Matches special form like `path::to::Name::[3, 4]` ===
142 let re = regex::Regex::new(r#"^([a-zA-Z_][\w]*)(?:::\[.*?\])?$"#).unwrap();
143
144 if let Some(captures) = re.captures(&last) {
145 let ident = captures.get(1)?.as_str();
146
147 // The produced name here will be of the form: `<last>__AYMqiUeJeQN`.
148 return Some(generate_hashed_name(path, ident));
149 }
150
151 // === Case 3: Matches special form like `path::to::Name?` (last always ends with `?`) ===
152 if last.ends_with("?\"") {
153 // Because the last segment of `path` always ends with `?` in case 3, we can guarantee
154 // that there will be no conflicts with case 2 (which doesn't allow `?` anywhere).
155 //
156 // The produced name here will be of the form: `Optional__JZCpIGdQvEZ`.
157 // The suffix after the `__` cannot conflict with the suffix in case 2 because of the `?`
158 return Some(generate_hashed_name(path, "Optional"));
159 }
160
161 // Last segment is neither legal nor matches special pattern
162 None
163 }
164}