Start work on revamping assignment so we can handle soft casting properly

This commit is contained in:
microproofs
2024-07-28 16:14:41 -04:00
committed by Kasey
parent 9ea54afd12
commit dd5badd884
4 changed files with 529 additions and 268 deletions

View File

@@ -101,6 +101,10 @@ pub enum Air {
Let {
name: String,
},
SoftCastLet {
name: String,
tipo: Rc<Type>,
},
CastFromData {
tipo: Rc<Type>,
full_cast: bool,

View File

@@ -70,7 +70,7 @@ pub struct AssignmentProperties {
pub kind: TypedAssignmentKind,
pub remove_unused: bool,
pub full_check: bool,
pub otherwise: AirTree,
pub otherwise: Option<AirTree>,
}
#[derive(Clone, Debug)]
@@ -948,9 +948,11 @@ pub fn unknown_data_to_type(term: Term<Name>, field_type: &Type) -> Term<Name> {
/// 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<Name>,
pub fn softcast_data_to_type_otherwise(
value: Term<Name>,
name: &String,
field_type: &Type,
then: Term<Name>,
otherwise_delayed: Term<Name>,
) -> Term<Name> {
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<Type>, tail_name: &str| {
let head_item = |name, tipo: &Rc<Type>, tail_name: &str, then: Term<Name>| {
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)

View File

@@ -125,6 +125,13 @@ pub enum AirTree {
value: Box<AirTree>,
then: Box<AirTree>,
},
SoftCastLet {
name: String,
tipo: Rc<Type>,
value: Box<AirTree>,
then: Box<AirTree>,
otherwise: Box<AirTree>,
},
DefineFunc {
func_name: String,
module_name: String,
@@ -312,7 +319,6 @@ pub enum AirTree {
CastFromData {
tipo: Rc<Type>,
value: Box<AirTree>,
otherwise: Box<AirTree>,
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<Type>,
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<Type>,
otherwise: AirTree,
full_cast: bool,
) -> AirTree {
pub fn cast_from_data(value: AirTree, tipo: Rc<Type>, 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 {