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}