Add 'Pair' pattern and rework internals to use it.

Currently, pattern-matching on 'Pair' is handled by treating Pair as a
  record, which comes as slightly odd given that it isn't actually a
  record and isn't user-defined. Thus now, every use of a record must
  distinguish between Pairs and other kind of records -- which screams
  for another variant constructor instead.

  We cannot use `Tuple` either for this, because then we have no ways to
  tell 2-tuples apart from pairs, which is the whole point here. So the
  most sensical thing to do is to define a new pattern `Pair` which is
  akin to tuples, but simpler since we know the number of elements and
  it's always 2.
This commit is contained in:
KtorZ 2024-04-04 10:36:13 +02:00 committed by Kasey
parent 897b5d1d7e
commit 91a7e77ab4
7 changed files with 143 additions and 120 deletions

View File

@ -1243,6 +1243,12 @@ pub enum Pattern<Constructor, Type> {
tipo: Type, tipo: Type,
}, },
Pair {
location: Span,
fst: Box<Self>,
snd: Box<Self>,
},
Tuple { Tuple {
location: Span, location: Span,
elems: Vec<Self>, elems: Vec<Self>,
@ -1258,6 +1264,7 @@ impl<A, B> Pattern<A, B> {
| Pattern::List { location, .. } | Pattern::List { location, .. }
| Pattern::Discard { location, .. } | Pattern::Discard { location, .. }
| Pattern::Tuple { location, .. } | Pattern::Tuple { location, .. }
| Pattern::Pair { location, .. }
| Pattern::Constructor { location, .. } => *location, | Pattern::Constructor { location, .. } => *location,
} }
} }
@ -1327,6 +1334,19 @@ impl TypedPattern {
_ => None, _ => 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 { Pattern::Constructor {
arguments, tipo, .. arguments, tipo, ..
} => match &**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<Rc<Type>> { pub fn tipo(&self, value: &TypedExpr) -> Option<Rc<Type>> {
match self { match self {
Pattern::Int { .. } => Some(builtins::int()), Pattern::Int { .. } => Some(builtins::int()),
@ -1347,7 +1368,7 @@ impl TypedPattern {
Pattern::Var { .. } | Pattern::Assign { .. } | Pattern::Discard { .. } => { Pattern::Var { .. } | Pattern::Assign { .. } | Pattern::Discard { .. } => {
Some(value.tipo()) Some(value.tipo())
} }
Pattern::List { .. } | Pattern::Tuple { .. } => None, Pattern::List { .. } | Pattern::Tuple { .. } | Pattern::Pair { .. } => None,
} }
} }
} }

View File

@ -1779,6 +1779,14 @@ impl<'comments> Formatter<'comments> {
wrap_args(elems.iter().map(|e| (self.pattern(e), false))).group() 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, .. } => { Pattern::List { elements, tail, .. } => {
let elements_document = let elements_document =
join(elements.iter().map(|e| self.pattern(e)), break_(",", ", ")); join(elements.iter().map(|e| self.pattern(e)), break_(",", ", "));

View File

@ -975,6 +975,7 @@ impl<'a> CodeGenerator<'a> {
AirTree::let_assignment(name, value, then) AirTree::let_assignment(name, value, then)
} }
} }
Pattern::Assign { name, pattern, .. } => { Pattern::Assign { name, pattern, .. } => {
let inner_pattern = self.assignment( let inner_pattern = self.assignment(
pattern, pattern,
@ -985,6 +986,7 @@ impl<'a> CodeGenerator<'a> {
); );
AirTree::let_assignment(name, value, inner_pattern) AirTree::let_assignment(name, value, inner_pattern)
} }
Pattern::Discard { name, .. } => { Pattern::Discard { name, .. } => {
if props.full_check { if props.full_check {
let name = &format!("__discard_expect_{}", name); let name = &format!("__discard_expect_{}", name);
@ -1015,6 +1017,7 @@ impl<'a> CodeGenerator<'a> {
AirTree::no_op(then) AirTree::no_op(then)
} }
} }
Pattern::List { elements, tail, .. } => { Pattern::List { elements, tail, .. } => {
assert!(tipo.is_list()); assert!(tipo.is_list());
assert!(props.kind.is_expect()); 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<usize, Rc<Type>> = IndexMap::new(); let mut type_map: IndexMap<usize, Rc<Type>> = 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(); let field_type = arg.clone();
type_map.insert(index, field_type); type_map.insert(index, field_type);
} }
assert!(type_map.len() >= arguments.len()); assert!(type_map.len() == 2);
let mut fields = vec![]; let mut fields = vec![];
let then = arguments let then = [fst, snd]
.iter() .iter()
.enumerate() .enumerate()
.rfold(then, |then, (index, arg)| { .rfold(then, |then, (field_index, arg)| {
let label = arg.label.clone().unwrap_or_default(); let field_name = match arg.as_ref() {
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 {
Pattern::Var { name, .. } => name.to_string(), Pattern::Var { name, .. } => name.to_string(),
Pattern::Assign { name, .. } => name.to_string(), Pattern::Assign { name, .. } => name.to_string(),
Pattern::Discard { name, .. } => { Pattern::Discard { name, .. } => {
if props.full_check { if props.full_check {
format!("__discard_{}_{}", name, index) format!("__discard_{}_{}", name, field_index)
} else { } else {
"_".to_string() "_".to_string()
} }
@ -1189,15 +1178,15 @@ impl<'a> CodeGenerator<'a> {
_ => format!( _ => format!(
"field_{}_span_{}_{}", "field_{}_span_{}_{}",
field_index, field_index,
arg.value.location().start, arg.location().start,
arg.value.location().end arg.location().end
), ),
}; };
let arg_type = type_map.get(&field_index).unwrap_or_else(|| { let arg_type = type_map.get(&field_index).unwrap_or_else(|| {
unreachable!( unreachable!(
"Missing type for field {} of constr {}", "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 != "_" { let then = if field_name != "_" {
self.assignment( self.assignment(
&arg.value, arg,
val, val,
then, then,
arg_type, arg_type,
@ -1232,7 +1221,7 @@ impl<'a> CodeGenerator<'a> {
// local var // local var
let constructor_name = format!( let constructor_name = format!(
"__constructor_{}_span_{}_{}", "__constructor_{}_span_{}_{}",
name, "Pair",
pattern.location().start, pattern.location().start,
pattern.location().end pattern.location().end
); );
@ -1421,6 +1410,7 @@ impl<'a> CodeGenerator<'a> {
AirTree::let_assignment(constructor_name, value, then) AirTree::let_assignment(constructor_name, value, then)
} }
Pattern::Tuple { Pattern::Tuple {
elems, location, .. elems, location, ..
} => { } => {
@ -2529,75 +2519,40 @@ impl<'a> CodeGenerator<'a> {
(AirTree::void(), list_assign) (AirTree::void(), list_assign)
} }
Pattern::Constructor { Pattern::Pair { fst, snd, .. } => {
name, let items_type = subject_tipo.get_inner_types();
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 { .. })
);
let field_map = match constructor { let mut name_index_assigns = vec![];
PatternConstructor::Record { field_map, .. } => field_map.clone(),
};
let mut type_map: IndexMap<usize, Rc<Type>> = 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 next_then = let next_then =
arguments [fst, snd]
.iter() .iter()
.enumerate() .enumerate()
.rfold(then, |inner_then, (index, arg)| { .rfold(then, |inner_then, (index, element)| {
let label = arg.label.clone().unwrap_or_default(); let elem_name = match element.as_ref() {
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 {
Pattern::Var { name, .. } => Some(name.to_string()), Pattern::Var { name, .. } => Some(name.to_string()),
Pattern::Assign { name, .. } => Some(name.to_string()), Pattern::Assign { name, .. } => Some(name.to_string()),
Pattern::Discard { .. } => None, Pattern::Discard { .. } => None,
_ => Some(format!( _ => Some(format!(
"field_{}_span_{}_{}", "pair_index_{}_span_{}_{}",
field_index, index,
arg.value.location().start, element.location().start,
arg.value.location().end element.location().end
)), )),
}; };
let arg_type = type_map.get(&field_index).unwrap_or_else(|| { let mut pair_props = ClauseProperties::init_inner(
unreachable!( &items_type[index],
"Missing type for field {} of constr {}", elem_name.clone().unwrap_or_else(|| "_".to_string()),
field_index, name elem_name.clone().unwrap_or_else(|| "_".to_string()),
)
});
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()),
props.final_clause, props.final_clause,
); );
let statement = if field_name.is_some() { let elem = if elem_name.is_some() {
self.nested_clause_condition( self.nested_clause_condition(
&arg.value, element,
arg_type, &items_type[index],
&mut field_props, &mut pair_props,
inner_then, inner_then,
) )
} else { } else {
@ -2605,21 +2560,21 @@ impl<'a> CodeGenerator<'a> {
}; };
props.complex_clause = 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 next_then
} else { } else {
AirTree::pair_access( AirTree::pair_access(
fields[0].0.clone(), name_index_assigns[0].0.clone(),
fields[1].0.clone(), name_index_assigns[1].0.clone(),
subject_tipo.clone(), subject_tipo.clone(),
AirTree::local_var(props.clause_var_name.clone(), subject_tipo.clone()), AirTree::local_var(props.clause_var_name.clone(), subject_tipo.clone()),
None, 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); let (_, assign) = self.clause_pattern(pattern, subject_tipo, props, then);
assign assign
} }

View File

@ -679,24 +679,19 @@ pub fn pattern_has_conditions(
Pattern::Tuple { elems, .. } => elems Pattern::Tuple { elems, .. } => elems
.iter() .iter()
.any(|elem| pattern_has_conditions(elem, data_types)), .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 { Pattern::Constructor {
arguments, tipo, .. arguments, tipo, ..
} => { } => {
if tipo.is_pair() let data_type = lookup_data_type_by_tipo(data_types, tipo)
|| (tipo.is_function() && tipo.return_type().map(|t| t.is_pair()).unwrap_or(false)) .unwrap_or_else(|| panic!("Data type not found: {:#?}", tipo));
{
arguments data_type.constructors.len() > 1
|| arguments
.iter() .iter()
.any(|arg| pattern_has_conditions(&arg.value, data_types)) .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::Assign { pattern, .. } => pattern_has_conditions(pattern, data_types),
Pattern::Var { .. } | Pattern::Discard { .. } => false, Pattern::Var { .. } | Pattern::Discard { .. } => false,

View File

@ -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<Vec<Rc<Self>>> { pub fn arg_types(&self) -> Option<Vec<Rc<Self>>> {
match self { match self {
Self::Fn { args, .. } => Some(args.clone()), Self::Fn { args, .. } => Some(args.clone()),

View File

@ -1,12 +1,10 @@
use std::{collections::BTreeMap, iter, ops::Deref};
use itertools::Itertools;
use crate::{ use crate::{
ast, ast,
builtins::{self, PAIR}, builtins::{self},
tipo::{self, environment::Environment, error::Error}, tipo::{self, environment::Environment, error::Error},
}; };
use itertools::Itertools;
use std::{collections::BTreeMap, iter, ops::Deref};
const NIL_NAME: &str = "[]"; const NIL_NAME: &str = "[]";
const CONS_NAME: &str = "::"; const CONS_NAME: &str = "::";
@ -88,8 +86,8 @@ impl PatternStack {
Some(self.chain_tail_into_iter(vec![Pattern::Wildcard; arity].into_iter())) Some(self.chain_tail_into_iter(vec![Pattern::Wildcard; arity].into_iter()))
} }
Pattern::Literal(_) => unreachable!( 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, .. }, constructor: super::PatternConstructor::Record { name, .. },
.. ..
} => { } => {
let (empty, pair) = (&"".to_string(), &PAIR.to_string());
let (module, type_name, arity) = match tipo.deref() { let (module, type_name, arity) = match tipo.deref() {
tipo::Type::App { tipo::Type::App {
name: type_name, name: type_name,
@ -578,13 +574,11 @@ pub(super) fn simplify(
module, module,
.. ..
} => (module, type_name, args.len()), } => (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"),
_ => unreachable!("tipo should be a Type::App or Type::Pair"),
}; };
let alts = environment.get_constructors_for_type(module, type_name, *location)?; 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)) 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, .. } => { ast::Pattern::Tuple { elems, .. } => {
let mut args = vec![]; let mut args = vec![];

View File

@ -8,7 +8,7 @@ use super::{
}; };
use crate::{ use crate::{
ast::{CallArg, Pattern, Span, TypedPattern, UntypedPattern}, ast::{CallArg, Pattern, Span, TypedPattern, UntypedPattern},
builtins::{int, list, tuple}, builtins::{int, list, pair, tuple},
}; };
use itertools::Itertools; use itertools::Itertools;
use std::{ 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() { Pattern::Tuple { elems, location } => match collapse_links(tipo.clone()).deref() {
Type::Tuple { Type::Tuple {
elems: type_elems, .. elems: type_elems, ..