diff --git a/crates/aiken-lang/src/gen_uplc.rs b/crates/aiken-lang/src/gen_uplc.rs index fad597d40..ebb148a26 100644 --- a/crates/aiken-lang/src/gen_uplc.rs +++ b/crates/aiken-lang/src/gen_uplc.rs @@ -4784,25 +4784,28 @@ impl<'a> CodeGenerator<'a> { let mut args = IndexMap::new(); let mut unchanged_field_indices = vec![]; + // plus 2 so we get one index higher than the record update index + // then we add that and any other unchanged fields to an array to later create the + // lambda bindings + unchanged_field_indices.push(0); let mut prev_index = 0; + for (index, tipo) in indices - .iter() + .into_iter() .sorted_by(|(index1, _), (index2, _)| index1.cmp(index2)) - .rev() { let arg = arg_stack.pop().unwrap(); - args.insert(*index, (tipo.clone(), arg)); + args.insert(index, (tipo.clone(), arg)); - for field_index in prev_index..*index { + for field_index in (prev_index + 1)..index { unchanged_field_indices.push(field_index); } - prev_index = *index; + prev_index = index; } - unchanged_field_indices.reverse(); + unchanged_field_indices.push(prev_index + 1); - let mut term = Term::tail_list() - .apply(Term::var(format!("{tail_name_prefix}_{highest_index}"))); + let mut term = Term::var(format!("{tail_name_prefix}_{}", highest_index + 1)); for current_index in (0..(highest_index + 1)).rev() { let tail_name = format!("{tail_name_prefix}_{current_index}"); @@ -4822,38 +4825,33 @@ impl<'a> CodeGenerator<'a> { .apply(Term::integer(0.into())) .apply(term); - if !unchanged_field_indices.is_empty() { - prev_index = highest_index; - for index in unchanged_field_indices.into_iter() { - let tail_name = format!("{tail_name_prefix}_{prev_index}"); - let prev_tail_name = format!("{tail_name_prefix}_{index}"); + if unchanged_field_indices.len() > 1 { + let (prev_index, rest_list) = unchanged_field_indices + .split_last() + .unwrap_or_else(|| panic!("WHAT HAPPENED")); + + let mut prev_index = *prev_index; + + for index in rest_list.iter().rev() { + let index = *index; + let suffix_tail = format!("{tail_name_prefix}_{prev_index}"); + let tail = format!("{tail_name_prefix}_{index}"); - let mut tail_list = Term::var(prev_tail_name); + let mut tail_list = Term::var(tail); if index < prev_index { for _ in index..prev_index { tail_list = Term::tail_list().apply(tail_list); } - term = term.lambda(tail_name).apply(tail_list); + term = term.lambda(suffix_tail).apply(tail_list); } prev_index = index; } } - let tail_name = format!("{tail_name_prefix}_{prev_index}"); - let prev_tail_name = format!("{tail_name_prefix}_0"); - - let mut tail_list = Term::var(prev_tail_name.clone()); - - for _ in 0..prev_index { - tail_list = Term::tail_list().apply(tail_list); - } - if prev_index != 0 { - term = term.lambda(tail_name).apply(tail_list); - } term = term - .lambda(prev_tail_name) + .lambda(format!("{tail_name_prefix}_0")) .apply(Term::var(CONSTR_FIELDS_EXPOSER).apply(record)); arg_stack.push(term); diff --git a/crates/aiken-project/src/tests/gen_uplc.rs b/crates/aiken-project/src/tests/gen_uplc.rs index a77e9aca2..3577442e4 100644 --- a/crates/aiken-project/src/tests/gen_uplc.rs +++ b/crates/aiken-project/src/tests/gen_uplc.rs @@ -2554,3 +2554,299 @@ fn pass_constr_as_function() { false, ); } + +#[test] +fn record_update_output_2_vals() { + let src = r#" + type Address { + thing: ByteArray, + } + + type Datum { + NoDatum + InlineDatum(Data) + } + + type Output { + address: Address, + value: List<(ByteArray, List<(ByteArray, Int)>)>, + datum: Datum, + script_ref: Option, + } + + type MyDatum { + a: Int, + } + + test huh() { + let prev_output = + Output { + address: Address { thing: "script_hash_0" }, + value: [], + datum: InlineDatum(MyDatum{a: 3}), + script_ref: None, + } + + let next_output = + Output { ..prev_output, value: [], datum: prev_output.datum } + + prev_output == next_output + } + "#; + + assert_uplc( + src, + Term::equals_data() + .apply(Term::var("prev_output")) + .apply(Term::var("next_output")) + .lambda("next_output") + .apply( + Term::constr_data() + .apply(Term::integer(0.into())) + .apply( + Term::mk_cons() + .apply(Term::head_list().apply(Term::var("record_fields"))) + .apply( + Term::mk_cons() + .apply(Term::map_data().apply(Term::empty_map())) + .apply( + Term::mk_cons() + .apply( + Term::var(CONSTR_GET_FIELD) + .apply( + Term::var(CONSTR_FIELDS_EXPOSER) + .apply(Term::var("prev_output")), + ) + .apply(Term::integer(2.into())), + ) + .apply(Term::var("tail_index_3")), + ), + ), + ) + .lambda("tail_index_3") + .apply( + Term::tail_list().apply( + Term::tail_list() + .apply(Term::tail_list().apply(Term::var("record_fields"))), + ), + ) + .lambda("record_fields") + .apply(Term::var(CONSTR_FIELDS_EXPOSER).apply(Term::var("prev_output"))), + ) + .lambda("prev_output") + .apply(Term::data(Data::constr( + 0, + vec![ + Data::constr( + 0, + vec![Data::bytestring("script_hash_0".as_bytes().to_vec())], + ), + Data::map(vec![]), + Data::constr(1, vec![Data::constr(0, vec![Data::integer(3.into())])]), + Data::constr(1, vec![]), + ], + ))) + .constr_get_field() + .constr_fields_exposer() + .constr_index_exposer(), + false, + ); +} + +#[test] +fn record_update_output_1_val() { + let src = r#" + type Address { + thing: ByteArray, + } + + type Datum { + NoDatum + InlineDatum(Data) + } + + type Output { + address: Address, + value: List<(ByteArray, List<(ByteArray, Int)>)>, + datum: Datum, + script_ref: Option, + } + + type MyDatum { + a: Int, + } + + test huh() { + let prev_output = + Output { + address: Address { thing: "script_hash_0" }, + value: [], + datum: InlineDatum(MyDatum{a: 3}), + script_ref: None, + } + + let next_output = + Output { ..prev_output, datum: prev_output.datum } + + prev_output == next_output + } + "#; + + assert_uplc( + src, + Term::equals_data() + .apply(Term::var("prev_output")) + .apply(Term::var("next_output")) + .lambda("next_output") + .apply( + Term::constr_data() + .apply(Term::integer(0.into())) + .apply( + Term::mk_cons() + .apply(Term::head_list().apply(Term::var("record_fields"))) + .apply( + Term::mk_cons() + .apply(Term::head_list().apply(Term::var("tail_index_1"))) + .apply( + Term::mk_cons() + .apply( + Term::var(CONSTR_GET_FIELD) + .apply( + Term::var(CONSTR_FIELDS_EXPOSER) + .apply(Term::var("prev_output")), + ) + .apply(Term::integer(2.into())), + ) + .apply(Term::var("tail_index_3")), + ), + ), + ) + .lambda("tail_index_3") + .apply( + Term::tail_list().apply(Term::tail_list().apply(Term::var("tail_index_1"))), + ) + .lambda("tail_index_1") + .apply(Term::tail_list().apply(Term::var("record_fields"))) + .lambda("record_fields") + .apply(Term::var(CONSTR_FIELDS_EXPOSER).apply(Term::var("prev_output"))), + ) + .lambda("prev_output") + .apply(Term::data(Data::constr( + 0, + vec![ + Data::constr( + 0, + vec![Data::bytestring("script_hash_0".as_bytes().to_vec())], + ), + Data::map(vec![]), + Data::constr(1, vec![Data::constr(0, vec![Data::integer(3.into())])]), + Data::constr(1, vec![]), + ], + ))) + .constr_get_field() + .constr_fields_exposer() + .constr_index_exposer(), + false, + ); +} + +#[test] +fn record_update_output_first_last_val() { + let src = r#" + type Address { + thing: ByteArray, + } + + type Datum { + NoDatum + InlineDatum(Data) + } + + type Output { + address: Address, + value: List<(ByteArray, List<(ByteArray, Int)>)>, + datum: Datum, + script_ref: Option, + } + + type MyDatum { + a: Int, + } + + test huh() { + let prev_output = + Output { + address: Address { thing: "script_hash_0" }, + value: [], + datum: InlineDatum(MyDatum{a: 3}), + script_ref: None, + } + + let next_output = + Output { ..prev_output, address: Address{thing: "script_hash_0"}, script_ref: None } + + prev_output == next_output + } + "#; + + assert_uplc( + src, + Term::equals_data() + .apply(Term::var("prev_output")) + .apply(Term::var("next_output")) + .lambda("next_output") + .apply( + Term::constr_data() + .apply(Term::integer(0.into())) + .apply( + Term::mk_cons() + .apply(Term::data(Data::constr( + 0, + vec![Data::bytestring("script_hash_0".as_bytes().to_vec())], + ))) + .apply( + Term::mk_cons() + .apply(Term::head_list().apply(Term::var("tail_index_1"))) + .apply( + Term::mk_cons() + .apply( + Term::head_list().apply(Term::var("tail_index_2")), + ) + .apply( + Term::mk_cons() + .apply(Term::data(Data::constr(1, vec![]))) + .apply(Term::var("tail_index_4")), + ), + ), + ), + ) + .lambda("tail_index_4") + .apply( + Term::tail_list().apply(Term::tail_list().apply(Term::var("tail_index_2"))), + ) + .lambda("tail_index_2") + .apply(Term::tail_list().apply(Term::var("tail_index_1"))) + .lambda("tail_index_1") + .apply(Term::tail_list().apply(Term::var("record_fields"))) + .lambda("record_fields") + .apply(Term::var(CONSTR_FIELDS_EXPOSER).apply(Term::var("prev_output"))), + ) + .lambda("prev_output") + .apply(Term::data(Data::constr( + 0, + vec![ + Data::constr( + 0, + vec![Data::bytestring("script_hash_0".as_bytes().to_vec())], + ), + Data::map(vec![]), + Data::constr(1, vec![Data::constr(0, vec![Data::integer(3.into())])]), + Data::constr(1, vec![]), + ], + ))) + .constr_get_field() + .constr_fields_exposer() + .constr_index_exposer(), + false, + ); +}