fix: change list_access_to_uplc to properly handle list discards
This commit is contained in:
parent
a83220c8d9
commit
444bccf19c
|
@ -1,7 +1,7 @@
|
|||
use std::{collections::HashMap, ops::Deref, rc::Rc};
|
||||
|
||||
use indexmap::{IndexMap, IndexSet};
|
||||
use itertools::Itertools;
|
||||
use itertools::{Itertools, Position};
|
||||
use uplc::{
|
||||
ast::{Constant as UplcConstant, Name, Term, Type as UplcType},
|
||||
builder::{CONSTR_FIELDS_EXPOSER, CONSTR_INDEX_EXPOSER},
|
||||
|
@ -1530,39 +1530,50 @@ pub fn list_access_to_uplc(
|
|||
error_term: Term<Name>,
|
||||
) -> Term<Name> {
|
||||
let names_len = names_types_ids.len();
|
||||
// Should never be expect level none on a list
|
||||
assert!(!(matches!(expect_level, ExpectLevel::None) && is_list_accessor));
|
||||
|
||||
let mut no_tailing_discards = names_types_ids
|
||||
.iter()
|
||||
.rev()
|
||||
.skip_while(|(name, _, _)| name == "_")
|
||||
.with_position()
|
||||
.skip_while(|pos| match pos {
|
||||
// Items are reversed order
|
||||
Position::Last((name, _, _)) | Position::Middle((name, _, _)) => {
|
||||
name == "_" && matches!(expect_level, ExpectLevel::None)
|
||||
}
|
||||
Position::First((name, _, _)) | Position::Only((name, _, _)) => {
|
||||
name == "_" && (tail_present || matches!(expect_level, ExpectLevel::None))
|
||||
}
|
||||
})
|
||||
.map(|position| match position {
|
||||
Position::First(a) | Position::Middle(a) | Position::Last(a) | Position::Only(a) => a,
|
||||
})
|
||||
.collect_vec();
|
||||
|
||||
// If the the is just discards and check_last_item then we check for empty list
|
||||
// If is just discards and check_last_item then we check for empty list
|
||||
if no_tailing_discards.is_empty() {
|
||||
if !tail_present && matches!(expect_level, ExpectLevel::Full | ExpectLevel::Items) {
|
||||
if tail_present || matches!(expect_level, ExpectLevel::None) {
|
||||
return term.lambda("_");
|
||||
} else {
|
||||
return Term::var("empty_list")
|
||||
.delayed_choose_list(term, error_term)
|
||||
.lambda("empty_list");
|
||||
} else {
|
||||
return term.lambda("_");
|
||||
}
|
||||
}
|
||||
|
||||
// reverse back to original order
|
||||
no_tailing_discards.reverse();
|
||||
|
||||
let no_tailing_len = no_tailing_discards.len();
|
||||
|
||||
// If we cut off at least one element then that was tail and possibly some heads
|
||||
let tail_wasnt_cutoff = tail_present && no_tailing_discards.len() == names_len;
|
||||
|
||||
no_tailing_discards.into_iter().enumerate().rev().fold(
|
||||
term,
|
||||
|acc, (index, (name, tipo, id))| {
|
||||
let tail_name = format!("tail_index_{}_{}", index, id);
|
||||
let tail_name = |id| format!("tail_id_{}", id);
|
||||
|
||||
let head_list =
|
||||
if matches!(tipo.get_uplc_type(), UplcType::Pair(_, _)) && is_list_accessor {
|
||||
let head_item = |name, tipo: &Rc<Type>, tail_name: &str| {
|
||||
if name == "_" {
|
||||
Term::unit()
|
||||
} else if matches!(tipo.get_uplc_type(), UplcType::Pair(_, _)) && is_list_accessor {
|
||||
Term::head_list().apply(Term::var(tail_name.to_string()))
|
||||
} else if matches!(expect_level, ExpectLevel::Full) && error_term != Term::Error {
|
||||
convert_data_to_type_debug(
|
||||
|
@ -1575,28 +1586,42 @@ pub fn list_access_to_uplc(
|
|||
Term::head_list().apply(Term::var(tail_name.to_string())),
|
||||
&tipo.to_owned(),
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
// handle tail case
|
||||
// name is guaranteed to not be discard at this point
|
||||
if index == no_tailing_len - 1 && tail_wasnt_cutoff {
|
||||
// simply lambda for tail name
|
||||
// Remember we reverse here so the First or Only is the last item
|
||||
no_tailing_discards
|
||||
.into_iter()
|
||||
.rev()
|
||||
.with_position()
|
||||
.fold(term, |acc, position| {
|
||||
match position {
|
||||
Position::First((name, _, _)) | Position::Only((name, _, _))
|
||||
if tail_wasnt_cutoff =>
|
||||
{
|
||||
// case for tail as the last item
|
||||
acc.lambda(name)
|
||||
} else if index == no_tailing_len - 1 {
|
||||
// case for no tail
|
||||
// name is guaranteed to not be discard at this point
|
||||
}
|
||||
|
||||
Position::First((name, tipo, id)) | Position::Only((name, tipo, id)) => {
|
||||
// case for no tail, but last item
|
||||
let tail_name = tail_name(id);
|
||||
|
||||
let head_item = head_item(name, tipo, &tail_name);
|
||||
|
||||
match expect_level {
|
||||
ExpectLevel::None => acc.lambda(name).apply(head_list).lambda(tail_name),
|
||||
ExpectLevel::None => acc.lambda(name).apply(head_item).lambda(tail_name),
|
||||
|
||||
ExpectLevel::Full | ExpectLevel::Items => {
|
||||
if error_term == Term::Error && tail_present {
|
||||
acc.lambda(name).apply(head_list).lambda(tail_name)
|
||||
// No need to check last item if tail was present
|
||||
acc.lambda(name).apply(head_item).lambda(tail_name)
|
||||
} else if tail_present {
|
||||
// Custom error instead of trying to do head_list on a possibly empty list.
|
||||
// Custom error instead of trying to do head_item on a possibly empty list.
|
||||
Term::var(tail_name.to_string())
|
||||
.delayed_choose_list(
|
||||
error_term.clone(),
|
||||
acc.lambda(name).apply(head_list),
|
||||
acc.lambda(name).apply(head_item),
|
||||
)
|
||||
.lambda(tail_name)
|
||||
} else if error_term == Term::Error {
|
||||
|
@ -1605,7 +1630,7 @@ pub fn list_access_to_uplc(
|
|||
.apply(Term::var(tail_name.to_string()))
|
||||
.delayed_choose_list(acc, error_term.clone())
|
||||
.lambda(name)
|
||||
.apply(head_list)
|
||||
.apply(head_item)
|
||||
.lambda(tail_name)
|
||||
} else {
|
||||
// Custom error if list is not empty after this head
|
||||
|
@ -1616,41 +1641,41 @@ pub fn list_access_to_uplc(
|
|||
.apply(Term::var(tail_name.to_string()))
|
||||
.delayed_choose_list(acc, error_term.clone())
|
||||
.lambda(name)
|
||||
.apply(head_list),
|
||||
.apply(head_item),
|
||||
)
|
||||
.lambda(tail_name)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if name == "_" {
|
||||
}
|
||||
|
||||
Position::Middle((name, tipo, id)) | Position::Last((name, tipo, id)) => {
|
||||
// case for every item except the last item
|
||||
let tail_name = tail_name(id);
|
||||
|
||||
let head_item = head_item(name, tipo, &tail_name);
|
||||
|
||||
if matches!(expect_level, ExpectLevel::None) || error_term == Term::Error {
|
||||
acc.apply(Term::tail_list().apply(Term::var(tail_name.to_string())))
|
||||
.lambda(name)
|
||||
.apply(head_item)
|
||||
.lambda(tail_name)
|
||||
} else {
|
||||
// case for a custom error if the list is empty at this point
|
||||
Term::var(tail_name.to_string())
|
||||
.delayed_choose_list(
|
||||
error_term.clone(),
|
||||
acc.apply(Term::tail_list().apply(Term::var(tail_name.to_string()))),
|
||||
acc.apply(
|
||||
Term::tail_list().apply(Term::var(tail_name.to_string())),
|
||||
)
|
||||
.lambda(name)
|
||||
.apply(head_item),
|
||||
)
|
||||
.lambda(tail_name)
|
||||
}
|
||||
} else if matches!(expect_level, ExpectLevel::None) || error_term == Term::Error {
|
||||
acc.apply(Term::tail_list().apply(Term::var(tail_name.to_string())))
|
||||
.lambda(name)
|
||||
.apply(head_list)
|
||||
.lambda(tail_name)
|
||||
} else {
|
||||
Term::var(tail_name.to_string())
|
||||
.delayed_choose_list(
|
||||
error_term.clone(),
|
||||
acc.apply(Term::tail_list().apply(Term::var(tail_name.to_string())))
|
||||
.lambda(name)
|
||||
.apply(head_list),
|
||||
)
|
||||
.lambda(tail_name)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn apply_builtin_forces(mut term: Term<Name>, force_count: u32) -> Term<Name> {
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
# This file was generated by Aiken
|
||||
# You typically do not need to edit this file
|
||||
|
||||
[[requirements]]
|
||||
name = "aiken-lang/stdlib"
|
||||
version = "main"
|
||||
source = "github"
|
||||
|
||||
[[packages]]
|
||||
name = "aiken-lang/stdlib"
|
||||
version = "main"
|
||||
requirements = []
|
||||
source = "github"
|
||||
|
||||
[etags]
|
||||
"aiken-lang/stdlib@main" = [{ secs_since_epoch = 1706674613, nanos_since_epoch = 871553000 }, "cf946239d3dd481ed41f20e56bf24910b5229ea35aa171a708edc2a47fc20a7b"]
|
|
@ -0,0 +1,8 @@
|
|||
name = "aiken-lang/acceptance_test_070"
|
||||
version = '0.0.0'
|
||||
description = ''
|
||||
|
||||
[[dependencies]]
|
||||
name = 'aiken-lang/stdlib'
|
||||
version = 'main'
|
||||
source = 'github'
|
|
@ -0,0 +1,47 @@
|
|||
use aiken/list
|
||||
use aiken/transaction.{InlineDatum, Input, OutputReference, TransactionId}
|
||||
|
||||
type OtherInput {
|
||||
output_reference: OutputReference,
|
||||
other: Data,
|
||||
}
|
||||
|
||||
type MyDatum<a> {
|
||||
Constructor1(a)
|
||||
Constructor2
|
||||
}
|
||||
|
||||
test discard_partitions() {
|
||||
let all_inputs =
|
||||
[
|
||||
OtherInput(OutputReference(TransactionId(#"aabb"), 2), 3),
|
||||
OtherInput(OutputReference(TransactionId(#"aabbcc"), 3), 3),
|
||||
]
|
||||
|
||||
let own_out_ref = OutputReference(TransactionId(#"aabb"), 2)
|
||||
|
||||
expect ([_], other_inputs) =
|
||||
list.partition(
|
||||
all_inputs,
|
||||
fn(input) { input.output_reference == own_out_ref },
|
||||
)
|
||||
|
||||
let inputs: List<Input> =
|
||||
[]
|
||||
|
||||
list.all(
|
||||
inputs,
|
||||
fn(input) {
|
||||
expect dat: MyDatum<Int> =
|
||||
when input.output.datum is {
|
||||
InlineDatum(d) -> d
|
||||
_ -> fail @"Not an inline datum"
|
||||
}
|
||||
|
||||
when dat is {
|
||||
Constructor1 { .. } -> True
|
||||
_ -> False
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
Loading…
Reference in New Issue