Skip to content

Commit e2f1920

Browse files
committed
feat: Cast values of structurally equivalent types
Casts allow us to convert a pair (u8, u8) of two integers into one u16 integer, and so on. A cast between two Simfony types A and B is possible if and only if the Simplicity types (aka structural types) that underly A and B are exactly equal. Casts in languages like Rust or C++ usually specify their target type. Simfony casts specify their **source type**. The Simfony compiler knows the expected output type of each expression. In the above example, the compiler knows that it expects a `u16` output, but it doesn't know if the cast input value `(1, 1)` is of type `(u8, u8)` or `(u16, u16)` or `(u32, u32)`, ... . Therefore, Simfony casts have a slightly weird syntax: let out: u16 = <(u8, u8)>::into((1, 1)); More generally, the syntax is let out: TargetType = <SourceType>::into(source_value); For what it's worth, this is valid Rust and the Rust code works in the same way as the Simfony code. I think we can live with this syntax, although it isn't perfect. Casts of the form <TargetType>::from(source_value) would require a major overhaul of the compiler. Also remove `$` from the grammar to allow whitespaces in types, such as `(u8, u8)`, as opposed to the spaceless `(u8,u8)`.
1 parent ccac596 commit e2f1920

File tree

5 files changed

+44
-4
lines changed

5 files changed

+44
-4
lines changed

src/ast.rs

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@ use crate::error::{Error, RichError, WithSpan};
1010
use crate::parse;
1111
use crate::parse::{FunctionName, Identifier, MatchPattern, Span, WitnessName};
1212
use crate::pattern::Pattern;
13-
use crate::types::{AliasedType, ResolvedType, TypeConstructible, TypeDeconstructible};
13+
use crate::types::{
14+
AliasedType, ResolvedType, StructuralType, TypeConstructible, TypeDeconstructible,
15+
};
1416
use crate::value::{UIntValue, Value};
1517

1618
/// Map of witness names to their expected type, as declared in the program.
@@ -233,6 +235,8 @@ pub enum CallName {
233235
UnwrapRight(ResolvedType),
234236
/// [`Option::unwrap`].
235237
Unwrap,
238+
/// Cast from the given source type.
239+
TypeCast(ResolvedType),
236240
/// A custom function that was defined previously.
237241
///
238242
/// We effectively copy the function body into every call of the function.
@@ -899,6 +903,20 @@ impl AbstractSyntaxTree for Call {
899903
scope,
900904
)?])
901905
}
906+
CallName::TypeCast(source) => {
907+
if from.args.len() != 1 {
908+
return Err(Error::InvalidNumberOfArguments(1, from.args.len()))
909+
.with_span(from);
910+
}
911+
if StructuralType::from(&source) != StructuralType::from(ty) {
912+
return Err(Error::InvalidCast(source, ty.clone())).with_span(from);
913+
}
914+
Arc::from([Expression::analyze(
915+
from.args.first().unwrap(),
916+
&source,
917+
scope,
918+
)?])
919+
}
902920
CallName::Custom(function) => {
903921
if from.args.len() != function.params().len() {
904922
return Err(Error::InvalidNumberOfArguments(
@@ -951,6 +969,9 @@ impl AbstractSyntaxTree for CallName {
951969
.map(Self::UnwrapRight)
952970
.with_span(from),
953971
parse::CallName::Unwrap => Ok(Self::Unwrap),
972+
parse::CallName::TypeCast(target) => {
973+
scope.resolve(target).map(Self::TypeCast).with_span(from)
974+
}
954975
parse::CallName::Custom(name) => scope
955976
.get_function(name)
956977
.cloned()

src/compile.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,13 @@ impl Call {
301301
let get_inner = ProgNode::assertr_take(fail_cmr, &ProgNode::iden());
302302
ProgNode::comp(&right_and_unit, &get_inner).with_span(self)
303303
}
304+
CallName::TypeCast(..) => {
305+
// A cast converts between two structurally equal types.
306+
// Structural equality of Simfony types A and B means
307+
// exact equality of the underlying Simplicity types of A and of B.
308+
// Therefore, a Simfony cast is a NOP in Simplicity.
309+
Ok(args)
310+
}
304311
CallName::Custom(function) => {
305312
let params_pattern = Pattern::tuple(
306313
function

src/error.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ pub enum Error {
154154
CannotCompile(String),
155155
JetDoesNotExist(JetName),
156156
TypeValueMismatch(ResolvedType),
157+
InvalidCast(ResolvedType, ResolvedType),
157158
MainNoInputs,
158159
MainNoOutput,
159160
MainRequired,
@@ -212,6 +213,10 @@ impl fmt::Display for Error {
212213
f,
213214
"Value does not match the assigned type `{ty}`"
214215
),
216+
Error::InvalidCast(source, target) => write!(
217+
f,
218+
"Cannot cast values of type `{source}` as values of type `{target}`"
219+
),
215220
Error::MainNoInputs => write!(
216221
f,
217222
"Main function takes no input parameters"

src/minimal.pest

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,10 +55,11 @@ none_expr = @{ "None" }
5555
some_expr = { "Some(" ~ expression ~ ")" }
5656
false_expr = @{ "false" }
5757
true_expr = @{ "true" }
58-
unwrap_left = ${ "unwrap_left::<" ~ ty ~ ">" }
59-
unwrap_right = ${ "unwrap_right::<" ~ ty ~ ">" }
58+
unwrap_left = { "unwrap_left::<" ~ ty ~ ">" }
59+
unwrap_right = { "unwrap_right::<" ~ ty ~ ">" }
6060
unwrap = @{ "unwrap" }
61-
call_name = ${ jet | unwrap_left | unwrap_right | unwrap | function_name }
61+
type_cast = { "<" ~ ty ~ ">::into" }
62+
call_name = { jet | unwrap_left | unwrap_right | unwrap | type_cast | function_name }
6263
call_args = { "(" ~ (expression ~ ("," ~ expression)*)? ~ ")" }
6364
call_expr = { call_name ~ call_args }
6465
unsigned_decimal = @{ (ASCII_DIGIT | "_")+ }

src/parse.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,8 @@ pub enum CallName {
278278
UnwrapRight(AliasedType),
279279
/// Some unwrap function.
280280
Unwrap,
281+
/// Cast from the given source type.
282+
TypeCast(AliasedType),
281283
/// Name of a custom function.
282284
Custom(FunctionName),
283285
}
@@ -813,6 +815,10 @@ impl PestParse for CallName {
813815
AliasedType::parse(inner).map(Self::UnwrapRight)
814816
}
815817
Rule::unwrap => Ok(Self::Unwrap),
818+
Rule::type_cast => {
819+
let inner = pair.into_inner().next().unwrap();
820+
AliasedType::parse(inner).map(Self::TypeCast)
821+
}
816822
Rule::function_name => FunctionName::parse(pair).map(Self::Custom),
817823
_ => panic!("Corrupt grammar"),
818824
}

0 commit comments

Comments
 (0)