From 084b900b2ae9a8a2ef625b8d5f4e4417dcd12ff8 Mon Sep 17 00:00:00 2001 From: microproofs Date: Sat, 19 Aug 2023 20:07:37 -0400 Subject: [PATCH] change: traverse_with_tree now has a boolean to determine when with is called fix: Opaque types are now properly handled in code gen (i.e. code gen functions, in datums/redeemers, in from data casts) chore: add specific nested opaque type tests to code gen --- crates/aiken-lang/src/gen_uplc.rs | 93 ++++++-- crates/aiken-lang/src/gen_uplc/builder.rs | 115 ++++----- crates/aiken-lang/src/gen_uplc/tree.rs | 66 ++++- crates/aiken-project/src/tests/gen_uplc.rs | 265 +++++++++++++++++++++ 4 files changed, 445 insertions(+), 94 deletions(-) diff --git a/crates/aiken-lang/src/gen_uplc.rs b/crates/aiken-lang/src/gen_uplc.rs index 528c05f1..67f823a6 100644 --- a/crates/aiken-lang/src/gen_uplc.rs +++ b/crates/aiken-lang/src/gen_uplc.rs @@ -23,10 +23,10 @@ use crate::{ builtins::{bool, data, int, void}, expr::TypedExpr, gen_uplc::builder::{ - convert_opaque_type, erase_opaque_type_operations, find_and_replace_generics, - find_list_clause_or_default_first, get_arg_type_name, get_generic_id_and_type, - get_variant_name, monomorphize, pattern_has_conditions, wrap_as_multi_validator, - wrap_validator_condition, CodeGenFunction, SpecificClause, + check_replaceable_opaque_type, convert_opaque_type, erase_opaque_type_operations, + find_and_replace_generics, find_list_clause_or_default_first, get_arg_type_name, + get_generic_id_and_type, get_variant_name, monomorphize, pattern_has_conditions, + wrap_as_multi_validator, wrap_validator_condition, CodeGenFunction, SpecificClause, }, tipo::{ ModuleValueConstructor, PatternConstructor, Type, TypeInfo, ValueConstructor, @@ -539,7 +539,13 @@ impl<'a> CodeGenerator<'a> { index, record, .. - } => AirTree::record_access(*index, tipo.clone(), self.build(record)), + } => { + if check_replaceable_opaque_type(&record.tipo(), &self.data_types) { + self.build(record) + } else { + AirTree::record_access(*index, tipo.clone(), self.build(record)) + } + } TypedExpr::ModuleSelect { tipo, @@ -1013,7 +1019,11 @@ impl<'a> CodeGenerator<'a> { // This `value` is either value param that was passed in or // local var - sequence.push(AirTree::fields_expose(indices, props.full_check, value)); + if check_replaceable_opaque_type(tipo, &self.data_types) { + sequence.push(AirTree::let_assignment(&indices[0].1, value)); + } else { + sequence.push(AirTree::fields_expose(indices, props.full_check, value)); + } sequence.append( &mut fields @@ -1116,6 +1126,7 @@ impl<'a> CodeGenerator<'a> { location: Span, ) -> AirTree { assert!(tipo.get_generic().is_none()); + let tipo = &convert_opaque_type(tipo, &self.data_types); if tipo.is_primitive() { // Since we would return void anyway and ignore then we can just return value here and ignore @@ -2158,11 +2169,25 @@ impl<'a> CodeGenerator<'a> { let mut air_fields = fields.into_iter().map(|(_, _, _, val)| val).collect_vec(); - let field_assign = AirTree::fields_expose( - indices, - false, - AirTree::local_var(props.clause_var_name.clone(), subject_tipo.clone()), - ); + let field_assign = + if check_replaceable_opaque_type(subject_tipo, &self.data_types) { + AirTree::let_assignment( + &indices[0].1, + AirTree::local_var( + props.clause_var_name.clone(), + subject_tipo.clone(), + ), + ) + } else { + AirTree::fields_expose( + indices, + false, + AirTree::local_var( + props.clause_var_name.clone(), + subject_tipo.clone(), + ), + ) + }; let mut sequence = vec![field_assign]; @@ -2530,9 +2555,12 @@ impl<'a> CodeGenerator<'a> { let mut validator_hoistable; // TODO change subsequent tree traversals to be more like a stream. - air_tree.traverse_tree_with(&mut |air_tree: &mut AirTree, _| { - erase_opaque_type_operations(air_tree, &self.data_types); - }); + air_tree.traverse_tree_with( + &mut |air_tree: &mut AirTree, _| { + erase_opaque_type_operations(air_tree, &self.data_types); + }, + true, + ); self.find_function_vars_and_depth( &mut air_tree, @@ -2964,7 +2992,8 @@ impl<'a> CodeGenerator<'a> { let function_def = self.functions.get(&generic_function_key); - let Some(function_def) = function_def else { + let Some(function_def) = function_def + else { let code_gen_func = self .code_gen_functions .get(&generic_function_key.function_name) @@ -2988,12 +3017,21 @@ impl<'a> CodeGenerator<'a> { let mut function_variant_path = IndexMap::new(); + let mut body = body.clone(); + + body.traverse_tree_with( + &mut |air_tree, _| { + erase_opaque_type_operations(air_tree, &self.data_types); + }, + true, + ); + function_variant_path.insert( "".to_string(), ( tree_path.clone(), UserFunction::Function { - body: body.clone(), + body, deps: vec![], params: params.clone(), }, @@ -3067,10 +3105,13 @@ impl<'a> CodeGenerator<'a> { let mut function_air_tree_body = self.build(&function_def.body); - function_air_tree_body.traverse_tree_with(&mut |air_tree, _| { - erase_opaque_type_operations(air_tree, &self.data_types); - monomorphize(air_tree, &mono_types); - }); + function_air_tree_body.traverse_tree_with( + &mut |air_tree, _| { + erase_opaque_type_operations(air_tree, &self.data_types); + monomorphize(air_tree, &mono_types); + }, + true, + ); func_variants.insert( variant, @@ -3093,10 +3134,13 @@ impl<'a> CodeGenerator<'a> { let mut function_air_tree_body = self.build(&function_def.body); - function_air_tree_body.traverse_tree_with(&mut |air_tree, _| { - erase_opaque_type_operations(air_tree, &self.data_types); - monomorphize(air_tree, &mono_types); - }); + function_air_tree_body.traverse_tree_with( + &mut |air_tree, _| { + erase_opaque_type_operations(air_tree, &self.data_types); + monomorphize(air_tree, &mono_types); + }, + true, + ); let mut function_variant_path = IndexMap::new(); @@ -3116,6 +3160,7 @@ impl<'a> CodeGenerator<'a> { } } }, + true, ); } diff --git a/crates/aiken-lang/src/gen_uplc/builder.rs b/crates/aiken-lang/src/gen_uplc/builder.rs index 32c8ba5e..541756db 100644 --- a/crates/aiken-lang/src/gen_uplc/builder.rs +++ b/crates/aiken-lang/src/gen_uplc/builder.rs @@ -542,37 +542,14 @@ pub fn erase_opaque_type_operations( air_tree: &mut AirTree, data_types: &IndexMap, ) { - if let AirTree::Expression(e) = air_tree { - match e { - AirExpression::Constr { tipo, args, .. } => { - if check_replaceable_opaque_type(tipo, data_types) { - let arg = args.pop().unwrap(); - if let AirTree::Expression(AirExpression::CastToData { value, .. }) = arg { - *air_tree = *value; - } else { - *air_tree = arg; - } - } + if let AirTree::Expression(AirExpression::Constr { tipo, args, .. }) = air_tree { + if check_replaceable_opaque_type(tipo, data_types) { + let arg = args.pop().unwrap(); + if let AirTree::Expression(AirExpression::CastToData { value, .. }) = arg { + *air_tree = *value; + } else { + *air_tree = arg; } - AirExpression::RecordAccess { record, .. } => { - if check_replaceable_opaque_type(&record.return_type(), data_types) { - *air_tree = (**record).clone(); - } - } - - _ => {} - } - } else if let AirTree::Statement { - statement: AirStatement::FieldsExpose { - record, indices, .. - }, - hoisted_over: Some(hoisted_over), - } = air_tree - { - if check_replaceable_opaque_type(&record.return_type(), data_types) { - let name = indices[0].1.clone(); - *air_tree = AirTree::let_assignment(name, (**record).clone()) - .hoist_over((**hoisted_over).clone()) } } @@ -720,17 +697,20 @@ pub fn modify_self_calls( // TODO: this would be a lot simpler if each `Var`, `Let`, function argument, etc. had a unique identifier // rather than just a name; this would let us track if the Var passed to itself was the same value as the method argument let mut shadowed_parameters: HashMap = HashMap::new(); - body.traverse_tree_with(&mut |air_tree: &mut AirTree, tree_path| { - identify_recursive_static_params( - air_tree, - tree_path, - func_params, - func_key, - variant, - &mut shadowed_parameters, - &mut potential_recursive_statics, - ); - }); + body.traverse_tree_with( + &mut |air_tree: &mut AirTree, tree_path| { + identify_recursive_static_params( + air_tree, + tree_path, + func_params, + func_key, + variant, + &mut shadowed_parameters, + &mut potential_recursive_statics, + ); + }, + false, + ); // Find the index of any recursively static parameters, // so we can remove them from the call-site of each recursive call @@ -742,35 +722,38 @@ pub fn modify_self_calls( .collect(); // Modify any self calls to remove recursive static parameters and append `self` as a parameter for the recursion - body.traverse_tree_with(&mut |air_tree: &mut AirTree, _| { - if let AirTree::Expression(AirExpression::Call { func, args, .. }) = air_tree { - if let AirTree::Expression(AirExpression::Var { - constructor: - ValueConstructor { - variant: ValueConstructorVariant::ModuleFn { name, module, .. }, - .. - }, - variant_name, - .. - }) = func.as_ref() - { - if name == &func_key.function_name - && module == &func_key.module_name - && variant == variant_name + body.traverse_tree_with( + &mut |air_tree: &mut AirTree, _| { + if let AirTree::Expression(AirExpression::Call { func, args, .. }) = air_tree { + if let AirTree::Expression(AirExpression::Var { + constructor: + ValueConstructor { + variant: ValueConstructorVariant::ModuleFn { name, module, .. }, + .. + }, + variant_name, + .. + }) = func.as_ref() { - // Remove any static-recursive-parameters, because they'll be bound statically - // above the recursive part of the function - // note: assumes that static_recursive_params is sorted - for arg in recursive_static_indexes.iter().rev() { - args.remove(*arg); + if name == &func_key.function_name + && module == &func_key.module_name + && variant == variant_name + { + // Remove any static-recursive-parameters, because they'll be bound statically + // above the recursive part of the function + // note: assumes that static_recursive_params is sorted + for arg in recursive_static_indexes.iter().rev() { + args.remove(*arg); + } + let mut new_args = vec![func.as_ref().clone()]; + new_args.append(args); + *args = new_args; } - let mut new_args = vec![func.as_ref().clone()]; - new_args.append(args); - *args = new_args; } } - } - }); + }, + true, + ); let recursive_nonstatics = func_params .iter() .filter(|p| !potential_recursive_statics.contains(p)) diff --git a/crates/aiken-lang/src/gen_uplc/tree.rs b/crates/aiken-lang/src/gen_uplc/tree.rs index 2b7aa06a..e37b6754 100644 --- a/crates/aiken-lang/src/gen_uplc/tree.rs +++ b/crates/aiken-lang/src/gen_uplc/tree.rs @@ -1362,9 +1362,13 @@ impl AirTree { } } - pub fn traverse_tree_with(&mut self, with: &mut impl FnMut(&mut AirTree, &TreePath)) { + pub fn traverse_tree_with( + &mut self, + with: &mut impl FnMut(&mut AirTree, &TreePath), + apply_with_last: bool, + ) { let mut tree_path = TreePath::new(); - self.do_traverse_tree_with(&mut tree_path, 0, 0, with); + self.do_traverse_tree_with(&mut tree_path, 0, 0, with, apply_with_last); } pub fn traverse_tree_with_path( @@ -1373,8 +1377,9 @@ impl AirTree { current_depth: usize, depth_index: usize, with: &mut impl FnMut(&mut AirTree, &TreePath), + apply_with_last: bool, ) { - self.do_traverse_tree_with(path, current_depth, depth_index, with); + self.do_traverse_tree_with(path, current_depth, depth_index, with, apply_with_last); } fn do_traverse_tree_with( @@ -1383,6 +1388,7 @@ impl AirTree { current_depth: usize, depth_index: usize, with: &mut impl FnMut(&mut AirTree, &TreePath), + apply_with_last: bool, ) { let mut index_count = IndexCounter::new(); tree_path.push(current_depth, depth_index); @@ -1395,6 +1401,7 @@ impl AirTree { current_depth + 1, index_count.next_number(), with, + apply_with_last, ); } AirStatement::DefineFunc { func_body, .. } => { @@ -1403,6 +1410,7 @@ impl AirTree { current_depth + 1, index_count.next_number(), with, + apply_with_last, ); } AirStatement::AssertConstr { constr, .. } => { @@ -1411,6 +1419,7 @@ impl AirTree { current_depth + 1, index_count.next_number(), with, + apply_with_last, ); } AirStatement::AssertBool { value, .. } => { @@ -1419,6 +1428,7 @@ impl AirTree { current_depth + 1, index_count.next_number(), with, + apply_with_last, ); } AirStatement::ClauseGuard { pattern, .. } => { @@ -1427,6 +1437,7 @@ impl AirTree { current_depth + 1, index_count.next_number(), with, + apply_with_last, ); } AirStatement::ListClauseGuard { .. } => {} @@ -1437,6 +1448,7 @@ impl AirTree { current_depth + 1, index_count.next_number(), with, + apply_with_last, ); } AirStatement::ListAccessor { list, .. } => { @@ -1445,6 +1457,7 @@ impl AirTree { current_depth + 1, index_count.next_number(), with, + apply_with_last, ); } AirStatement::ListExpose { .. } => {} @@ -1454,6 +1467,7 @@ impl AirTree { current_depth + 1, index_count.next_number(), with, + apply_with_last, ); } AirStatement::NoOp => {} @@ -1463,6 +1477,7 @@ impl AirTree { current_depth + 1, index_count.next_number(), with, + apply_with_last, ); } AirStatement::ListEmpty { list } => { @@ -1471,12 +1486,15 @@ impl AirTree { current_depth + 1, index_count.next_number(), with, + apply_with_last, ); } }; } - with(self, tree_path); + if !apply_with_last { + with(self, tree_path); + } match self { AirTree::Statement { @@ -1488,6 +1506,7 @@ impl AirTree { current_depth + 1, index_count.next_number(), with, + apply_with_last, ); } AirTree::Expression(e) => match e { @@ -1498,6 +1517,7 @@ impl AirTree { current_depth + 1, index_count.next_number(), with, + apply_with_last, ); } } @@ -1508,6 +1528,7 @@ impl AirTree { current_depth + 1, index_count.next_number(), with, + apply_with_last, ); } } @@ -1517,6 +1538,7 @@ impl AirTree { current_depth + 1, index_count.next_number(), with, + apply_with_last, ); for arg in args { @@ -1525,6 +1547,7 @@ impl AirTree { current_depth + 1, index_count.next_number(), with, + apply_with_last, ); } } @@ -1534,6 +1557,7 @@ impl AirTree { current_depth + 1, index_count.next_number(), with, + apply_with_last, ); } AirExpression::Builtin { args, .. } => { @@ -1543,6 +1567,7 @@ impl AirTree { current_depth + 1, index_count.next_number(), with, + apply_with_last, ); } } @@ -1552,6 +1577,7 @@ impl AirTree { current_depth + 1, index_count.next_number(), with, + apply_with_last, ); right.do_traverse_tree_with( @@ -1559,6 +1585,7 @@ impl AirTree { current_depth + 1, index_count.next_number(), with, + apply_with_last, ); } AirExpression::UnOp { arg, .. } => { @@ -1567,6 +1594,7 @@ impl AirTree { current_depth + 1, index_count.next_number(), with, + apply_with_last, ); } AirExpression::CastFromData { value, .. } => { @@ -1575,6 +1603,7 @@ impl AirTree { current_depth + 1, index_count.next_number(), with, + apply_with_last, ); } AirExpression::CastToData { value, .. } => { @@ -1583,6 +1612,7 @@ impl AirTree { current_depth + 1, index_count.next_number(), with, + apply_with_last, ); } AirExpression::When { @@ -1593,6 +1623,7 @@ impl AirTree { current_depth + 1, index_count.next_number(), with, + apply_with_last, ); clauses.do_traverse_tree_with( @@ -1600,6 +1631,7 @@ impl AirTree { current_depth + 1, index_count.next_number(), with, + apply_with_last, ); } AirExpression::Clause { @@ -1613,6 +1645,7 @@ impl AirTree { current_depth + 1, index_count.next_number(), with, + apply_with_last, ); then.do_traverse_tree_with( @@ -1620,6 +1653,7 @@ impl AirTree { current_depth + 1, index_count.next_number(), with, + apply_with_last, ); otherwise.do_traverse_tree_with( @@ -1627,6 +1661,7 @@ impl AirTree { current_depth + 1, index_count.next_number(), with, + apply_with_last, ); } AirExpression::ListClause { @@ -1637,6 +1672,7 @@ impl AirTree { current_depth + 1, index_count.next_number(), with, + apply_with_last, ); otherwise.do_traverse_tree_with( @@ -1644,6 +1680,7 @@ impl AirTree { current_depth + 1, index_count.next_number(), with, + apply_with_last, ); } AirExpression::WrapClause { then, otherwise } => { @@ -1652,6 +1689,7 @@ impl AirTree { current_depth + 1, index_count.next_number(), with, + apply_with_last, ); otherwise.do_traverse_tree_with( @@ -1659,6 +1697,7 @@ impl AirTree { current_depth + 1, index_count.next_number(), with, + apply_with_last, ); } AirExpression::TupleClause { @@ -1669,6 +1708,7 @@ impl AirTree { current_depth + 1, index_count.next_number(), with, + apply_with_last, ); otherwise.do_traverse_tree_with( @@ -1676,6 +1716,7 @@ impl AirTree { current_depth + 1, index_count.next_number(), with, + apply_with_last, ); } AirExpression::Finally { pattern, then } => { @@ -1684,6 +1725,7 @@ impl AirTree { current_depth + 1, index_count.next_number(), with, + apply_with_last, ); then.do_traverse_tree_with( @@ -1691,6 +1733,7 @@ impl AirTree { current_depth + 1, index_count.next_number(), with, + apply_with_last, ); } AirExpression::If { @@ -1704,6 +1747,7 @@ impl AirTree { current_depth + 1, index_count.next_number(), with, + apply_with_last, ); then.do_traverse_tree_with( @@ -1711,6 +1755,7 @@ impl AirTree { current_depth + 1, index_count.next_number(), with, + apply_with_last, ); otherwise.do_traverse_tree_with( @@ -1718,6 +1763,7 @@ impl AirTree { current_depth + 1, index_count.next_number(), with, + apply_with_last, ); } AirExpression::Constr { args, .. } => { @@ -1727,6 +1773,7 @@ impl AirTree { current_depth + 1, index_count.next_number(), with, + apply_with_last, ); } } @@ -1736,6 +1783,7 @@ impl AirTree { current_depth + 1, index_count.next_number(), with, + apply_with_last, ); for arg in args { arg.do_traverse_tree_with( @@ -1743,6 +1791,7 @@ impl AirTree { current_depth + 1, index_count.next_number(), with, + apply_with_last, ); } } @@ -1752,6 +1801,7 @@ impl AirTree { current_depth + 1, index_count.next_number(), with, + apply_with_last, ); } AirExpression::TupleIndex { tuple, .. } => { @@ -1760,6 +1810,7 @@ impl AirTree { current_depth + 1, index_count.next_number(), with, + apply_with_last, ); } AirExpression::Trace { msg, then, .. } => { @@ -1768,6 +1819,7 @@ impl AirTree { current_depth + 1, index_count.next_number(), with, + apply_with_last, ); then.do_traverse_tree_with( @@ -1775,12 +1827,18 @@ impl AirTree { current_depth + 1, index_count.next_number(), with, + apply_with_last, ); } _ => {} }, a => unreachable!("GOT THIS {:#?}", a), } + + if apply_with_last { + with(self, tree_path); + } + tree_path.pop(); } diff --git a/crates/aiken-project/src/tests/gen_uplc.rs b/crates/aiken-project/src/tests/gen_uplc.rs index 40b4690d..1122ab2a 100644 --- a/crates/aiken-project/src/tests/gen_uplc.rs +++ b/crates/aiken-project/src/tests/gen_uplc.rs @@ -5077,3 +5077,268 @@ fn list_clause_with_assign2() { false, ); } + +#[test] +fn opaque_value_in_datum() { + let src = r#" + opaque type Value { + inner: Dict> + } + + opaque type Dict { + inner: List<(ByteArray, v)> + } + + type Dat { + c: Int, + a: Value + } + + + validator { + fn spend(dat: Dat, red: Data, ctx: Data) { + let val = dat.a + + expect [(_, amount)] = val.inner.inner + + let final_amount = [(#"AA", 4)] |> Dict + + final_amount == amount + + } + } + "#; + + assert_uplc( + src, + Term::tail_list() + .apply(Term::var("val")) + .delayed_choose_list( + Term::equals_data() + .apply(Term::map_data().apply(Term::var("final_amount"))) + .apply(Term::map_data().apply(Term::var("amount"))) + .lambda("final_amount") + .apply(Term::map_values(vec![Constant::ProtoPair( + Type::Data, + Type::Data, + Constant::Data(Data::bytestring(vec![170])).into(), + Constant::Data(Data::integer(4.into())).into(), + )])) + .lambda("amount") + .apply( + Term::unmap_data().apply(Term::snd_pair().apply(Term::var("tuple_item_0"))), + ), + Term::Error.trace(Term::string( + "List/Tuple/Constr contains more items than expected", + )), + ) + .lambda("tuple_item_0") + .apply(Term::head_list().apply(Term::var("val"))) + .lambda("val") + .apply( + Term::unmap_data().apply( + Term::var(CONSTR_GET_FIELD) + .apply(Term::var(CONSTR_FIELDS_EXPOSER).apply(Term::var("dat"))) + .apply(Term::integer(1.into())), + ), + ) + .delayed_if_else(Term::unit(), Term::Error) + .lambda("_") + .apply( + Term::equals_integer() + .apply(Term::integer(0.into())) + .apply(Term::var("subject")) + .delayed_if_else( + Term::tail_list() + .apply(Term::var("tail_1")) + .delayed_choose_list( + Term::unit().lambda("_").apply( + Term::var("expect_on_list").apply(Term::var("a")).apply( + Term::var("expect_on_list") + .apply(Term::var("pair_snd_outer")) + .apply( + Term::un_i_data() + .apply( + Term::snd_pair().apply(Term::var("pair")), + ) + .lambda("pair_fst") + .apply(Term::un_b_data().apply( + Term::fst_pair().apply(Term::var("pair")), + )) + .lambda("pair"), + ) + .lambda("pair_snd_outer") + .apply(Term::unmap_data().apply( + Term::snd_pair().apply(Term::var("pair_outer")), + )) + .lambda("pair_fst_outer") + .apply(Term::un_b_data().apply( + Term::fst_pair().apply(Term::var("pair_outer")), + )) + .lambda("pair_outer"), + ), + ), + Term::Error.trace(Term::string( + "List/Tuple/Constr contains more items than expected", + )), + ) + .lambda("a") + .apply( + Term::unmap_data() + .apply(Term::head_list().apply(Term::var("tail_1"))), + ) + .lambda("tail_1") + .apply(Term::tail_list().apply(Term::var("dat_fields"))) + .lambda("c") + .apply( + Term::un_i_data() + .apply(Term::head_list().apply(Term::var("dat_fields"))), + ) + .lambda("dat_fields") + .apply(Term::var(CONSTR_FIELDS_EXPOSER).apply(Term::var("param_0"))), + Term::Error + .trace(Term::string("Constr index did not match any type variant")), + ) + .lambda("subject") + .apply(Term::var(CONSTR_INDEX_EXPOSER).apply(Term::var("param_0"))) + .lambda("param_0") + .lambda("expect_on_list") + .apply( + Term::var("expect_on_list") + .apply(Term::var("expect_on_list")) + .apply(Term::var("list_to_check")) + .lambda("expect_on_list") + .apply( + Term::var("list_to_check") + .delayed_choose_list( + Term::unit(), + Term::var("expect_on_list") + .apply(Term::var("expect_on_list")) + .apply( + Term::tail_list().apply(Term::var("list_to_check")), + ) + .lambda("_") + .apply(Term::var("check_with").apply( + Term::head_list().apply(Term::var("list_to_check")), + )), + ) + .lambda("list_to_check") + .lambda("expect_on_list"), + ) + .lambda("check_with") + .lambda("list_to_check"), + ) + .apply(Term::var("dat")), + ) + .lambda("ctx") + .lambda("red") + .lambda("dat") + .constr_get_field() + .constr_fields_exposer() + .constr_index_exposer(), + false, + ); +} + +#[test] +fn opaque_value_in_test() { + let src = r#" + pub opaque type Value { + inner: Dict> + } + + pub opaque type Dict { + inner: List<(ByteArray, v)> + } + + pub type Dat { + c: Int, + a: Value + } + + pub fn dat_new() -> Dat { + let v = Value { inner: Dict { inner: [("", [(#"aa", 4)] |> Dict)] } } + Dat { + c: 0, + a: v + } + } + + + test spend() { + let dat = dat_new() + + let val = dat.a + + expect [(_, amount)] = val.inner.inner + + let final_amount = [(#"AA", 4)] |> Dict + + final_amount == amount + } + "#; + + assert_uplc( + src, + Term::tail_list() + .apply(Term::var("val")) + .delayed_choose_list( + Term::equals_data() + .apply(Term::map_data().apply(Term::var("final_amount"))) + .apply(Term::map_data().apply(Term::var("amount"))) + .lambda("final_amount") + .apply(Term::map_values(vec![Constant::ProtoPair( + Type::Data, + Type::Data, + Constant::Data(Data::bytestring(vec![170])).into(), + Constant::Data(Data::integer(4.into())).into(), + )])) + .lambda("amount") + .apply( + Term::unmap_data().apply(Term::snd_pair().apply(Term::var("tuple_item_0"))), + ), + Term::Error.trace(Term::string( + "List/Tuple/Constr contains more items than expected", + )), + ) + .lambda("tuple_item_0") + .apply(Term::head_list().apply(Term::var("val"))) + .lambda("val") + .apply( + Term::unmap_data().apply( + Term::var(CONSTR_GET_FIELD) + .apply(Term::var(CONSTR_FIELDS_EXPOSER).apply(Term::var("dat"))) + .apply(Term::integer(1.into())), + ), + ) + .lambda("dat") + .apply(Term::Constant( + Constant::Data(Data::constr( + 0, + vec![ + Data::integer(0.into()), + Data::map(vec![( + Data::bytestring(vec![]), + Data::map(vec![(Data::bytestring(vec![170]), Data::integer(4.into()))]), + )]), + ], + )) + .into(), + )) + .lambda("v") + .apply(Term::map_values(vec![Constant::ProtoPair( + Type::Data, + Type::Data, + Constant::Data(Data::bytestring(vec![])).into(), + Constant::Data(Data::map(vec![( + Data::bytestring(vec![170]), + Data::integer(4.into()), + )])) + .into(), + )])) + .constr_get_field() + .constr_fields_exposer() + .constr_index_exposer(), + false, + ); +}