leo_passes/option_lowering/mod.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
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::{Pass, PathResolution, SymbolTable, SymbolTableCreation, TypeChecking, TypeCheckingInput};
61
62use leo_ast::{ArrayType, CompositeType, ProgramReconstructor as _, Type};
63use leo_errors::Result;
64use leo_span::Symbol;
65
66use indexmap::IndexMap;
67use itertools::Itertools;
68
69mod ast;
70
71mod program;
72
73mod visitor;
74use visitor::*;
75
76pub struct OptionLowering;
77
78impl Pass for OptionLowering {
79 type Input = TypeCheckingInput;
80 type Output = ();
81
82 const NAME: &str = "OptionLowering";
83
84 fn do_pass(input: TypeCheckingInput, state: &mut crate::CompilerState) -> Result<Self::Output> {
85 let mut ast = std::mem::take(&mut state.ast);
86 let mut visitor = OptionLoweringVisitor {
87 state,
88 program: Symbol::intern(""),
89 module: vec![],
90 function: None,
91 new_structs: IndexMap::new(),
92 reconstructed_structs: IndexMap::new(),
93 };
94 ast.ast = visitor.reconstruct_program(ast.ast);
95 visitor.state.handler.last_err()?;
96 visitor.state.ast = ast;
97
98 // We need to recreate the symbol table and run type checking again because this pass may introduce new structs
99 // and modify existing ones.
100 visitor.state.symbol_table = SymbolTable::default();
101 PathResolution::do_pass((), state)?;
102 SymbolTableCreation::do_pass((), state)?;
103 TypeChecking::do_pass(input.clone(), state)?;
104
105 Ok(())
106 }
107}
108
109pub fn make_optional_struct_symbol(ty: &Type) -> Symbol {
110 // Step 1: Extract a usable type name
111 fn display_type(ty: &Type) -> String {
112 match ty {
113 Type::Address
114 | Type::Field
115 | Type::Group
116 | Type::Scalar
117 | Type::Signature
118 | Type::Boolean
119 | Type::Integer(..) => format!("{ty}"),
120 Type::Array(ArrayType { element_type, length }) => {
121 format!("[{}; {length}]", display_type(element_type))
122 }
123 Type::Composite(CompositeType { path, .. }) => {
124 format!("::{}", path.absolute_path().iter().format("::"))
125 }
126
127 Type::Tuple(_)
128 | Type::Optional(_)
129 | Type::Mapping(_)
130 | Type::Numeric
131 | Type::Identifier(_)
132 | Type::Future(_)
133 | Type::String
134 | Type::Err
135 | Type::Unit => {
136 panic!("unexpected inner type in optional struct name")
137 }
138 }
139 }
140
141 // Step 3: Build symbol that ends with `?`.
142 Symbol::intern(&format!("\"{}?\"", display_type(ty)))
143}