Skip to main content

leo_ast/types/
type_.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
17use crate::{
18    ArrayType,
19    CompositeType,
20    FutureType,
21    Identifier,
22    IntegerType,
23    Location,
24    MappingType,
25    OptionalType,
26    Path,
27    ProgramId,
28    TupleType,
29    VectorType,
30};
31
32use itertools::Itertools;
33use serde::{Deserialize, Serialize};
34use snarkvm::prelude::{
35    LiteralType,
36    Network,
37    PlaintextType,
38    PlaintextType::{Array, ExternalStruct, Literal, Struct},
39};
40use std::fmt;
41
42/// Explicit type used for defining a variable or expression type
43#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
44pub enum Type {
45    /// The `address` type.
46    Address,
47    /// The array type.
48    Array(ArrayType),
49    /// The `bool` type.
50    Boolean,
51    /// The composite type.
52    Composite(CompositeType),
53    /// The `field` type.
54    Field,
55    /// The `future` type.
56    Future(FutureType),
57    /// The `group` type.
58    Group,
59    /// The `identifier` type.
60    Identifier,
61    /// The `dyn record` type.
62    DynRecord,
63    /// A reference to a built in type.
64    Ident(Identifier),
65    /// An integer type.
66    Integer(IntegerType),
67    /// A mapping type.
68    Mapping(MappingType),
69    /// A nullable type.
70    Optional(OptionalType),
71    /// The `scalar` type.
72    Scalar,
73    /// The `signature` type.
74    Signature,
75    /// The `string` type.
76    String,
77    /// A static tuple of at least one type.
78    Tuple(TupleType),
79    /// The vector type.
80    Vector(VectorType),
81    /// Numeric type which should be resolved to `Field`, `Group`, `Integer(_)`, or `Scalar`.
82    Numeric,
83    /// The `unit` type.
84    Unit,
85    /// Placeholder for a type that could not be resolved or was not well-formed.
86    /// Will eventually lead to a compile error.
87    #[default]
88    Err,
89}
90
91impl Type {
92    /// Are the types considered equal as far as the Leo user is concerned?
93    ///
94    /// In particular, any comparison involving an `Err` is `true`, and Futures which aren't explicit compare equal to
95    /// other Futures.
96    ///
97    /// An array with an undetermined length (e.g., one that depends on a `const`) is considered equal to other arrays
98    /// if their element types match. This allows const propagation to potentially resolve the length before type
99    /// checking is performed again.
100    ///
101    /// Composite types are considered equal if their names and resolved program names match. If either side still has
102    /// const generic arguments, they are treated as equal unconditionally since monomorphization and other passes of
103    /// type-checking will handle mismatches later.
104    pub fn types_equivalent(&self, other: &Type) -> bool {
105        match (self, other) {
106            (Type::Err, _)
107            | (_, Type::Err)
108            | (Type::Address, Type::Address)
109            | (Type::Boolean, Type::Boolean)
110            | (Type::Field, Type::Field)
111            | (Type::Group, Type::Group)
112            | (Type::Scalar, Type::Scalar)
113            | (Type::Signature, Type::Signature)
114            | (Type::String, Type::String)
115            | (Type::Identifier, Type::Identifier)
116            | (Type::DynRecord, Type::DynRecord)
117            | (Type::Unit, Type::Unit) => true,
118            (Type::Array(left), Type::Array(right)) => {
119                (match (left.length.as_u32(), right.length.as_u32()) {
120                    (Some(l1), Some(l2)) => l1 == l2,
121                    _ => {
122                        // An array with an undetermined length (e.g., one that depends on a `const`) is considered
123                        // equal to other arrays because their lengths _may_ eventually be proven equal.
124                        true
125                    }
126                }) && left.element_type().types_equivalent(right.element_type())
127            }
128            (Type::Ident(left), Type::Ident(right)) => left.name == right.name,
129            (Type::Integer(left), Type::Integer(right)) => left == right,
130            (Type::Mapping(left), Type::Mapping(right)) => {
131                left.key.types_equivalent(&right.key) && left.value.types_equivalent(&right.value)
132            }
133            (Type::Optional(left), Type::Optional(right)) => left.inner.types_equivalent(&right.inner),
134            (Type::Tuple(left), Type::Tuple(right)) if left.length() == right.length() => left
135                .elements()
136                .iter()
137                .zip_eq(right.elements().iter())
138                .all(|(left_type, right_type)| left_type.types_equivalent(right_type)),
139            (Type::Vector(left), Type::Vector(right)) => left.element_type.types_equivalent(&right.element_type),
140            (Type::Composite(left), Type::Composite(right)) => {
141                // If either composite still has const generic arguments, treat them as equal;
142                // monomorphization and a subsequent type-checking pass will handle mismatches.
143                if !left.const_arguments.is_empty() || !right.const_arguments.is_empty() {
144                    return true;
145                }
146
147                // Two composite types are the same if their global locations match.
148                match (&left.path.try_global_location(), &right.path.try_global_location()) {
149                    (Some(l), Some(r)) => l == r,
150                    _ => false,
151                }
152            }
153
154            (Type::Future(left), Type::Future(right)) if !left.is_explicit || !right.is_explicit => true,
155            (Type::Future(left), Type::Future(right)) if left.inputs.len() == right.inputs.len() => left
156                .inputs()
157                .iter()
158                .zip_eq(right.inputs().iter())
159                .all(|(left_type, right_type)| left_type.types_equivalent(right_type)),
160            _ => false,
161        }
162    }
163
164    pub fn from_snarkvm<N: Network>(t: &PlaintextType<N>, program_id: ProgramId) -> Self {
165        match t {
166            Literal(lit) => (*lit).into(),
167            Struct(s) => Type::Composite(CompositeType {
168                path: {
169                    let ident = Identifier::from(s);
170                    Path::from(ident).to_global(Location::new(program_id.as_symbol(), vec![ident.name]))
171                },
172                const_arguments: Vec::new(),
173            }),
174            ExternalStruct(l) => Type::Composite(CompositeType {
175                path: {
176                    let external_program = ProgramId::from(l.program_id());
177                    let name = Identifier::from(l.resource());
178                    Path::from(name)
179                        .with_user_program(external_program)
180                        .to_global(Location::new(external_program.as_symbol(), vec![name.name]))
181                },
182                const_arguments: Vec::new(),
183            }),
184            Array(array) => Type::Array(ArrayType::from_snarkvm(array, program_id)),
185        }
186    }
187
188    // Attempts to convert `self` to a snarkVM `PlaintextType`.
189    pub fn to_snarkvm<N: Network>(&self) -> anyhow::Result<PlaintextType<N>> {
190        match self {
191            Type::Address => Ok(PlaintextType::Literal(snarkvm::prelude::LiteralType::Address)),
192            Type::Boolean => Ok(PlaintextType::Literal(snarkvm::prelude::LiteralType::Boolean)),
193            Type::Field => Ok(PlaintextType::Literal(snarkvm::prelude::LiteralType::Field)),
194            Type::Group => Ok(PlaintextType::Literal(snarkvm::prelude::LiteralType::Group)),
195            Type::Integer(int_type) => match int_type {
196                IntegerType::U8 => Ok(PlaintextType::Literal(snarkvm::prelude::LiteralType::U8)),
197                IntegerType::U16 => Ok(PlaintextType::Literal(snarkvm::prelude::LiteralType::U16)),
198                IntegerType::U32 => Ok(PlaintextType::Literal(snarkvm::prelude::LiteralType::U32)),
199                IntegerType::U64 => Ok(PlaintextType::Literal(snarkvm::prelude::LiteralType::U64)),
200                IntegerType::U128 => Ok(PlaintextType::Literal(snarkvm::prelude::LiteralType::U128)),
201                IntegerType::I8 => Ok(PlaintextType::Literal(snarkvm::prelude::LiteralType::I8)),
202                IntegerType::I16 => Ok(PlaintextType::Literal(snarkvm::prelude::LiteralType::I16)),
203                IntegerType::I32 => Ok(PlaintextType::Literal(snarkvm::prelude::LiteralType::I32)),
204                IntegerType::I64 => Ok(PlaintextType::Literal(snarkvm::prelude::LiteralType::I64)),
205                IntegerType::I128 => Ok(PlaintextType::Literal(snarkvm::prelude::LiteralType::I128)),
206            },
207            Type::Scalar => Ok(PlaintextType::Literal(snarkvm::prelude::LiteralType::Scalar)),
208            Type::Signature => Ok(PlaintextType::Literal(snarkvm::prelude::LiteralType::Signature)),
209            Type::Array(array_type) => Ok(PlaintextType::<N>::Array(array_type.to_snarkvm()?)),
210            _ => anyhow::bail!("Converting from type {self} to snarkVM type is not supported"),
211        }
212    }
213
214    // A helper function to get the size in bits of the input type.
215    pub fn size_in_bits<N: Network, F0, F1>(
216        &self,
217        is_raw: bool,
218        get_structs: F0,
219        get_external_structs: F1,
220    ) -> anyhow::Result<usize>
221    where
222        F0: Fn(&snarkvm::prelude::Identifier<N>) -> anyhow::Result<snarkvm::prelude::StructType<N>>,
223        F1: Fn(&snarkvm::prelude::Locator<N>) -> anyhow::Result<snarkvm::prelude::StructType<N>>,
224    {
225        match is_raw {
226            false => self.to_snarkvm::<N>()?.size_in_bits(&get_structs, &get_external_structs),
227            true => self.to_snarkvm::<N>()?.size_in_bits_raw(&get_structs, &get_external_structs),
228        }
229    }
230
231    /// Determines whether `self` can be coerced to the `expected` type.
232    ///
233    /// This method checks if the current type can be implicitly coerced to the expected type
234    /// according to specific rules:
235    /// - `Optional<T>` can be coerced to `Optional<T>`.
236    /// - `T` can be coerced to `Optional<T>`.
237    /// - Arrays `[T; N]` can be coerced to `[Optional<T>; N]` if lengths match or are unknown,
238    ///   and element types are coercible.
239    /// - Falls back to an equality check for other types.
240    ///
241    /// # Arguments
242    /// * `expected` - The type to which `self` is being coerced.
243    ///
244    /// # Returns
245    /// `true` if coercion is allowed; `false` otherwise.
246    pub fn can_coerce_to(&self, expected: &Type) -> bool {
247        use Type::*;
248
249        match (self, expected) {
250            // Allow Optional<T> → Optional<T>
251            (Optional(actual_opt), Optional(expected_opt)) => actual_opt.inner.can_coerce_to(&expected_opt.inner),
252
253            // Allow T → Optional<T>
254            (a, Optional(opt)) => a.can_coerce_to(&opt.inner),
255
256            // Allow [T; N] → [Optional<T>; N]
257            (Array(a_arr), Array(e_arr)) => {
258                let lengths_equal = match (a_arr.length.as_u32(), e_arr.length.as_u32()) {
259                    (Some(l1), Some(l2)) => l1 == l2,
260                    _ => true,
261                };
262
263                lengths_equal && a_arr.element_type().can_coerce_to(e_arr.element_type())
264            }
265
266            // Fallback: check for exact match
267            _ => self.types_equivalent(expected),
268        }
269    }
270
271    pub fn is_optional(&self) -> bool {
272        matches!(self, Self::Optional(_))
273    }
274
275    pub fn is_vector(&self) -> bool {
276        matches!(self, Self::Vector(_))
277    }
278
279    pub fn is_mapping(&self) -> bool {
280        matches!(self, Self::Mapping(_))
281    }
282
283    pub fn to_optional(&self) -> Type {
284        Type::Optional(OptionalType { inner: Box::new(self.clone()) })
285    }
286
287    pub fn is_empty(&self) -> bool {
288        match self {
289            Type::Unit => true,
290            Type::Array(array_type) => {
291                if let Some(length) = array_type.length.as_u32() {
292                    length == 0
293                } else {
294                    false
295                }
296            }
297            _ => false,
298        }
299    }
300}
301
302impl From<LiteralType> for Type {
303    fn from(value: LiteralType) -> Self {
304        match value {
305            LiteralType::Identifier => Type::Identifier,
306            LiteralType::Address => Type::Address,
307            LiteralType::Boolean => Type::Boolean,
308            LiteralType::Field => Type::Field,
309            LiteralType::Group => Type::Group,
310            LiteralType::U8 => Type::Integer(IntegerType::U8),
311            LiteralType::U16 => Type::Integer(IntegerType::U16),
312            LiteralType::U32 => Type::Integer(IntegerType::U32),
313            LiteralType::U64 => Type::Integer(IntegerType::U64),
314            LiteralType::U128 => Type::Integer(IntegerType::U128),
315            LiteralType::I8 => Type::Integer(IntegerType::I8),
316            LiteralType::I16 => Type::Integer(IntegerType::I16),
317            LiteralType::I32 => Type::Integer(IntegerType::I32),
318            LiteralType::I64 => Type::Integer(IntegerType::I64),
319            LiteralType::I128 => Type::Integer(IntegerType::I128),
320            LiteralType::Scalar => Type::Scalar,
321            LiteralType::Signature => Type::Signature,
322            LiteralType::String => Type::String,
323        }
324    }
325}
326
327impl fmt::Display for Type {
328    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
329        match *self {
330            Type::Address => write!(f, "address"),
331            Type::Identifier => write!(f, "identifier"),
332            Type::DynRecord => write!(f, "dyn record"),
333            Type::Array(ref array_type) => write!(f, "{array_type}"),
334            Type::Boolean => write!(f, "bool"),
335            Type::Field => write!(f, "field"),
336            Type::Future(ref future_type) => write!(f, "{future_type}"),
337            Type::Group => write!(f, "group"),
338            Type::Ident(ref variable) => write!(f, "{variable}"),
339            Type::Integer(ref integer_type) => write!(f, "{integer_type}"),
340            Type::Mapping(ref mapping_type) => write!(f, "{mapping_type}"),
341            Type::Optional(ref optional_type) => write!(f, "{optional_type}"),
342            Type::Scalar => write!(f, "scalar"),
343            Type::Signature => write!(f, "signature"),
344            Type::String => write!(f, "string"),
345            Type::Composite(ref composite_type) => write!(f, "{composite_type}"),
346            Type::Tuple(ref tuple) => write!(f, "{tuple}"),
347            Type::Vector(ref vector_type) => write!(f, "{vector_type}"),
348            Type::Numeric => write!(f, "numeric"),
349            Type::Unit => write!(f, "()"),
350            Type::Err => write!(f, "error"),
351        }
352    }
353}