diff --git a/crates/aiken-lang/src/ast.rs b/crates/aiken-lang/src/ast.rs index cb3c9e94..38cb41ea 100644 --- a/crates/aiken-lang/src/ast.rs +++ b/crates/aiken-lang/src/ast.rs @@ -1243,6 +1243,12 @@ pub enum Pattern { tipo: Type, }, + Pair { + location: Span, + fst: Box, + snd: Box, + }, + Tuple { location: Span, elems: Vec, @@ -1258,6 +1264,7 @@ impl Pattern { | Pattern::List { location, .. } | Pattern::Discard { location, .. } | Pattern::Tuple { location, .. } + | Pattern::Pair { location, .. } | Pattern::Constructor { location, .. } => *location, } } @@ -1327,6 +1334,19 @@ impl TypedPattern { _ => None, }, + Pattern::Pair { fst, snd, .. } => match &**value { + Type::Pair { + fst: fst_v, + snd: snd_v, + .. + } => [fst, snd] + .into_iter() + .zip([fst_v, snd_v].iter()) + .find_map(|(e, t)| e.find_node(byte_index, t)) + .or(Some(Located::Pattern(self, value.clone()))), + _ => None, + }, + Pattern::Constructor { arguments, tipo, .. } => match &**tipo { @@ -1340,6 +1360,7 @@ impl TypedPattern { } } + // TODO: This function definition is weird, see where this is used and how. pub fn tipo(&self, value: &TypedExpr) -> Option> { match self { Pattern::Int { .. } => Some(builtins::int()), @@ -1347,7 +1368,7 @@ impl TypedPattern { Pattern::Var { .. } | Pattern::Assign { .. } | Pattern::Discard { .. } => { Some(value.tipo()) } - Pattern::List { .. } | Pattern::Tuple { .. } => None, + Pattern::List { .. } | Pattern::Tuple { .. } | Pattern::Pair { .. } => None, } } } diff --git a/crates/aiken-lang/src/format.rs b/crates/aiken-lang/src/format.rs index bb715843..7b77b1df 100644 --- a/crates/aiken-lang/src/format.rs +++ b/crates/aiken-lang/src/format.rs @@ -1779,6 +1779,14 @@ impl<'comments> Formatter<'comments> { wrap_args(elems.iter().map(|e| (self.pattern(e), false))).group() } + Pattern::Pair { fst, snd, .. } => "Pair" + .to_doc() + .append("(") + .append(self.pattern(fst)) + .append(",") + .append(self.pattern(snd)) + .append(")"), + Pattern::List { elements, tail, .. } => { let elements_document = join(elements.iter().map(|e| self.pattern(e)), break_(",", ", ")); diff --git a/crates/aiken-lang/src/gen_uplc.rs b/crates/aiken-lang/src/gen_uplc.rs index 7867343b..c79e774d 100644 --- a/crates/aiken-lang/src/gen_uplc.rs +++ b/crates/aiken-lang/src/gen_uplc.rs @@ -975,6 +975,7 @@ impl<'a> CodeGenerator<'a> { AirTree::let_assignment(name, value, then) } } + Pattern::Assign { name, pattern, .. } => { let inner_pattern = self.assignment( pattern, @@ -985,6 +986,7 @@ impl<'a> CodeGenerator<'a> { ); AirTree::let_assignment(name, value, inner_pattern) } + Pattern::Discard { name, .. } => { if props.full_check { let name = &format!("__discard_expect_{}", name); @@ -1015,6 +1017,7 @@ impl<'a> CodeGenerator<'a> { AirTree::no_op(then) } } + Pattern::List { elements, tail, .. } => { assert!(tipo.is_list()); assert!(props.kind.is_expect()); @@ -1141,47 +1144,33 @@ impl<'a> CodeGenerator<'a> { ) } } - // Pairs overlap by using the Constructor pattern type - // The logic is slightly different for pairs - Pattern::Constructor { - arguments, - constructor: PatternConstructor::Record { name, field_map }, - tipo: constr_tipo, - .. - } if tipo.is_pair() => { - // Constr and Pair execution branch - let field_map = field_map.clone(); + Pattern::Pair { + fst, + snd, + location: _, + } => { let mut type_map: IndexMap> = IndexMap::new(); - for (index, arg) in constr_tipo.get_inner_types().iter().enumerate() { + for (index, arg) in tipo.get_inner_types().iter().enumerate() { let field_type = arg.clone(); - type_map.insert(index, field_type); } - assert!(type_map.len() >= arguments.len()); + assert!(type_map.len() == 2); let mut fields = vec![]; - let then = arguments + let then = [fst, snd] .iter() .enumerate() - .rfold(then, |then, (index, arg)| { - let label = arg.label.clone().unwrap_or_default(); - - let field_index = if let Some(field_map) = &field_map { - *field_map.fields.get(&label).map(|x| &x.0).unwrap_or(&index) - } else { - index - }; - - let field_name = match &arg.value { + .rfold(then, |then, (field_index, arg)| { + let field_name = match arg.as_ref() { Pattern::Var { name, .. } => name.to_string(), Pattern::Assign { name, .. } => name.to_string(), Pattern::Discard { name, .. } => { if props.full_check { - format!("__discard_{}_{}", name, index) + format!("__discard_{}_{}", name, field_index) } else { "_".to_string() } @@ -1189,15 +1178,15 @@ impl<'a> CodeGenerator<'a> { _ => format!( "field_{}_span_{}_{}", field_index, - arg.value.location().start, - arg.value.location().end + arg.location().start, + arg.location().end ), }; let arg_type = type_map.get(&field_index).unwrap_or_else(|| { unreachable!( "Missing type for field {} of constr {}", - field_index, name + field_index, field_name ) }); @@ -1205,7 +1194,7 @@ impl<'a> CodeGenerator<'a> { let then = if field_name != "_" { self.assignment( - &arg.value, + arg, val, then, arg_type, @@ -1232,7 +1221,7 @@ impl<'a> CodeGenerator<'a> { // local var let constructor_name = format!( "__constructor_{}_span_{}_{}", - name, + "Pair", pattern.location().start, pattern.location().end ); @@ -1421,6 +1410,7 @@ impl<'a> CodeGenerator<'a> { AirTree::let_assignment(constructor_name, value, then) } + Pattern::Tuple { elems, location, .. } => { @@ -2529,75 +2519,40 @@ impl<'a> CodeGenerator<'a> { (AirTree::void(), list_assign) } - Pattern::Constructor { - name, - arguments, - constructor, - tipo: function_tipo, - .. - } if subject_tipo.is_pair() => { - assert!( - matches!(function_tipo.as_ref().clone(), Type::Fn { .. }) - || matches!(function_tipo.as_ref().clone(), Type::App { .. }) - ); + Pattern::Pair { fst, snd, .. } => { + let items_type = subject_tipo.get_inner_types(); - let field_map = match constructor { - PatternConstructor::Record { field_map, .. } => field_map.clone(), - }; - - let mut type_map: IndexMap> = IndexMap::new(); - - for (index, arg) in function_tipo.arg_types().unwrap().iter().enumerate() { - let field_type = arg.clone(); - type_map.insert(index, field_type); - } - - let mut fields = vec![]; + let mut name_index_assigns = vec![]; let next_then = - arguments + [fst, snd] .iter() .enumerate() - .rfold(then, |inner_then, (index, arg)| { - let label = arg.label.clone().unwrap_or_default(); - - let field_index = if let Some(field_map) = &field_map { - *field_map.fields.get(&label).map(|x| &x.0).unwrap_or(&index) - } else { - index - }; - - let field_name = match &arg.value { + .rfold(then, |inner_then, (index, element)| { + let elem_name = match element.as_ref() { Pattern::Var { name, .. } => Some(name.to_string()), Pattern::Assign { name, .. } => Some(name.to_string()), Pattern::Discard { .. } => None, _ => Some(format!( - "field_{}_span_{}_{}", - field_index, - arg.value.location().start, - arg.value.location().end + "pair_index_{}_span_{}_{}", + index, + element.location().start, + element.location().end )), }; - let arg_type = type_map.get(&field_index).unwrap_or_else(|| { - unreachable!( - "Missing type for field {} of constr {}", - field_index, name - ) - }); - - let mut field_props = ClauseProperties::init_inner( - arg_type, - field_name.clone().unwrap_or_else(|| "_".to_string()), - field_name.clone().unwrap_or_else(|| "_".to_string()), + let mut pair_props = ClauseProperties::init_inner( + &items_type[index], + elem_name.clone().unwrap_or_else(|| "_".to_string()), + elem_name.clone().unwrap_or_else(|| "_".to_string()), props.final_clause, ); - let statement = if field_name.is_some() { + let elem = if elem_name.is_some() { self.nested_clause_condition( - &arg.value, - arg_type, - &mut field_props, + element, + &items_type[index], + &mut pair_props, inner_then, ) } else { @@ -2605,21 +2560,21 @@ impl<'a> CodeGenerator<'a> { }; props.complex_clause = - props.complex_clause || field_props.complex_clause; + props.complex_clause || pair_props.complex_clause; - fields.push((field_name, arg_type.clone())); + name_index_assigns.push((elem_name, index)); - statement + elem }); - fields.reverse(); + name_index_assigns.reverse(); - let field_assign = if fields.iter().all(|s| s.0.is_none()) { + let field_assign = if name_index_assigns.iter().all(|s| s.0.is_none()) { next_then } else { AirTree::pair_access( - fields[0].0.clone(), - fields[1].0.clone(), + name_index_assigns[0].0.clone(), + name_index_assigns[1].0.clone(), subject_tipo.clone(), AirTree::local_var(props.clause_var_name.clone(), subject_tipo.clone()), None, @@ -3020,9 +2975,8 @@ impl<'a> CodeGenerator<'a> { } } - Pattern::Constructor { .. } if subject_tipo.is_pair() => { + Pattern::Pair { .. } => { let (_, assign) = self.clause_pattern(pattern, subject_tipo, props, then); - assign } diff --git a/crates/aiken-lang/src/gen_uplc/builder.rs b/crates/aiken-lang/src/gen_uplc/builder.rs index b12cca27..1ee6c66a 100644 --- a/crates/aiken-lang/src/gen_uplc/builder.rs +++ b/crates/aiken-lang/src/gen_uplc/builder.rs @@ -679,24 +679,19 @@ pub fn pattern_has_conditions( Pattern::Tuple { elems, .. } => elems .iter() .any(|elem| pattern_has_conditions(elem, data_types)), + Pattern::Pair { fst, snd, .. } => { + pattern_has_conditions(fst, data_types) || pattern_has_conditions(snd, data_types) + } Pattern::Constructor { arguments, tipo, .. } => { - if tipo.is_pair() - || (tipo.is_function() && tipo.return_type().map(|t| t.is_pair()).unwrap_or(false)) - { - arguments + 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)) - } 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.rs b/crates/aiken-lang/src/tipo.rs index a7b46151..156045ef 100644 --- a/crates/aiken-lang/src/tipo.rs +++ b/crates/aiken-lang/src/tipo.rs @@ -433,6 +433,10 @@ impl Type { } } + // TODO: Self::App { args, ..} looks fishy, because App's args are referring + // to _type parameters_ not to value types unlike Fn's args. So this function + // definition is probably wrong. Luckily, we likely never hit the `Self::App` + // case at all. pub fn arg_types(&self) -> Option>> { match self { Self::Fn { args, .. } => Some(args.clone()), diff --git a/crates/aiken-lang/src/tipo/exhaustive.rs b/crates/aiken-lang/src/tipo/exhaustive.rs index ab310eca..a69dbf99 100644 --- a/crates/aiken-lang/src/tipo/exhaustive.rs +++ b/crates/aiken-lang/src/tipo/exhaustive.rs @@ -1,12 +1,10 @@ -use std::{collections::BTreeMap, iter, ops::Deref}; - -use itertools::Itertools; - use crate::{ ast, - builtins::{self, PAIR}, + builtins::{self}, tipo::{self, environment::Environment, error::Error}, }; +use itertools::Itertools; +use std::{collections::BTreeMap, iter, ops::Deref}; const NIL_NAME: &str = "[]"; const CONS_NAME: &str = "::"; @@ -88,8 +86,8 @@ impl PatternStack { Some(self.chain_tail_into_iter(vec![Pattern::Wildcard; arity].into_iter())) } Pattern::Literal(_) => unreachable!( - "constructors and literals should never align in pattern match exhaustiveness checks." - ), + "constructors and literals should never align in pattern match exhaustiveness checks." + ), } } @@ -564,8 +562,6 @@ 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, @@ -578,13 +574,11 @@ pub(super) fn simplify( module, .. } => (module, type_name, args.len()), - tipo::Type::Pair { .. } => (empty, pair, 2), _ => { - unreachable!("ret should be a Type::App or Type::Pair") + unreachable!("ret should be a Type::App") } }, - tipo::Type::Pair { .. } => (empty, pair, 2), - _ => unreachable!("tipo should be a Type::App or Type::Pair"), + _ => unreachable!("tipo should be a Type::App"), }; let alts = environment.get_constructors_for_type(module, type_name, *location)?; @@ -603,6 +597,13 @@ pub(super) fn simplify( Ok(Pattern::Constructor(name.to_string(), alts, args)) } + ast::Pattern::Pair { fst, snd, location } => simplify( + environment, + &ast::Pattern::Tuple { + elems: vec![*fst.clone(), *snd.clone()], + location: *location, + }, + ), ast::Pattern::Tuple { elems, .. } => { let mut args = vec![]; diff --git a/crates/aiken-lang/src/tipo/pattern.rs b/crates/aiken-lang/src/tipo/pattern.rs index f2f7582f..a601ff6c 100644 --- a/crates/aiken-lang/src/tipo/pattern.rs +++ b/crates/aiken-lang/src/tipo/pattern.rs @@ -8,7 +8,7 @@ use super::{ }; use crate::{ ast::{CallArg, Pattern, Span, TypedPattern, UntypedPattern}, - builtins::{int, list, tuple}, + builtins::{int, list, pair, tuple}, }; use itertools::Itertools; use std::{ @@ -236,6 +236,46 @@ impl<'a, 'b> PatternTyper<'a, 'b> { }), }, + Pattern::Pair { fst, snd, location } => match collapse_links(tipo.clone()).deref() { + Type::Pair { + fst: t_fst, + snd: t_snd, + .. + } => { + let fst = Box::new(self.unify(*fst, t_fst.clone(), None, false)?); + let snd = Box::new(self.unify(*snd, t_snd.clone(), None, false)?); + Ok(Pattern::Pair { fst, snd, location }) + } + + Type::Var { .. } => { + let t_fst = self.environment.new_unbound_var(); + let t_snd = self.environment.new_unbound_var(); + + self.environment.unify( + pair(t_fst.clone(), t_snd.clone()), + tipo, + location, + false, + )?; + + let fst = Box::new(self.unify(*fst, t_fst, None, false)?); + let snd = Box::new(self.unify(*snd, t_snd, None, false)?); + + Ok(Pattern::Pair { fst, snd, location }) + } + + _ => Err(Error::CouldNotUnify { + given: pair( + self.environment.new_unbound_var(), + self.environment.new_unbound_var(), + ), + expected: tipo, + situation: None, + location, + rigid_type_names: HashMap::new(), + }), + }, + Pattern::Tuple { elems, location } => match collapse_links(tipo.clone()).deref() { Type::Tuple { elems: type_elems, ..