leo_passes/option_lowering/mod.rs
1// Copyright (C) 2019-2026 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
17//! Performs lowering of `optional` types (`T?`) and `optional` expressions within a `ProgramScope`.
18//!
19//! This pass rewrites all `optional` types into explicit struct representations with two fields:
20//! - `is_some: bool` — indicating whether the value is present,
21//! - `val: T` — holding the underlying value (or the "zero" value of `T` when `is_some` is `false`).
22//!
23//! All literals, variables, function parameters, and return values involving `optional` types are
24//! transformed into this struct representation. Nested structures (e.g., arrays, tuples, or user-defined
25//! structs containing `optional` types) are lowered recursively.
26//!
27//! ### Example
28//!
29//! ```leo
30//! let x: u8? = 42u8;
31//! ```
32//!
33//! is lowered to:
34//!
35//! ```leo
36//! let x: "u8?" = "u8?" { is_some: true, val: 42u8 };
37//! ```
38//!
39//! When a value is `none`, the `is_some` field is set to `false` and `val` is initialized
40//! with the zero value of the underlying type (`0u8`, `false`, `0field`, etc.).
41//!
42//! ### Recursive Lowering Example
43//!
44//! ```leo
45//! let arr: [u64?; 2] = [1u64, none];
46//! ```
47//!
48//! is lowered to:
49//!
50//! ```leo
51//! let arr: ["u64?"; 2] = [
52//! "u64?" { is_some: true, val: 1u64 },
53//! "u64?" { is_some: false, val: 0u64 },
54//! ];
55//! ```
56//!
57//! After this pass, no `T?` types remain in the program: all optional values are represented explicitly
58//! as structs with `is_some` and `val` fields.
59
60use crate::{
61 GlobalItemsCollection,
62 GlobalVarsCollection,
63 Pass,
64 PathResolution,
65 SymbolTable,
66 TypeChecking,
67 TypeCheckingInput,
68};
69
70use leo_ast::{ArrayType, CompositeType, ProgramReconstructor as _, Type};
71use leo_errors::Result;
72use leo_span::Symbol;
73
74use indexmap::IndexMap;
75use itertools::Itertools;
76
77mod ast;
78
79mod program;
80
81mod visitor;
82use visitor::*;
83
84pub struct OptionLowering;
85
86impl Pass for OptionLowering {
87 type Input = TypeCheckingInput;
88 type Output = ();
89
90 const NAME: &str = "OptionLowering";
91
92 fn do_pass(input: TypeCheckingInput, state: &mut crate::CompilerState) -> Result<Self::Output> {
93 let ast = std::mem::take(&mut state.ast);
94 let mut visitor = OptionLoweringVisitor {
95 state,
96 program: Symbol::intern(""),
97 module: vec![],
98 function: None,
99 new_structs: IndexMap::new(),
100 reconstructed_composites: IndexMap::new(),
101 };
102
103 let ast = ast.map(
104 |program| visitor.reconstruct_program(program),
105 |library| library, // no-op for libraries
106 );
107
108 visitor.state.handler.last_err()?;
109 visitor.state.ast = ast;
110
111 // We need to recreate the symbol table and run type checking again because this pass may introduce new structs
112 // and modify existing ones.
113 visitor.state.symbol_table = SymbolTable::default();
114 GlobalVarsCollection::do_pass((), state)?;
115 PathResolution::do_pass((), state)?;
116 GlobalItemsCollection::do_pass((), state)?;
117 TypeChecking::do_pass(input.clone(), state)?;
118
119 Ok(())
120 }
121}
122
123pub fn make_optional_struct_symbol(ty: &Type) -> Symbol {
124 // Step 1: Extract a usable type name
125 fn display_type(ty: &Type) -> String {
126 match ty {
127 Type::Address
128 | Type::Field
129 | Type::Group
130 | Type::Scalar
131 | Type::Signature
132 | Type::Boolean
133 | Type::Integer(..) => format!("{ty}"),
134 Type::Array(ArrayType { element_type, length }) => {
135 format!("[{}; {length}]", display_type(element_type))
136 }
137 Type::Composite(CompositeType { path, .. }) => {
138 format!("::{}", path.expect_global_location().path.iter().format("::"))
139 }
140
141 Type::Tuple(_)
142 | Type::Optional(_)
143 | Type::Mapping(_)
144 | Type::Numeric
145 | Type::Ident(_)
146 | Type::Future(_)
147 | Type::Vector(_)
148 | Type::String
149 | Type::Identifier
150 | Type::DynRecord
151 | Type::Err
152 | Type::Unit => {
153 panic!("unexpected inner type in optional struct name")
154 }
155 }
156 }
157
158 // Step 3: Build symbol that ends with `?`.
159 Symbol::intern(&format!("\"{}?\"", display_type(ty)))
160}