leo_passes/option_lowering/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::*;
20use leo_span::{Span, Symbol};
21
22use indexmap::IndexMap;
23
24pub struct OptionLoweringVisitor<'a> {
25 pub state: &'a mut CompilerState,
26 // The name of the current program scope
27 pub program: Symbol,
28 // The path to the current module. This should be an empty vector for the root.
29 pub module: Vec<Symbol>,
30 // The name of the current function, if we're inside one.
31 pub function: Option<Symbol>,
32 // The newly created structs. Each struct correspond to a converted optional type. All these
33 // structs are to be inserted in the program scope.
34 pub new_structs: IndexMap<Symbol, Composite>,
35 // The reconstructed structs. These are the new versions of the existing structs in the program.
36 pub reconstructed_structs: IndexMap<Vec<Symbol>, Composite>,
37}
38
39impl OptionLoweringVisitor<'_> {
40 /// Enter module scope with path `module`, execute `func`, and then return to the parent module.
41 pub fn in_module_scope<T>(&mut self, module: &[Symbol], func: impl FnOnce(&mut Self) -> T) -> T {
42 let parent_module = self.module.clone();
43 self.module = module.to_vec();
44 let result = func(self);
45 self.module = parent_module;
46 result
47 }
48
49 /// Wraps an expression of a given type in an `Optional<T>`-like struct representing `Some(value)`.
50 ///
51 /// This function creates a struct expression that encodes an optional value with `is_some = true`
52 /// and the provided expression as the `val` field. It also ensures that the type is fully
53 /// reconstructed, which guarantees that all necessary struct definitions are available and registered.
54 ///
55 /// # Parameters
56 /// - `expr`: The expression to wrap as the value of the optional.
57 /// - `ty`: The type of the expression.
58 ///
59 /// # Returns
60 /// - An `Expression` representing the constructed `Optional<T>` struct instance.
61 pub fn wrap_optional_value(&mut self, expr: Expression, ty: Type) -> Expression {
62 let is_some_expr = Expression::Literal(Literal {
63 span: Span::default(),
64 id: self.state.node_builder.next_id(),
65 variant: LiteralVariant::Boolean(true),
66 });
67
68 // Fully lower the type before proceeding. This also ensures that all required structs
69 // are actually registered in `self.new_structs`.
70 let lowered_inner_type = self.reconstruct_type(ty).0;
71
72 // Create or get an optional wrapper struct for `lowered_inner_type`
73 let struct_name = self.insert_optional_wrapper_struct(&lowered_inner_type);
74
75 let struct_expr = StructExpression {
76 path: Path::from(Identifier::new(struct_name, self.state.node_builder.next_id())).into_absolute(),
77 const_arguments: vec![],
78 members: vec![
79 StructVariableInitializer {
80 identifier: Identifier::new(Symbol::intern("is_some"), self.state.node_builder.next_id()),
81 expression: Some(is_some_expr),
82 span: Span::default(),
83 id: self.state.node_builder.next_id(),
84 },
85 StructVariableInitializer {
86 identifier: Identifier::new(Symbol::intern("val"), self.state.node_builder.next_id()),
87 expression: Some(expr),
88 span: Span::default(),
89 id: self.state.node_builder.next_id(),
90 },
91 ],
92 span: Span::default(),
93 id: self.state.node_builder.next_id(),
94 };
95
96 struct_expr.into()
97 }
98
99 /// Constructs an `Optional<T>`-like struct representing `None` for a given inner type.
100 ///
101 /// The returned struct expression sets `is_some = false`, and provides a zero value for the `val`
102 /// field, where "zero" is defined according to the type:
103 /// numeric types use literal zero, structs are recursively zero-initialized, etc.
104 ///
105 /// This function assumes that all required struct types are already reconstructed and registered.
106 ///
107 /// # Parameters
108 /// - `inner_ty`: The type `T` inside the `Optional<T>`.
109 ///
110 /// # Returns
111 /// - An `Expression` representing the constructed `Optional<T>` struct instance with `None`.
112 pub fn wrap_none(&mut self, inner_ty: &Type) -> Expression {
113 let is_some_expr = Expression::Literal(Literal {
114 span: Span::default(),
115 id: self.state.node_builder.next_id(),
116 variant: LiteralVariant::Boolean(false),
117 });
118
119 // Fully lower the type before proceeding. This also ensures that all required structs
120 // are actually registered in `self.new_structs`.
121 let lowered_inner_type = self.reconstruct_type(inner_ty.clone()).0;
122
123 // Even though the `val` field of the struct will not be used as long as `is_some` is
124 // `false`, we still have to set it to something. We choose "zero", whatever "zero" means
125 // for each type.
126
127 // Instead of relying on the symbol table (which does not get updated in this pass), we rely on the set of
128 // reconstructed structs which is produced for all program scopes and all modules before doing anything else.
129 let reconstructed_structs = &self.reconstructed_structs;
130 let struct_lookup = |sym: &[Symbol]| {
131 reconstructed_structs
132 .get(sym) // check the new version of existing structs
133 .or_else(|| self.new_structs.get(sym.last().unwrap())) // check the newly produced structs
134 .expect("must exist by construction")
135 .members
136 .iter()
137 .map(|mem| (mem.identifier.name, mem.type_.clone()))
138 .collect()
139 };
140
141 let zero_val_expr =
142 Expression::zero(&lowered_inner_type, Span::default(), &self.state.node_builder, &struct_lookup).expect("");
143
144 // Create or get an optional wrapper struct for `lowered_inner_type`
145 let struct_name = self.insert_optional_wrapper_struct(&lowered_inner_type);
146
147 let struct_expr = StructExpression {
148 path: Path::from(Identifier::new(struct_name, self.state.node_builder.next_id())).into_absolute(),
149 const_arguments: vec![],
150 members: vec![
151 StructVariableInitializer {
152 identifier: Identifier::new(Symbol::intern("is_some"), self.state.node_builder.next_id()),
153 expression: Some(is_some_expr.clone()),
154 span: Span::default(),
155 id: self.state.node_builder.next_id(),
156 },
157 StructVariableInitializer {
158 identifier: Identifier::new(Symbol::intern("val"), self.state.node_builder.next_id()),
159 expression: Some(zero_val_expr.clone()),
160 span: Span::default(),
161 id: self.state.node_builder.next_id(),
162 },
163 ],
164 span: Span::default(),
165 id: self.state.node_builder.next_id(),
166 };
167
168 struct_expr.into()
169 }
170
171 /// Inserts (or reuses) a compiler-generated struct representing `Optional<T>`.
172 ///
173 /// The struct has two fields:
174 /// - `is_some: bool` — indicates whether the value is present.
175 /// - `val: T` — the wrapped value.
176 ///
177 /// If the struct for this type already exists, it’s reused; otherwise, a new one is created.
178 /// Returns the `Symbol` for the struct name.
179 pub fn insert_optional_wrapper_struct(&mut self, ty: &Type) -> Symbol {
180 let struct_name = crate::make_optional_struct_symbol(ty);
181
182 self.new_structs.entry(struct_name).or_insert_with(|| Composite {
183 identifier: Identifier::new(struct_name, self.state.node_builder.next_id()),
184 const_parameters: vec![], // this is not a generic struct
185 members: vec![
186 Member {
187 mode: Mode::None,
188 identifier: Identifier::new(Symbol::intern("is_some"), self.state.node_builder.next_id()),
189 type_: Type::Boolean,
190 span: Span::default(),
191 id: self.state.node_builder.next_id(),
192 },
193 Member {
194 mode: Mode::None,
195 identifier: Identifier::new(Symbol::intern("val"), self.state.node_builder.next_id()),
196 type_: ty.clone(),
197 span: Span::default(),
198 id: self.state.node_builder.next_id(),
199 },
200 ],
201 external: None,
202 is_record: false,
203 span: Span::default(),
204 id: self.state.node_builder.next_id(),
205 });
206
207 struct_name
208 }
209}