Skip to main content

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}