Skip to main content

snarkvm_circuit_program/data/literal/cast/
mod.rs

1// Copyright (c) 2019-2026 Provable Inc.
2// This file is part of the snarkVM library.
3
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at:
7
8// http://www.apache.org/licenses/LICENSE-2.0
9
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16mod boolean;
17mod field;
18mod identifier;
19mod integer;
20mod scalar;
21
22use crate::data::{CastLossy, Literal};
23use console::LiteralType;
24use snarkvm_circuit_network::Aleo;
25use snarkvm_circuit_types::{
26    IdentifierLiteral,
27    prelude::{
28        Address,
29        BitOr,
30        Boolean,
31        Environment,
32        Field,
33        FromBits,
34        FromField,
35        FromGroup,
36        Group,
37        IntegerType,
38        MSB,
39        One,
40        Result,
41        Scalar,
42        ToBits,
43        ToField,
44        ToGroup,
45        Zero,
46        bail,
47        integers::Integer,
48    },
49};
50
51#[cfg(test)]
52use snarkvm_circuit_types::prelude::{I8, I16, I32, I64, I128, U8, U16, U32, U64, U128};
53
54/// Unary operator for casting values of one type to another.
55pub trait Cast<T: Sized = Self> {
56    /// Casts the value of `self` into a value of type `T`.
57    ///
58    /// This method checks that the cast does not lose any bits of information.
59    fn cast(&self) -> T;
60}
61
62impl<A: Aleo> Literal<A> {
63    /// Casts the literal to the given literal type.
64    ///
65    /// This method checks that the cast does not lose any bits of information,
66    /// and returns an error if it does.
67    ///
68    /// The hierarchy of casting is as follows:
69    ///  - (`Address`, `Group`) <-> `Field` <-> `Scalar` <-> `Integer` <-> `Boolean`
70    ///  - `Identifier` <-> `Field`
71    ///  - `Signature` (not supported)
72    ///  - `String` (not supported)
73    ///
74    /// Note that casting to left along the hierarchy always preserves information.
75    pub fn cast(&self, to_type: LiteralType) -> Result<Self> {
76        match self {
77            Self::Address(address) => cast_group_to_type(&address.to_group(), to_type),
78            Self::Boolean(boolean) => cast_boolean_to_type(boolean, to_type),
79            Self::Field(field) => cast_field_to_type(field, to_type),
80            Self::Group(group) => cast_group_to_type(group, to_type),
81            Self::I8(integer) => cast_integer_to_type(integer, to_type),
82            Self::I16(integer) => cast_integer_to_type(integer, to_type),
83            Self::I32(integer) => cast_integer_to_type(integer, to_type),
84            Self::I64(integer) => cast_integer_to_type(integer, to_type),
85            Self::I128(integer) => cast_integer_to_type(integer, to_type),
86            Self::U8(integer) => cast_integer_to_type(integer, to_type),
87            Self::U16(integer) => cast_integer_to_type(integer, to_type),
88            Self::U32(integer) => cast_integer_to_type(integer, to_type),
89            Self::U64(integer) => cast_integer_to_type(integer, to_type),
90            Self::U128(integer) => cast_integer_to_type(integer, to_type),
91            Self::Scalar(scalar) => cast_scalar_to_type(scalar, to_type),
92            Self::Signature(..) => bail!("Cannot cast a signature literal to another type."),
93            Self::String(..) => bail!("Cannot cast a string literal to another type."),
94            Self::Identifier(identifier) => cast_identifier_to_type(identifier, to_type),
95        }
96    }
97}
98
99/// A helper macro to implement the body of the `cast` methods.
100macro_rules! impl_cast_body {
101    ($type_name:ident, $cast:ident, $input:expr, $to_type:expr) => {
102        match $to_type {
103            LiteralType::Address => Ok(Literal::Address($input.$cast())),
104            LiteralType::Boolean => Ok(Literal::Boolean($input.$cast())),
105            LiteralType::Field => Ok(Literal::Field($input.$cast())),
106            LiteralType::Group => Ok(Literal::Group($input.$cast())),
107            LiteralType::I8 => Ok(Literal::I8($input.$cast())),
108            LiteralType::I16 => Ok(Literal::I16($input.$cast())),
109            LiteralType::I32 => Ok(Literal::I32($input.$cast())),
110            LiteralType::I64 => Ok(Literal::I64($input.$cast())),
111            LiteralType::I128 => Ok(Literal::I128($input.$cast())),
112            LiteralType::U8 => Ok(Literal::U8($input.$cast())),
113            LiteralType::U16 => Ok(Literal::U16($input.$cast())),
114            LiteralType::U32 => Ok(Literal::U32($input.$cast())),
115            LiteralType::U64 => Ok(Literal::U64($input.$cast())),
116            LiteralType::U128 => Ok(Literal::U128($input.$cast())),
117            LiteralType::Scalar => Ok(Literal::Scalar($input.$cast())),
118            LiteralType::Signature => {
119                bail!(concat!("Cannot cast a ", stringify!($type_name), " literal to a signature type."))
120            }
121            LiteralType::String => {
122                bail!(concat!("Cannot cast a ", stringify!($type_name), " literal to a string type."))
123            }
124            LiteralType::Identifier => Ok(Literal::Identifier(Box::new($input.$cast()))),
125        }
126    };
127}
128
129/// Casts a boolean literal to the given literal type.
130fn cast_boolean_to_type<A: Aleo>(input: &Boolean<A>, to_type: LiteralType) -> Result<Literal<A>> {
131    impl_cast_body!(boolean, cast, input, to_type)
132}
133
134/// Casts a field literal to the given literal type.
135fn cast_field_to_type<A: Aleo>(input: &Field<A>, to_type: LiteralType) -> Result<Literal<A>> {
136    impl_cast_body!(field, cast, input, to_type)
137}
138
139/// Casts a group literal to the given literal type.
140fn cast_group_to_type<A: Aleo>(input: &Group<A>, to_type: LiteralType) -> Result<Literal<A>> {
141    match to_type {
142        LiteralType::Address => Ok(Literal::Address(Address::from_group(input.clone()))),
143        LiteralType::Group => Ok(Literal::Group(input.clone())),
144        _ => cast_field_to_type(&input.to_x_coordinate(), to_type),
145    }
146}
147
148/// Casts an integer literal to the given literal type.
149fn cast_integer_to_type<A: Aleo, I: IntegerType>(input: &Integer<A, I>, to_type: LiteralType) -> Result<Literal<A>> {
150    impl_cast_body!(integer, cast, input, to_type)
151}
152
153/// Casts a scalar literal to the given literal type.
154fn cast_scalar_to_type<A: Aleo>(input: &Scalar<A>, to_type: LiteralType) -> Result<Literal<A>> {
155    impl_cast_body!(scalar, cast, input, to_type)
156}
157
158/// Casts an identifier literal to the given literal type.
159fn cast_identifier_to_type<A: Aleo>(input: &IdentifierLiteral<A>, to_type: LiteralType) -> Result<Literal<A>> {
160    impl_cast_body!(identifier, cast, input, to_type)
161}
162
163#[cfg(test)]
164macro_rules! impl_check_cast {
165    ($fun:ident, $circuit_type:ty, $console_type:ty) => {
166        fn check_cast<CircuitType, ConsoleType>(mode: Mode, count: UpdatableCount)
167        where
168            CircuitType: Eject,
169            <CircuitType as Eject>::Primitive: Debug + PartialEq<ConsoleType>,
170            ConsoleType: Debug,
171            $console_type: console::Cast<ConsoleType>,
172            $circuit_type: crate::Cast<CircuitType>,
173        {
174            let rng = &mut TestRng::default();
175            for i in 0..ITERATIONS {
176                let (console_value, circuit_value) = sample_values(i, mode, rng);
177                match console_value.$fun() {
178                    // If the console cast fails and the mode is constant, then the circuit cast should fail.
179                    Err(_) if mode == Mode::Constant => {
180                        assert!(
181                            std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| circuit_value.$fun())).is_err()
182                        )
183                    }
184                    // If the console cast fails, the circuit cast can either fail by panicking or fail by being unsatisfied.
185                    Err(_) => {
186                        Circuit::scope("test", || {
187                            if std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| circuit_value.$fun())).is_ok() {
188                                assert!(!Circuit::is_satisfied());
189                                count.assert_matches(
190                                    Circuit::num_constants_in_scope(),
191                                    Circuit::num_public_in_scope(),
192                                    Circuit::num_private_in_scope(),
193                                    Circuit::num_constraints_in_scope(),
194                                );
195                            }
196                        });
197                    }
198                    // If the console cast succeeds, the circuit cast should succeed and the values should match.
199                    Ok(expected) => Circuit::scope("test", || {
200                        let result = circuit_value.$fun();
201                        assert_eq!(result.eject_value(), expected);
202                        assert!(Circuit::is_satisfied());
203                        count.assert_matches(
204                            Circuit::num_constants_in_scope(),
205                            Circuit::num_public_in_scope(),
206                            Circuit::num_private_in_scope(),
207                            Circuit::num_constraints_in_scope(),
208                        );
209                    }),
210                };
211                Circuit::reset();
212            }
213        }
214    };
215}
216#[cfg(test)]
217pub(super) use impl_check_cast;