diff --git a/crates/aiken-lang/src/gen_uplc.rs b/crates/aiken-lang/src/gen_uplc.rs index d9475b0b..3aa38f10 100644 --- a/crates/aiken-lang/src/gen_uplc.rs +++ b/crates/aiken-lang/src/gen_uplc.rs @@ -372,32 +372,42 @@ impl<'a> CodeGenerator<'a> { }, .. } => { - let data_type = lookup_data_type_by_tipo(&self.data_types, tipo) - .expect("Creating a record with no record definition."); + if tipo.is_pair() { + assert!(args.len() == 2); - let (constr_index, _) = data_type - .constructors - .iter() - .enumerate() - .find(|(_, dt)| &dt.name == constr_name) - .unwrap(); + let arg1 = self.build(&args[0].value, module_build_name, &[]); - let constr_args = args - .iter() - .zip(constr_tipo.arg_types().unwrap()) - .map(|(arg, tipo)| { - if tipo.is_data() { - AirTree::cast_to_data( - self.build(&arg.value, module_build_name, &[]), - arg.value.tipo(), - ) - } else { - self.build(&arg.value, module_build_name, &[]) - } - }) - .collect_vec(); + let arg2 = self.build(&args[1].value, module_build_name, &[]); - AirTree::create_constr(constr_index, constr_tipo.clone(), constr_args) + AirTree::pair(arg1, arg2, tipo.clone()) + } else { + let data_type = lookup_data_type_by_tipo(&self.data_types, tipo) + .expect("Creating a record with no record definition."); + + let (constr_index, _) = data_type + .constructors + .iter() + .enumerate() + .find(|(_, dt)| &dt.name == constr_name) + .unwrap(); + + let constr_args = args + .iter() + .zip(constr_tipo.arg_types().unwrap()) + .map(|(arg, tipo)| { + if tipo.is_data() { + AirTree::cast_to_data( + self.build(&arg.value, module_build_name, &[]), + arg.value.tipo(), + ) + } else { + self.build(&arg.value, module_build_name, &[]) + } + }) + .collect_vec(); + + AirTree::create_constr(constr_index, constr_tipo.clone(), constr_args) + } } TypedExpr::Var { @@ -5420,12 +5430,13 @@ impl<'a> CodeGenerator<'a> { match (extract_constant(&fst), extract_constant(&snd)) { (Some(fst), Some(snd)) => { + let mut pair_fields = builder::convert_constants_to_data(vec![fst, snd]); let term = Term::Constant( UplcConstant::ProtoPair( UplcType::Data, UplcType::Data, - fst.clone(), - snd.clone(), + pair_fields.remove(0).into(), + pair_fields.remove(0).into(), ) .into(), ); diff --git a/crates/aiken-lang/src/gen_uplc/builder.rs b/crates/aiken-lang/src/gen_uplc/builder.rs index 642f7507..7da7e059 100644 --- a/crates/aiken-lang/src/gen_uplc/builder.rs +++ b/crates/aiken-lang/src/gen_uplc/builder.rs @@ -695,13 +695,21 @@ pub fn pattern_has_conditions( Pattern::Constructor { arguments, tipo, .. } => { - let data_type = - lookup_data_type_by_tipo(data_types, tipo).expect("Data type not found"); - - data_type.constructors.len() > 1 - || arguments + if tipo.is_pair() + || (tipo.is_function() && tipo.return_type().map(|t| t.is_pair()).unwrap_or(false)) + { + arguments .iter() .any(|arg| pattern_has_conditions(&arg.value, data_types)) + } else { + let data_type = lookup_data_type_by_tipo(data_types, tipo) + .unwrap_or_else(|| panic!("Data type not found: {:#?}", tipo)); + + data_type.constructors.len() > 1 + || arguments + .iter() + .any(|arg| pattern_has_conditions(&arg.value, data_types)) + } } Pattern::Assign { pattern, .. } => pattern_has_conditions(pattern, data_types), Pattern::Var { .. } | Pattern::Discard { .. } => false, diff --git a/crates/aiken-lang/src/tipo/environment.rs b/crates/aiken-lang/src/tipo/environment.rs index e74a82e2..bc608e27 100644 --- a/crates/aiken-lang/src/tipo/environment.rs +++ b/crates/aiken-lang/src/tipo/environment.rs @@ -1495,94 +1495,172 @@ impl<'a> Environment<'a> { .map_err(|e| e.flip_unify()); } - match (lhs.deref(), rhs.deref()) { - ( - Type::App { - module: m1, - name: n1, - args: args1, - public: _, - contains_opaque: _, - alias: _, - }, - Type::App { + match lhs.deref() { + Type::App { + module: m1, + name: n1, + args: args1, + public: _, + contains_opaque: _, + alias: _, + } => { + if let Type::App { module: m2, name: n2, args: args2, public: _, contains_opaque: _, alias: _, - }, - ) if m1 == m2 && n1 == n2 && args1.len() == args2.len() => { - for (a, b) in args1.iter().zip(args2) { - unify_enclosed_type( - lhs.clone(), - rhs.clone(), - self.unify(a.clone(), b.clone(), location, false), - )?; - } - Ok(()) - } - - ( - Type::Tuple { - elems: elems1, - alias: _, - }, - Type::Tuple { - elems: elems2, - alias: _, - }, - ) if elems1.len() == elems2.len() => { - for (a, b) in elems1.iter().zip(elems2) { - unify_enclosed_type( - lhs.clone(), - rhs.clone(), - self.unify(a.clone(), b.clone(), location, false), - )?; - } - Ok(()) - } - - ( - Type::Fn { - args: args1, - ret: retrn1, - alias: _, - }, - Type::Fn { - args: args2, - ret: retrn2, - alias: _, - }, - ) if args1.len() == args2.len() => { - for (a, b) in args1.iter().zip(args2) { - self.unify(a.clone(), b.clone(), location, allow_cast) - .map_err(|_| Error::CouldNotUnify { + } = rhs.deref() + { + if m1 == m2 && n1 == n2 && args1.len() == args2.len() { + for (a, b) in args1.iter().zip(args2) { + unify_enclosed_type( + lhs.clone(), + rhs.clone(), + self.unify(a.clone(), b.clone(), location, false), + )?; + } + Ok(()) + } else { + Err(Error::CouldNotUnify { location, expected: lhs.clone(), given: rhs.clone(), situation: None, rigid_type_names: HashMap::new(), - })?; - } - self.unify(retrn1.clone(), retrn2.clone(), location, false) - .map_err(|_| Error::CouldNotUnify { + }) + } + } else { + Err(Error::CouldNotUnify { location, expected: lhs.clone(), given: rhs.clone(), situation: None, rigid_type_names: HashMap::new(), }) + } } - _ => Err(Error::CouldNotUnify { - location, - expected: lhs.clone(), - given: rhs.clone(), - situation: None, - rigid_type_names: HashMap::new(), - }), + Type::Tuple { + elems: elems1, + alias: _, + } => { + if let Type::Tuple { + elems: elems2, + alias: _, + } = rhs.deref() + { + if elems1.len() == elems2.len() { + for (a, b) in elems1.iter().zip(elems2) { + unify_enclosed_type( + lhs.clone(), + rhs.clone(), + self.unify(a.clone(), b.clone(), location, false), + )?; + } + Ok(()) + } else { + Err(Error::CouldNotUnify { + location, + expected: lhs.clone(), + given: rhs.clone(), + situation: None, + rigid_type_names: HashMap::new(), + }) + } + } else { + Err(Error::CouldNotUnify { + location, + expected: lhs.clone(), + given: rhs.clone(), + situation: None, + rigid_type_names: HashMap::new(), + }) + } + } + + Type::Fn { + args: args1, + ret: retrn1, + alias: _, + } => { + if let Type::Fn { + args: args2, + ret: retrn2, + alias: _, + } = rhs.deref() + { + if args1.len() == args2.len() { + for (a, b) in args1.iter().zip(args2) { + self.unify(a.clone(), b.clone(), location, allow_cast) + .map_err(|_| Error::CouldNotUnify { + location, + expected: lhs.clone(), + given: rhs.clone(), + situation: None, + rigid_type_names: HashMap::new(), + })?; + } + self.unify(retrn1.clone(), retrn2.clone(), location, false) + .map_err(|_| Error::CouldNotUnify { + location, + expected: lhs.clone(), + given: rhs.clone(), + situation: None, + rigid_type_names: HashMap::new(), + }) + } else { + Err(Error::CouldNotUnify { + location, + expected: lhs.clone(), + given: rhs.clone(), + situation: None, + rigid_type_names: HashMap::new(), + }) + } + } else { + Err(Error::CouldNotUnify { + location, + expected: lhs.clone(), + given: rhs.clone(), + situation: None, + rigid_type_names: HashMap::new(), + }) + } + } + Type::Pair { fst, snd, alias: _ } => { + if let Type::Pair { + fst: fst2, + snd: snd2, + alias: _, + } = rhs.deref() + { + unify_enclosed_type( + lhs.clone(), + rhs.clone(), + self.unify(fst.clone(), fst2.clone(), location, false), + )?; + + unify_enclosed_type( + lhs.clone(), + rhs.clone(), + self.unify(snd.clone(), snd2.clone(), location, false), + )?; + + Ok(()) + } else { + Err(Error::CouldNotUnify { + location, + expected: lhs.clone(), + given: rhs.clone(), + situation: None, + rigid_type_names: HashMap::new(), + }) + } + } + + Type::Var { .. } => unreachable!(), } } @@ -1801,7 +1879,7 @@ fn unify_unbound_type(tipo: Rc, own_id: u64, location: Span) -> Result<(), Ok(()) } - Type::Pair { fst, snd, .. } => { + Type::Pair { fst, snd, alias: _ } => { unify_unbound_type(fst.clone(), own_id, location)?; unify_unbound_type(snd.clone(), own_id, location)?; diff --git a/crates/aiken-lang/src/tipo/exhaustive.rs b/crates/aiken-lang/src/tipo/exhaustive.rs index 12801be4..ab310eca 100644 --- a/crates/aiken-lang/src/tipo/exhaustive.rs +++ b/crates/aiken-lang/src/tipo/exhaustive.rs @@ -3,7 +3,8 @@ use std::{collections::BTreeMap, iter, ops::Deref}; use itertools::Itertools; use crate::{ - ast, builtins, + ast, + builtins::{self, PAIR}, tipo::{self, environment::Environment, error::Error}, }; @@ -563,6 +564,8 @@ pub(super) fn simplify( constructor: super::PatternConstructor::Record { name, .. }, .. } => { + let (empty, pair) = (&"".to_string(), &PAIR.to_string()); + let (module, type_name, arity) = match tipo.deref() { tipo::Type::App { name: type_name, @@ -575,11 +578,13 @@ pub(super) fn simplify( module, .. } => (module, type_name, args.len()), + tipo::Type::Pair { .. } => (empty, pair, 2), _ => { - unreachable!("ret should be a Type::App") + unreachable!("ret should be a Type::App or Type::Pair") } }, - _ => unreachable!("tipo should be a Type::App"), + tipo::Type::Pair { .. } => (empty, pair, 2), + _ => unreachable!("tipo should be a Type::App or Type::Pair"), }; let alts = environment.get_constructors_for_type(module, type_name, *location)?; diff --git a/crates/aiken-project/src/tests/gen_uplc.rs b/crates/aiken-project/src/tests/gen_uplc.rs index 8291573f..59b75e5d 100644 --- a/crates/aiken-project/src/tests/gen_uplc.rs +++ b/crates/aiken-project/src/tests/gen_uplc.rs @@ -680,10 +680,10 @@ fn acceptance_test_6_if_else() { } #[test] -fn acceptance_test_6_equals() { +fn acceptance_test_6_equals_pair() { let src = r#" test foo() { - (1, []) == (1, []) + Pair(1, []) == Pair(1, []) } "#; @@ -725,7 +725,46 @@ fn acceptance_test_6_equals() { } #[test] -fn acceptance_test_7_unzip() { +fn acceptance_test_6_equals_tuple() { + let src = r#" + test foo() { + (1, []) == (1, []) + } + "#; + + assert_uplc( + src, + Term::equals_data() + .apply( + Term::list_data().apply(Term::Constant( + Constant::ProtoList( + Type::Data, + vec![ + Constant::Data(Data::integer(1.into())), + Constant::Data(Data::list(vec![])), + ], + ) + .into(), + )), + ) + .apply( + Term::list_data().apply(Term::Constant( + Constant::ProtoList( + Type::Data, + vec![ + Constant::Data(Data::integer(1.into())), + Constant::Data(Data::list(vec![])), + ], + ) + .into(), + )), + ), + false, + ); +} + +#[test] +fn acceptance_test_7_unzip_tuple() { let src = r#" pub fn unzip(xs: List<(a, b)>) -> (List, List) { when xs is { @@ -744,6 +783,127 @@ fn acceptance_test_7_unzip() { } "#; + assert_uplc( + src, + Term::equals_data() + .apply( + Term::list_data().apply( + Term::var("unzip") + .lambda("unzip") + .apply(Term::var("unzip").apply(Term::var("unzip"))) + .lambda("unzip") + .apply( + Term::var("xs") + .delayed_choose_list( + Term::list_values(vec![ + Constant::Data(Data::list(vec![])), + Constant::Data(Data::list(vec![])), + ]), + Term::mk_cons() + .apply( + Term::list_data().apply( + Term::mk_cons() + .apply(Term::i_data().apply(Term::var("a"))) + .apply(Term::var("a_tail")), + ), + ) + .apply( + Term::mk_cons() + .apply( + Term::list_data().apply( + Term::mk_cons() + .apply( + Term::b_data() + .apply(Term::var("b")), + ) + .apply(Term::var("b_tail")), + ), + ) + .apply(Term::empty_list()), + ) + .lambda("b_tail") + .apply(Term::unlist_data().apply(Term::head_list().apply( + Term::tail_list().apply(Term::var("tail_tuple")), + ))) + .lambda("a_tail") + .apply(Term::unlist_data().apply( + Term::head_list().apply(Term::var("tail_tuple")), + )) + .lambda("tail_tuple") + .apply( + Term::var("unzip") + .apply(Term::var("unzip")) + .apply(Term::var("rest")), + ) + .lambda("b") + .apply(Term::un_b_data().apply(Term::head_list().apply( + Term::tail_list().apply(Term::var("head_tuple")), + ))) + .lambda("a") + .apply(Term::un_i_data().apply( + Term::head_list().apply(Term::var("head_tuple")), + )) + .lambda("rest") + .apply(Term::tail_list().apply(Term::var("xs"))) + .lambda("head_tuple") + .apply( + Term::unlist_data() + .apply(Term::head_list().apply(Term::var("xs"))), + ), + ) + .lambda("xs") + .lambda("unzip"), + ) + .apply(Term::var("x")), + ), + ) + .apply(Term::list_data().apply(Term::list_values(vec![ + Constant::Data(Data::list(vec![ + Data::integer(3.into()), + Data::integer(4.into()), + ])), + Constant::Data(Data::list(vec![ + Data::bytestring(vec![85]), + Data::bytestring(vec![119, 153]), + ])), + ]))) + .lambda("x") + .apply(Term::list_values(vec![ + Constant::Data(Data::list(vec![ + Data::integer(3.into()), + Data::bytestring(vec![85]), + ])), + Constant::Data(Data::list(vec![ + Data::integer(4.into()), + Data::bytestring(vec![119, 153]), + ])), + ])), + false, + ); +} + +#[test] +fn acceptance_test_7_unzip_pair() { + let src = r#" + type Map = List> + + pub fn unzip(xs: Map) -> Pair, List> { + when xs is { + [] -> Pair([], []) + [Pair(a, b), ..rest] -> { + let Pair(a_tail, b_tail) = unzip(rest) + Pair([a, ..a_tail], [b, ..b_tail]) + } + } + } + + test unzip1() { + let x = [Pair(3, #"55"), Pair(4, #"7799")] + + unzip(x) == Pair([3, 4], [#"55", #"7799"]) + } + "#; + assert_uplc( src, Term::equals_data() @@ -1414,7 +1574,7 @@ fn acceptance_test_14_list_creation() { fn acceptance_test_15_zero_arg() { let src = r#" pub opaque type Map { - inner: List<(key, value)>, + inner: List>, } pub fn new() { @@ -1956,48 +2116,51 @@ fn acceptance_test_22_filter_map() { #[test] fn acceptance_test_23_to_list() { let src = r#" - pub opaque type AssocList { - inner: List<(key, value)>, - } - - pub fn new() -> AssocList { - AssocList { inner: [] } - } - - pub fn to_list(m: AssocList) -> List<(key, value)> { - m.inner - } - - pub fn insert( - in m: AssocList, - key k: key, - value v: value, - ) -> AssocList { - AssocList { inner: do_insert(m.inner, k, v) } - } - - fn do_insert(elems: List<(key, value)>, k: key, v: value) -> List<(key, value)> { - when elems is { - [] -> - [(k, v)] - [(k2, v2), ..rest] -> - if k == k2 { - [(k, v), ..rest] - } else { - [(k2, v2), ..do_insert(rest, k, v)] + pub type Map = + List> + + pub opaque type AssocList { + inner: Map, + } + + pub fn new() -> AssocList { + AssocList { inner: [] } + } + + pub fn to_list(m: AssocList) -> Map { + m.inner + } + + pub fn insert( + in m: AssocList, + key k: key, + value v: value, + ) -> AssocList { + AssocList { inner: do_insert(m.inner, k, v) } + } + + fn do_insert(elems: Map, k: key, v: value) -> Map { + when elems is { + [] -> + [Pair(k, v)] + [Pair { fst: k2, snd: v2 }, ..rest] -> + if k == k2 { + [Pair(k, v), ..rest] + } else { + [Pair(k2, v2), ..do_insert(rest, k, v)] + } } } - } - - fn fixture_1() { - new() - |> insert("foo", 42) - |> insert("bar", 14) - } - - test to_list_2() { - to_list(fixture_1()) == [("foo", 42), ("bar", 14)] - } + + fn fixture_1() { + new() + |> insert("foo", 42) + |> insert("bar", 14) + } + + test to_list_2() { + to_list(fixture_1()) == [Pair("foo", 42), Pair("bar", 14)] + } "#; assert_uplc( @@ -2036,7 +2199,7 @@ fn acceptance_test_23_to_list() { } #[test] -fn acceptance_test_24_map2() { +fn acceptance_test_24_map_pair() { let src = r#" pub fn map2( opt_a: Option, @@ -2057,7 +2220,7 @@ fn acceptance_test_24_map2() { } test map2_3() { - map2(Some(14), Some(42), fn(a, b) { (a, b) }) == Some((14, 42)) + map2(Some(14), Some(42), fn(a, b) { Pair(a, b) }) == Some(Pair(14, 42)) } "#; @@ -2178,6 +2341,129 @@ fn acceptance_test_24_map2() { ); } +#[test] +fn acceptance_test_24_map2() { + let src = r#" + pub fn map2( + opt_a: Option, + opt_b: Option, + f: fn(a, b) -> result, + ) -> Option { + when opt_a is { + None -> + None + Some(a) -> + when opt_b is { + None -> + None + Some(b) -> + Some(f(a, b)) + } + } + } + + test map2_3() { + map2(Some(14), Some(42), fn(a, b) { (a, b) }) == Some((14, 42)) + } + "#; + + assert_uplc( + src, + Term::equals_data() + .apply( + Term::var("map2") + .lambda("map2") + .apply( + Term::equals_integer() + .apply(Term::integer(1.into())) + .apply(Term::var("opt_a_index")) + .delayed_if_then_else( + Term::Constant(Constant::Data(Data::constr(1, vec![])).into()), + Term::equals_integer() + .apply(Term::integer(1.into())) + .apply(Term::var("opt_b_index")) + .delayed_if_then_else( + Term::Constant( + Constant::Data(Data::constr(1, vec![])).into(), + ), + Term::constr_data() + .apply(Term::integer(0.into())) + .apply( + Term::mk_cons() + .apply( + Term::list_data().apply( + Term::var("f") + .apply(Term::var("a")) + .apply(Term::var("b")), + ), + ) + .apply(Term::empty_list()), + ) + .lambda("b") + .apply(Term::un_i_data().apply( + Term::head_list().apply(Term::var("opt_b_fields")), + )) + .lambda("opt_b_fields") + .apply( + Term::var(CONSTR_FIELDS_EXPOSER) + .apply(Term::var("opt_b")), + ), + ) + .lambda("opt_b_index") + .apply( + Term::var(CONSTR_INDEX_EXPOSER).apply(Term::var("opt_b")), + ) + .lambda("a") + .apply( + Term::un_i_data().apply( + Term::head_list().apply(Term::var("opt_a_fields")), + ), + ) + .lambda("opt_a_fields") + .apply( + Term::var(CONSTR_FIELDS_EXPOSER).apply(Term::var("opt_a")), + ), + ) + .lambda("opt_a_index") + .apply(Term::var(CONSTR_INDEX_EXPOSER).apply(Term::var("opt_a"))) + .lambda("f") + .lambda("opt_b") + .lambda("opt_a"), + ) + .apply(Term::Constant( + Constant::Data(Data::constr(0, vec![Data::integer(14.into())])).into(), + )) + .apply(Term::Constant( + Constant::Data(Data::constr(0, vec![Data::integer(42.into())])).into(), + )) + .apply( + Term::mk_cons() + .apply(Term::i_data().apply(Term::var("a"))) + .apply( + Term::mk_cons() + .apply(Term::i_data().apply(Term::var("b"))) + .apply(Term::empty_list()), + ) + .lambda("b") + .lambda("a"), + ), + ) + .apply(Term::Constant( + Constant::Data(Data::constr( + 0, + vec![Data::list(vec![ + Data::integer(14.into()), + Data::integer(42.into()), + ])], + )) + .into(), + )) + .constr_fields_exposer() + .constr_index_exposer(), + false, + ); +} + #[test] fn acceptance_test_25_void_equal() { let src = r#"