From dd5badd8848ffce125ff3fd4ed2332df40a351b3 Mon Sep 17 00:00:00 2001 From: microproofs Date: Sun, 28 Jul 2024 16:14:41 -0400 Subject: [PATCH] Start work on revamping assignment so we can handle soft casting properly --- crates/aiken-lang/src/gen_uplc.rs | 439 ++++++++++++++-------- crates/aiken-lang/src/gen_uplc/air.rs | 4 + crates/aiken-lang/src/gen_uplc/builder.rs | 206 ++++++---- crates/aiken-lang/src/gen_uplc/tree.rs | 148 ++++++-- 4 files changed, 529 insertions(+), 268 deletions(-) diff --git a/crates/aiken-lang/src/gen_uplc.rs b/crates/aiken-lang/src/gen_uplc.rs index 0aa75267..97c09386 100644 --- a/crates/aiken-lang/src/gen_uplc.rs +++ b/crates/aiken-lang/src/gen_uplc.rs @@ -38,6 +38,7 @@ use crate::{ }, IdGenerator, }; +use builder::unknown_data_to_type; use indexmap::{IndexMap, IndexSet}; use itertools::Itertools; use petgraph::{algo, Graph}; @@ -271,17 +272,16 @@ impl<'a> CodeGenerator<'a> { let msg_func_name = msg.split_whitespace().join(""); - self.special_functions.insert_new_function( - msg_func_name.clone(), - if msg.is_empty() { - Term::Error.delay() - } else { - Term::Error.delayed_trace(Term::string(msg)).delay() - }, - void(), - ); - - self.special_functions.use_function_tree(msg_func_name) + if msg_func_name.is_empty() { + None + } else { + self.special_functions.insert_new_function( + msg_func_name.clone(), + Term::Error.delayed_trace(Term::string(msg)).delay(), + void(), + ); + Some(self.special_functions.use_function_tree(msg_func_name)) + } }; let (then, context) = context.split_first().unwrap(); @@ -569,7 +569,7 @@ impl<'a> CodeGenerator<'a> { kind: AssignmentKind::let_(), remove_unused: false, full_check: false, - otherwise: AirTree::error(void(), false), + otherwise: None, }, ) } else { @@ -625,43 +625,47 @@ impl<'a> CodeGenerator<'a> { // // if is : { } // [(builtin ifThenElse) (condition is pattern) (body) (else) ] - TypedExpr::If { + a @ TypedExpr::If { branches, final_else, tipo, .. - } => branches.iter().rfold( - self.build(final_else, module_build_name, &[]), - |acc, branch| { - let condition = self.build(&branch.condition, module_build_name, &[]); - let body = self.build(&branch.body, module_build_name, &[]); + } => { + println!("A: {:#?}", a); + branches.iter().rfold( + self.build(final_else, module_build_name, &[]), + |acc, branch| { + let condition = self.build(&branch.condition, module_build_name, &[]); + let body = self.build(&branch.body, module_build_name, &[]); - match &branch.is { - Some((pattern, tipo)) => { - AirTree::let_assignment( - "acc_var", - // use anon function as a delay to avoid evaluating the acc - AirTree::anon_func(vec![], acc, true), - self.assignment( - pattern, - condition, - body, - tipo, - AssignmentProperties { - value_type: branch.condition.tipo(), - kind: AssignmentKind::Expect { backpassing: () }, - remove_unused: false, - full_check: true, - otherwise: AirTree::local_var("acc_var", void()), - }, - ), - ) + match &branch.is { + Some((pattern, tipo)) => { + println!("PATTERN TYPE: {:#?}", pattern); + println!("Branch TYPE: {:#?}", tipo); + AirTree::let_assignment( + "acc_var", + // use anon function as a delay to avoid evaluating the acc + AirTree::anon_func(vec![], acc, true), + self.assignment( + pattern, + condition, + body, + tipo, + AssignmentProperties { + value_type: branch.condition.tipo(), + kind: AssignmentKind::Expect { backpassing: () }, + remove_unused: false, + full_check: true, + otherwise: AirTree::local_var("acc_var", void()), + }, + ), + ) + } + None => AirTree::if_branch(tipo.clone(), condition, body, acc), } - None => AirTree::if_branch(tipo.clone(), condition, body, acc), - } - }, - ), - + }, + ) + } TypedExpr::RecordAccess { tipo, index, @@ -890,7 +894,7 @@ impl<'a> CodeGenerator<'a> { pub fn assignment( &mut self, pattern: &TypedPattern, - mut value: AirTree, + value: AirTree, then: AirTree, tipo: &Rc, props: AssignmentProperties, @@ -906,41 +910,90 @@ impl<'a> CodeGenerator<'a> { ); // Cast value to or from data so we don't have to worry from this point onward - if props.value_type.is_data() && props.kind.is_expect() && !tipo.is_data() { - value = AirTree::cast_from_data(value, tipo.clone(), props.otherwise.clone(), true); - } else if !props.value_type.is_data() && tipo.is_data() { - value = AirTree::cast_to_data(value, props.value_type.clone()); - } + let assign_casted_value = |name, value, then| { + if props.value_type.is_data() && props.kind.is_expect() && !tipo.is_data() { + if props.otherwise.is_some() { + AirTree::soft_cast_assignment( + name, + tipo.clone(), + value, + then, + props.otherwise.as_ref().unwrap().clone(), + ) + } else { + AirTree::let_assignment( + name, + AirTree::cast_from_data(value, tipo.clone(), true), + then, + ) + } + } else if !props.value_type.is_data() && tipo.is_data() { + AirTree::let_assignment( + name, + AirTree::cast_to_data(value, props.value_type.clone()), + then, + ) + } else { + AirTree::let_assignment(name, value, then) + } + }; + + let otherwise = match &props.otherwise { + Some(x) => x.clone(), + // (delay (error )) + None => AirTree::anon_func(vec![], AirTree::error(void(), false), true), + }; match pattern { Pattern::Int { value: expected_int, location, .. - } => AirTree::assign_literal_pattern( - format!( + } => { + let name = format!( "__expected_by_{}_span_{}_{}", expected_int, location.start, location.end - ), - AirTree::int(expected_int), - value, - int(), - props, - then, - ), + ); + + let expect = AirTree::binop( + BinOp::Eq, + bool(), + AirTree::int(expected_int), + AirTree::local_var(&name, int()), + int(), + ); + + assign_casted_value( + name, + value, + AirTree::assert_bool(true, expect, then, otherwise), + ) + } Pattern::ByteArray { - location, value: expected_bytes, + location, .. - } => AirTree::assign_literal_pattern( - format!("__expected_bytes_span_{}_{}", location.start, location.end), - AirTree::byte_array(expected_bytes.clone()), - value, - byte_array(), - props, - then, - ), + } => { + let name = format!( + "__expected_bytes_span_{}_{}", + location.start, location.end + ); + + let expect = AirTree::binop( + BinOp::Eq, + bool(), + AirTree::byte_array(expected_bytes), + AirTree::local_var(&name, byte_string()), + byte_string(), + ); + + assign_casted_value( + name, + value, + AirTree::assert_bool(true, expect, then, otherwise), + ) + } Pattern::Var { name, .. } => { if props.full_check { @@ -951,26 +1004,28 @@ impl<'a> CodeGenerator<'a> { let val = AirTree::local_var(name, tipo.clone()); if non_opaque_tipo.is_primitive() { - AirTree::let_assignment(name, value, then) + assign_casted_value(name.clone(), value, then) } else { - let expect = self.expect_type_assign( - &non_opaque_tipo, - val, - &mut index_map, - pattern.location(), - props.otherwise, - ); - - let assign_expect = AirTree::let_assignment("_", expect, then); - - AirTree::let_assignment(name, value, assign_expect) + assign_casted_value( + name.clone(), + value, + self.expect_type_assign( + &non_opaque_tipo, + val, + &mut index_map, + pattern.location(), + then, + otherwise, + ), + ) } } else { - AirTree::let_assignment(name, value, then) + assign_casted_value(name.clone(), value, then) } } Pattern::Assign { name, pattern, .. } => { + // Don't need any data casting for Assign let inner_pattern = self.assignment( pattern, AirTree::local_var(name, tipo.clone()), @@ -983,30 +1038,31 @@ impl<'a> CodeGenerator<'a> { Pattern::Discard { name, .. } => { if props.full_check { - let name = &format!("__discard_expect_{}", name); + let name = format!("__discard_expect_{}", name); let mut index_map = IndexMap::new(); let non_opaque_tipo = convert_opaque_type(tipo, &self.data_types, true); - let val = AirTree::local_var(name, tipo.clone()); + let val = AirTree::local_var(&name, tipo.clone()); if non_opaque_tipo.is_primitive() { - AirTree::let_assignment(name, value, then) + assign_casted_value(name, value, then) } else { - let expect = self.expect_type_assign( - &non_opaque_tipo, - val, - &mut index_map, - pattern.location(), - props.otherwise, - ); - - let assignment = AirTree::let_assignment("_", expect, then); - - AirTree::let_assignment(name, value, assignment) + assign_casted_value( + name, + value, + self.expect_type_assign( + &non_opaque_tipo, + val, + &mut index_map, + pattern.location(), + then, + otherwise, + ), + ) } } else if !props.remove_unused { - AirTree::let_assignment(name, value, then) + assign_casted_value(name.clone(), value, then) } else { then } @@ -1030,6 +1086,7 @@ impl<'a> CodeGenerator<'a> { Some(tail) => { let tail_name = match tail.as_ref() { Pattern::Var { name, .. } => name.to_string(), + // This Pattern one doesn't even make sense Pattern::Assign { name, .. } => name.to_string(), Pattern::Discard { name, .. } => { if props.full_check { @@ -1038,6 +1095,7 @@ impl<'a> CodeGenerator<'a> { "_".to_string() } } + // This should be unreachable _ => format!( "tail_span_{}_{}", tail.location().start, @@ -1056,6 +1114,9 @@ impl<'a> CodeGenerator<'a> { AssignmentProperties { value_type: tipo.clone(), kind: props.kind, + // The reason the top level of recursion might have remove_unused + // false is to deal with expect _ = thing + // next_thing remove_unused: true, full_check: props.full_check, otherwise: props.otherwise.clone(), @@ -1120,21 +1181,37 @@ impl<'a> CodeGenerator<'a> { elems.reverse(); + let name = format!( + "__List_span_{}_{}", + pattern.location().start, + pattern.location().end + ); + + let casted_var = AirTree::local_var(&name, tipo.clone()); + if elements.is_empty() { - AirTree::list_empty(value, then, props.otherwise.clone()) - } else { - AirTree::list_access( - elems, - tipo.clone(), - tail.is_some(), + assign_casted_value( + name, value, - if props.full_check { - ExpectLevel::Full - } else { - ExpectLevel::Items - }, - then, - props.otherwise.clone(), + AirTree::list_empty(casted_var, then, otherwise), + ) + } else { + assign_casted_value( + name, + value, + AirTree::list_access( + elems, + tipo.clone(), + tail.is_some(), + casted_var, + if props.full_check { + ExpectLevel::Full + } else { + ExpectLevel::Items + }, + then, + otherwise, + ), ) } } @@ -1214,8 +1291,7 @@ impl<'a> CodeGenerator<'a> { // This `value` is either value param that was passed in or // local var let constructor_name = format!( - "__constructor_{}_span_{}_{}", - "Pair", + "Pair_span_{}_{}", pattern.location().start, pattern.location().end ); @@ -1238,11 +1314,11 @@ impl<'a> CodeGenerator<'a> { local_value, props.full_check, then, - props.otherwise.clone(), + otherwise, ) }; - AirTree::let_assignment(constructor_name, value, then) + assign_casted_value(constructor_name, value, then) } Pattern::Constructor { @@ -1251,13 +1327,25 @@ impl<'a> CodeGenerator<'a> { } if tipo.is_bool() => { assert!(props.kind.is_expect()); - AirTree::assert_bool(name == "True", value, then, props.otherwise.clone()) + let name_var = format!( + "__Bool_{}_{}", + pattern.location().start, + pattern.location().end + ); + + let local_var = AirTree::local_var(&name_var, tipo.clone()); + + assign_casted_value( + name_var, + value, + AirTree::assert_bool(name == "True", local_var, then, otherwise), + ) } Pattern::Constructor { .. } if tipo.is_void() => { // Void type is checked when casting from data // So we just assign the value and move on - AirTree::let_assignment("_", value, then) + assign_casted_value("_".to_string(), value, then) } Pattern::Constructor { @@ -1376,7 +1464,7 @@ impl<'a> CodeGenerator<'a> { local_value, props.full_check, then, - props.otherwise.clone(), + otherwise.clone(), ) }; @@ -1404,7 +1492,7 @@ impl<'a> CodeGenerator<'a> { index, AirTree::local_var(&subject_name, tipo.clone()), then, - props.otherwise.clone(), + otherwise, ), ) } else { @@ -1415,7 +1503,7 @@ impl<'a> CodeGenerator<'a> { then }; - AirTree::let_assignment(constructor_name, value, then) + assign_casted_value(constructor_name, value, then) } Pattern::Tuple { @@ -1487,13 +1575,25 @@ impl<'a> CodeGenerator<'a> { // This `value` is either value param that was passed in or local var - AirTree::tuple_access( - fields, - tipo.clone(), + let name = format!( + "__Tuple_span_{}_{}", + pattern.location().start, + pattern.location().end + ); + + let local_var = AirTree::local_var(&name, tipo.clone()); + + assign_casted_value( + name, value, - props.full_check, - then, - props.otherwise.clone(), + AirTree::tuple_access( + fields, + tipo.clone(), + local_var, + props.full_check, + then, + otherwise, + ), ) } } @@ -1505,6 +1605,7 @@ impl<'a> CodeGenerator<'a> { value: AirTree, defined_data_types: &mut IndexMap, location: Span, + then: AirTree, otherwise: AirTree, ) -> AirTree { assert!(tipo.get_generic().is_none()); @@ -1621,27 +1722,29 @@ impl<'a> CodeGenerator<'a> { let mut tuple_expect_items = vec![]; - let then = tuple_inner_types.iter().enumerate().rfold( - AirTree::void(), - |then, (index, arg)| { - let tuple_index_name = format!( - "__tuple_index_{}_span_{}_{}", - index, location.start, location.end - ); + let then = + tuple_inner_types + .iter() + .enumerate() + .rfold(then, |then, (index, arg)| { + let tuple_index_name = format!( + "__tuple_index_{}_span_{}_{}", + index, location.start, location.end + ); - let expect_tuple_item = self.expect_type_assign( - arg, - AirTree::local_var(&tuple_index_name, arg.clone()), - defined_data_types, - location, - otherwise.clone(), - ); + let expect_tuple_item = self.expect_type_assign( + arg, + AirTree::local_var(&tuple_index_name, arg.clone()), + defined_data_types, + location, + then, + otherwise.clone(), + ); - tuple_expect_items.push(tuple_index_name); + tuple_expect_items.push(tuple_index_name); - AirTree::let_assignment("_", expect_tuple_item, then) - }, - ); + expect_tuple_item + }); tuple_expect_items.reverse(); @@ -1663,11 +1766,53 @@ impl<'a> CodeGenerator<'a> { let inner_list_type = &tipo.get_inner_types()[0]; if inner_list_type.is_data() { - value + then } else { let list_name = format!("__list_span_{}_{}", location.start, location.end); let item_name = format!("__item_span_{}_{}", location.start, location.end); + let g = AirTree::anon_func( + vec!["__list".to_string(), "__curried_expect_on_list".to_string()], + AirTree::list_empty( + AirTree::local_var("__list", tipo.clone()), + then, + AirTree::let_assignment( + &item_name, + AirTree::builtin( + DefaultFunction::HeadList, + data(), + vec![AirTree::local_var("__list", tipo.clone())], + ), + AirTree::let_assignment( + &item_name, + self.expect_type_assign( + inner_list_type, + AirTree::cast_from_data( + AirTree::local_var(item_name, data()), + inner_list_type.clone(), + otherwise.clone(), + true, + ), + defined_data_types, + location, + AirTree::void(), + otherwise, + ), + AirTree::call( + AirTree::local_var("__curried_expect_on_list", void()), + void(), + vec![AirTree::builtin( + DefaultFunction::TailList, + list(data()), + vec![AirTree::local_var("__list", tipo.clone())], + )], + ), + ), + ), + ), + false, + ); + let expect_item = self.expect_type_assign( inner_list_type, AirTree::cast_from_data( @@ -4051,7 +4196,7 @@ impl<'a> CodeGenerator<'a> { if otherwise == Term::Error.delay() { builder::unknown_data_to_type(term, tipo) } else { - builder::unknown_data_to_type_otherwise(term, tipo, otherwise) + builder::softcast_data_to_type_otherwise(term, tipo, otherwise) } }; @@ -4766,14 +4911,8 @@ impl<'a> CodeGenerator<'a> { Air::CastFromData { tipo, full_cast } => { let mut term = arg_stack.pop().unwrap(); - let otherwise = if full_cast { - arg_stack.pop().unwrap() - } else { - Term::Error.delay() - }; - term = if full_cast { - convert_data_to_type(term, &tipo, otherwise) + unknown_data_to_type(term, &tipo) } else { known_data_to_type(term, &tipo) }; diff --git a/crates/aiken-lang/src/gen_uplc/air.rs b/crates/aiken-lang/src/gen_uplc/air.rs index 07322314..981ad76e 100644 --- a/crates/aiken-lang/src/gen_uplc/air.rs +++ b/crates/aiken-lang/src/gen_uplc/air.rs @@ -101,6 +101,10 @@ pub enum Air { Let { name: String, }, + SoftCastLet { + name: String, + tipo: Rc, + }, CastFromData { tipo: Rc, full_cast: bool, diff --git a/crates/aiken-lang/src/gen_uplc/builder.rs b/crates/aiken-lang/src/gen_uplc/builder.rs index 31b3896f..f5931813 100644 --- a/crates/aiken-lang/src/gen_uplc/builder.rs +++ b/crates/aiken-lang/src/gen_uplc/builder.rs @@ -70,7 +70,7 @@ pub struct AssignmentProperties { pub kind: TypedAssignmentKind, pub remove_unused: bool, pub full_check: bool, - pub otherwise: AirTree, + pub otherwise: Option, } #[derive(Clone, Debug)] @@ -948,9 +948,11 @@ pub fn unknown_data_to_type(term: Term, field_type: &Type) -> Term { /// Due to the nature of the types BLS12_381_G1Element and BLS12_381_G2Element and String coming from bytearray /// We don't have error handling if the bytearray is not properly aligned to the type. Oh well lol /// For BLS12_381_G1Element and BLS12_381_G2Element, hash to group exists so just adopt that. -pub fn unknown_data_to_type_otherwise( - term: Term, +pub fn softcast_data_to_type_otherwise( + value: Term, + name: &String, field_type: &Type, + then: Term, otherwise_delayed: Term, ) -> Term { let uplc_type = field_type.get_uplc_type(); @@ -961,59 +963,67 @@ pub fn unknown_data_to_type_otherwise( otherwise_delayed.clone(), otherwise_delayed.clone(), otherwise_delayed.clone(), - Term::un_i_data().apply(Term::var("__val")).delay(), + then.lambda(name) + .apply(Term::un_i_data().apply(Term::var("__val"))) + .delay(), otherwise_delayed.clone(), ) .force() .lambda("__val") - .apply(term), + .apply(value), Some(UplcType::ByteString) => Term::var("__val") .choose_data( otherwise_delayed.clone(), otherwise_delayed.clone(), otherwise_delayed.clone(), otherwise_delayed.clone(), - Term::un_b_data().apply(Term::var("__val")).delay(), + then.lambda(name) + .apply(Term::un_b_data().apply(Term::var("__val"))) + .delay(), ) .force() .lambda("__val") - .apply(term), + .apply(value), Some(UplcType::String) => Term::var("__val") .choose_data( otherwise_delayed.clone(), otherwise_delayed.clone(), otherwise_delayed.clone(), otherwise_delayed.clone(), - Term::decode_utf8() - .apply(Term::un_b_data().apply(Term::var("__val"))) + then.lambda(name) + .apply(Term::decode_utf8().apply(Term::un_b_data().apply(Term::var("__val")))) .delay(), ) .force() .lambda("__val") - .apply(term), + .apply(value), Some(UplcType::List(_)) if field_type.is_map() => Term::var("__val") .choose_data( otherwise_delayed.clone(), - Term::unmap_data().apply(Term::var("__val")).delay(), + then.lambda(name) + .apply(Term::unmap_data().apply(Term::var("__val"))) + .delay(), otherwise_delayed.clone(), otherwise_delayed.clone(), otherwise_delayed.clone(), ) .force() .lambda("__val") - .apply(term), + .apply(value), Some(UplcType::List(_)) => Term::var("__val") .choose_data( otherwise_delayed.clone(), otherwise_delayed.clone(), - Term::unlist_data().apply(Term::var("__val")).delay(), + then.lambda(name) + .apply(Term::unlist_data().apply(Term::var("__val"))) + .delay(), otherwise_delayed.clone(), otherwise_delayed.clone(), ) .force() .lambda("__val") - .apply(term), + .apply(value), Some(UplcType::Bls12_381G1Element) => Term::var("__val") .choose_data( @@ -1021,26 +1031,32 @@ pub fn unknown_data_to_type_otherwise( otherwise_delayed.clone(), otherwise_delayed.clone(), otherwise_delayed.clone(), - Term::bls12_381_g1_uncompress() - .apply(Term::un_b_data().apply(Term::var("__val"))) + then.lambda(name) + .apply( + Term::bls12_381_g1_uncompress() + .apply(Term::un_b_data().apply(Term::var("__val"))), + ) .delay(), ) .force() .lambda("__val") - .apply(term), + .apply(value), Some(UplcType::Bls12_381G2Element) => Term::var("__val") .choose_data( otherwise_delayed.clone(), otherwise_delayed.clone(), otherwise_delayed.clone(), otherwise_delayed.clone(), - Term::bls12_381_g2_uncompress() - .apply(Term::un_b_data().apply(Term::var("__val"))) + then.lambda(name) + .apply( + Term::bls12_381_g2_uncompress() + .apply(Term::un_b_data().apply(Term::var("__val"))), + ) .delay(), ) .force() .lambda("__val") - .apply(term), + .apply(value), Some(UplcType::Bls12_381MlResult) => panic!("ML Result not supported"), Some(UplcType::Pair(_, _)) => Term::var("__val") .choose_data( @@ -1055,11 +1071,18 @@ pub fn unknown_data_to_type_otherwise( Term::tail_list() .apply(Term::var("__tail")) .choose_list( - Term::mk_pair_data() + then.lambda(name) .apply( - Term::head_list().apply(Term::var("__list_data")), + Term::mk_pair_data() + .apply( + Term::head_list() + .apply(Term::var("__list_data")), + ) + .apply( + Term::head_list() + .apply(Term::var("__tail")), + ), ) - .apply(Term::head_list().apply(Term::var("__tail"))) .delay(), otherwise_delayed.clone(), ) @@ -1080,25 +1103,22 @@ pub fn unknown_data_to_type_otherwise( ) .force() .lambda("__val") - .apply(term), + .apply(value), Some(UplcType::Bool) => Term::var("__val") .choose_data( Term::snd_pair() .apply(Term::var("__pair__")) .choose_list( - Term::equals_integer() - .apply(Term::integer(1.into())) + Term::less_than_equals_integer() + .apply(Term::integer(2.into())) .apply(Term::fst_pair().apply(Term::var("__pair__"))) .delayed_if_then_else( - Term::bool(true), - Term::equals_integer() - .apply(Term::integer(0.into())) - .apply(Term::fst_pair().apply(Term::var("__pair__"))) - .if_then_else( - Term::bool(false).delay(), - otherwise_delayed.clone(), - ) - .force(), + otherwise_delayed.clone(), + then.lambda(name).apply( + Term::equals_integer() + .apply(Term::fst_pair().apply(Term::var("__pair__"))) + .apply(Term::integer(1.into())), + ), ) .delay(), otherwise_delayed.clone(), @@ -1114,7 +1134,7 @@ pub fn unknown_data_to_type_otherwise( ) .force() .lambda("__val") - .apply(term), + .apply(value), Some(UplcType::Unit) => Term::var("__val") .choose_data( Term::equals_integer() @@ -1123,7 +1143,10 @@ pub fn unknown_data_to_type_otherwise( .if_then_else( Term::snd_pair() .apply(Term::unconstr_data().apply(Term::var("__val"))) - .choose_list(Term::unit().delay(), otherwise_delayed.clone()) + .choose_list( + then.lambda(name).apply(Term::unit()).delay(), + otherwise_delayed.clone(), + ) .force() .delay(), otherwise_delayed.clone(), @@ -1137,13 +1160,13 @@ pub fn unknown_data_to_type_otherwise( ) .force() .lambda("__val") - .apply(term), + .apply(value), - Some(UplcType::Data) => term, + Some(UplcType::Data) => then.lambda(name).apply(value), // constr type None => Term::var("__val") .choose_data( - Term::var("__val").delay(), + then.lambda(name).apply(Term::var("__val")).delay(), otherwise_delayed.clone(), otherwise_delayed.clone(), otherwise_delayed.clone(), @@ -1151,7 +1174,7 @@ pub fn unknown_data_to_type_otherwise( ) .force() .lambda("__val") - .apply(term), + .apply(value), } } @@ -1354,30 +1377,32 @@ pub fn list_access_to_uplc( let tail_name = |id| format!("tail_id_{}", id); - let head_item = |name, tipo: &Rc, tail_name: &str| { + let head_item = |name, tipo: &Rc, tail_name: &str, then: Term| { if name == "_" { - Term::unit() + then } else if tipo.is_pair() && is_list_accessor { Term::head_list().apply(Term::var(tail_name.to_string())) } else if matches!(expect_level, ExpectLevel::Full) { // Expect level is full so we have an unknown piece of data to cast if otherwise_delayed == Term::Error.delay() { - unknown_data_to_type( + then.lambda(name).apply(unknown_data_to_type( Term::head_list().apply(Term::var(tail_name.to_string())), &tipo.to_owned(), - ) + )) } else { - unknown_data_to_type_otherwise( + softcast_data_to_type_otherwise( Term::head_list().apply(Term::var(tail_name.to_string())), + name, &tipo.to_owned(), + then, otherwise_delayed.clone(), ) } } else { - known_data_to_type( + then.lambda(name).apply(known_data_to_type( Term::head_list().apply(Term::var(tail_name.to_string())), &tipo.to_owned(), - ) + )) } }; @@ -1399,48 +1424,56 @@ pub fn list_access_to_uplc( // case for no tail, but last item let tail_name = tail_name(id); - let head_item = head_item(name, tipo, &tail_name); + // let head_item = head_item(name, tipo, &tail_name); match expect_level { - ExpectLevel::None => acc.lambda(name).apply(head_item).lambda(tail_name), + ExpectLevel::None => { + head_item(name, tipo, &tail_name, acc).lambda(tail_name) + } ExpectLevel::Full | ExpectLevel::Items => { if otherwise_delayed == Term::Error.delay() && tail_present { // No need to check last item if tail was present - acc.lambda(name).apply(head_item).lambda(tail_name) + head_item(name, tipo, &tail_name, acc).lambda(tail_name) } else if tail_present { // Custom error instead of trying to do head_item on a possibly empty list. Term::var(tail_name.to_string()) .choose_list( otherwise_delayed.clone(), - acc.lambda(name).apply(head_item).delay(), + head_item(name, tipo, &tail_name, acc).delay(), ) .force() .lambda(tail_name) } else if otherwise_delayed == Term::Error.delay() { // Check head is last item in this list - Term::tail_list() - .apply(Term::var(tail_name.to_string())) - .choose_list(acc.delay(), Term::Error.delay()) - .force() - .lambda(name) - .apply(head_item) - .lambda(tail_name) + head_item( + name, + tipo, + &tail_name, + Term::tail_list() + .apply(Term::var(tail_name.to_string())) + .choose_list(acc.delay(), Term::Error.delay()) + .force(), + ) + .lambda(tail_name) } else { // Custom error if list is not empty after this head - Term::var(tail_name.to_string()) - .choose_list( - otherwise_delayed.clone(), - Term::tail_list() - .apply(Term::var(tail_name.to_string())) - .choose_list(acc.delay(), otherwise_delayed.clone()) - .force() - .lambda(name) - .apply(head_item) - .delay(), - ) - .force() - .lambda(tail_name) + head_item( + name, + tipo, + &tail_name, + Term::var(tail_name.to_string()) + .choose_list( + otherwise_delayed.clone(), + Term::tail_list() + .apply(Term::var(tail_name.to_string())) + .choose_list(acc.delay(), otherwise_delayed.clone()) + .force(), + ) + .delay(), + ) + .force() + .lambda(tail_name) } } } @@ -1450,26 +1483,33 @@ pub fn list_access_to_uplc( // case for every item except the last item let tail_name = tail_name(id); - let head_item = head_item(name, tipo, &tail_name); + // let head_item = head_item(name, tipo, &tail_name); if matches!(expect_level, ExpectLevel::None) || otherwise_delayed == Term::Error.delay() { - acc.apply(Term::tail_list().apply(Term::var(tail_name.to_string()))) - .lambda(name) - .apply(head_item) - .lambda(tail_name) + head_item( + name, + tipo, + &tail_name, + acc.apply(Term::tail_list().apply(Term::var(tail_name.to_string()))), + ) + .lambda(tail_name) } else { // case for a custom error if the list is empty at this point + Term::var(tail_name.to_string()) .choose_list( otherwise_delayed.clone(), - acc.apply( - Term::tail_list().apply(Term::var(tail_name.to_string())), - ) - .lambda(name) - .apply(head_item) - .delay(), + head_item( + name, + tipo, + &tail_name, + acc.apply( + Term::tail_list().apply(Term::var(tail_name.to_string())), + ) + .delay(), + ), ) .force() .lambda(tail_name) diff --git a/crates/aiken-lang/src/gen_uplc/tree.rs b/crates/aiken-lang/src/gen_uplc/tree.rs index c716b5a6..a5dc2673 100644 --- a/crates/aiken-lang/src/gen_uplc/tree.rs +++ b/crates/aiken-lang/src/gen_uplc/tree.rs @@ -125,6 +125,13 @@ pub enum AirTree { value: Box, then: Box, }, + SoftCastLet { + name: String, + tipo: Rc, + value: Box, + then: Box, + otherwise: Box, + }, DefineFunc { func_name: String, module_name: String, @@ -312,7 +319,6 @@ pub enum AirTree { CastFromData { tipo: Rc, value: Box, - otherwise: Box, full_cast: bool, }, CastToData { @@ -589,39 +595,26 @@ impl AirTree { } } - pub fn assign_literal_pattern( - name: String, - pattern: AirTree, - rhs: AirTree, + pub fn soft_cast_assignment( + name: impl ToString, tipo: Rc, - props: AssignmentProperties, + value: AirTree, then: AirTree, + otherwise: AirTree, ) -> AirTree { - assert!(props.kind.is_expect()); - - let expect = AirTree::binop( - BinOp::Eq, - bool(), - pattern, - AirTree::local_var(&name, tipo.clone()), + AirTree::SoftCastLet { + name: name.to_string(), tipo, - ); - - let expr = AirTree::let_assignment(name, rhs, expect); - - AirTree::assert_bool(true, expr, then, props.otherwise.clone()) + value: value.into(), + then: then.into(), + otherwise: otherwise.into(), + } } - pub fn cast_from_data( - value: AirTree, - tipo: Rc, - otherwise: AirTree, - full_cast: bool, - ) -> AirTree { + pub fn cast_from_data(value: AirTree, tipo: Rc, full_cast: bool) -> AirTree { AirTree::CastFromData { tipo, value: value.into(), - otherwise: otherwise.into(), full_cast, } } @@ -888,7 +881,6 @@ impl AirTree { vec![list_of_fields], ), tipo.clone(), - AirTree::error(void(), false), false, ) } @@ -994,7 +986,6 @@ impl AirTree { vec![tuple], ), tipo.clone(), - AirTree::error(void(), false), false, ) } @@ -1045,6 +1036,32 @@ impl AirTree { } } + pub fn expect_on_list2() -> AirTree { + let expect_on_list = AirTree::var( + ValueConstructor::public( + void(), + ValueConstructorVariant::ModuleFn { + name: EXPECT_ON_LIST.to_string(), + field_map: None, + module: "".to_string(), + arity: 1, + location: Span::empty(), + builtin: None, + }, + ), + EXPECT_ON_LIST, + "", + ); + + let list_var = AirTree::local_var("__list_to_check", list(data())); + + AirTree::call( + AirTree::local_var("__check_with", void()), + void(), + vec![list_var, expect_on_list], + ) + } + pub fn expect_on_list() -> AirTree { let list_var = AirTree::local_var("__list_to_check", list(data())); @@ -1108,6 +1125,21 @@ impl AirTree { value.create_air_vec(air_vec); then.create_air_vec(air_vec); } + AirTree::SoftCastLet { + name, + tipo, + value, + then, + otherwise, + } => { + air_vec.push(Air::SoftCastLet { + name: name.clone(), + tipo: tipo.clone(), + }); + value.create_air_vec(air_vec); + then.create_air_vec(air_vec); + otherwise.create_air_vec(air_vec); + } AirTree::DefineFunc { func_name, module_name, @@ -1457,7 +1489,6 @@ impl AirTree { AirTree::CastFromData { tipo, value, - otherwise, full_cast, } => { air_vec.push(Air::CastFromData { @@ -1466,9 +1497,6 @@ impl AirTree { }); value.create_air_vec(air_vec); - if *full_cast { - otherwise.create_air_vec(air_vec); - } } AirTree::CastToData { tipo, value } => { air_vec.push(Air::CastToData { tipo: tipo.clone() }); @@ -1670,6 +1698,7 @@ impl AirTree { | AirTree::PairClause { then, .. } | AirTree::Finally { then, .. } | AirTree::Let { then, .. } + | AirTree::SoftCastLet { then, .. } | AirTree::DefineFunc { then, .. } | AirTree::DefineCyclicFuncs { then, .. } | AirTree::AssertConstr { then, .. } @@ -1715,7 +1744,8 @@ impl AirTree { | AirTree::Constr { tipo, .. } | AirTree::ErrorTerm { tipo, .. } | AirTree::Trace { tipo, .. } - | AirTree::Pair { tipo, .. } => vec![tipo], + | AirTree::Pair { tipo, .. } + | AirTree::SoftCastLet { tipo, .. } => vec![tipo], AirTree::FieldsExpose { indices, .. } => { let mut types = vec![]; @@ -1821,6 +1851,30 @@ impl AirTree { ); } + AirTree::SoftCastLet { + name: _, + tipo: _, + value, + then: _, + otherwise, + } => { + value.do_traverse_tree_with( + tree_path, + current_depth + 1, + Fields::ThirdField, + with, + apply_with_func_last, + ); + + otherwise.do_traverse_tree_with( + tree_path, + current_depth + 1, + Fields::FifthField, + with, + apply_with_func_last, + ); + } + AirTree::AssertConstr { constr_index: _, constr, @@ -2291,7 +2345,6 @@ impl AirTree { AirTree::CastFromData { tipo: _, value, - otherwise: _, full_cast: _, } => { value.do_traverse_tree_with( @@ -2560,6 +2613,21 @@ impl AirTree { apply_with_func_last, ); } + AirTree::SoftCastLet { + name: _, + tipo: _, + value: _, + then, + otherwise: _, + } => { + then.do_traverse_tree_with( + tree_path, + current_depth + 1, + Fields::FourthField, + with, + apply_with_func_last, + ); + } AirTree::AssertConstr { constr_index: _, constr: _, @@ -2806,6 +2874,18 @@ impl AirTree { Fields::ThirdField => then.as_mut().do_find_air_tree_node(tree_path_iter), _ => panic!("Tree Path index outside tree children nodes"), }, + AirTree::SoftCastLet { + name: _, + tipo: _, + value, + then, + otherwise, + } => match field { + Fields::ThirdField => value.as_mut().do_find_air_tree_node(tree_path_iter), + Fields::FourthField => then.as_mut().do_find_air_tree_node(tree_path_iter), + Fields::FifthField => otherwise.as_mut().do_find_air_tree_node(tree_path_iter), + _ => panic!("Tree Path index outside tree children nodes"), + }, AirTree::AssertConstr { constr_index: _, constr, @@ -3011,11 +3091,9 @@ impl AirTree { AirTree::CastFromData { tipo: _, value, - otherwise, full_cast: _, } => match field { Fields::SecondField => value.as_mut().do_find_air_tree_node(tree_path_iter), - Fields::ThirdField => otherwise.as_mut().do_find_air_tree_node(tree_path_iter), _ => panic!("Tree Path index outside tree children nodes"), }, AirTree::CastToData { tipo: _, value } => match field {