Decision tree compilation of list patterns

This commit is contained in:
Ayaz Hafiz 2022-11-01 13:59:44 -05:00
parent da1d937277
commit ae71c7efe2
No known key found for this signature in database
GPG Key ID: 0E2A37416A25EF58
7 changed files with 479 additions and 38 deletions

View File

@ -1,5 +1,5 @@
use crate::expr::{self, IntValue, WhenBranch};
use crate::pattern::{DestructType, ListPatterns};
use crate::pattern::DestructType;
use roc_collections::all::HumanIndex;
use roc_collections::VecMap;
use roc_error_macros::internal_error;
@ -344,23 +344,19 @@ fn sketch_pattern(pattern: &crate::pattern::Pattern) -> SketchedPattern {
}
List {
patterns: ListPatterns { patterns, opt_rest },
patterns,
list_var: _,
elem_var: _,
} => {
let list_arity = match opt_rest {
Some(i) => {
let before = *i;
let after = patterns.len() - before;
ListArity::Slice(before, after)
}
None => ListArity::Exact(patterns.len()),
};
let arity = patterns.arity();
let sketched_elem_patterns =
patterns.iter().map(|p| sketch_pattern(&p.value)).collect();
let sketched_elem_patterns = patterns
.patterns
.iter()
.map(|p| sketch_pattern(&p.value))
.collect();
SP::List(list_arity, sketched_elem_patterns)
SP::List(arity, sketched_elem_patterns)
}
AppliedTag {

View File

@ -6,6 +6,7 @@ use crate::num::{
ParsedNumResult,
};
use crate::scope::{PendingAbilitiesInScope, Scope};
use roc_exhaustive::ListArity;
use roc_module::ident::{Ident, Lowercase, TagName};
use roc_module::symbol::Symbol;
use roc_parse::ast::{self, StrLiteral, StrSegment};
@ -189,6 +190,17 @@ impl ListPatterns {
fn surely_exhaustive(&self) -> bool {
self.patterns.is_empty() && matches!(self.opt_rest, Some(0))
}
pub fn arity(&self) -> ListArity {
match self.opt_rest {
Some(i) => {
let before = i;
let after = self.patterns.len() - before;
ListArity::Slice(before, after)
}
None => ListArity::Exact(self.patterns.len()),
}
}
}
#[derive(Clone, Debug)]

View File

@ -93,7 +93,7 @@ impl ListArity {
/// The trivially-exhaustive list pattern `[..]`
const ANY: ListArity = ListArity::Slice(0, 0);
fn min_len(&self) -> usize {
pub fn min_len(&self) -> usize {
match self {
ListArity::Exact(n) => *n,
ListArity::Slice(l, r) => l + r,

View File

@ -1,11 +1,12 @@
use crate::ir::{
BranchInfo, DestructType, Env, Expr, JoinPointId, Literal, Param, Pattern, Procs, Stmt,
BranchInfo, Call, CallType, DestructType, Env, Expr, JoinPointId, Literal, Param, Pattern,
Procs, Stmt,
};
use crate::layout::{Builtin, Layout, LayoutCache, TagIdIntType, UnionLayout};
use roc_builtins::bitcode::{FloatWidth, IntWidth};
use roc_collections::all::{MutMap, MutSet};
use roc_error_macros::internal_error;
use roc_exhaustive::{Ctor, CtorName, RenderAs, TagId, Union};
use roc_exhaustive::{Ctor, CtorName, ListArity, RenderAs, TagId, Union};
use roc_module::ident::TagName;
use roc_module::low_level::LowLevel;
use roc_module::symbol::Symbol;
@ -77,6 +78,12 @@ enum GuardedTest<'a> {
Placeholder,
}
#[derive(Clone, Copy, Debug, PartialEq, Hash)]
enum ListLenBound {
Exact,
AtLeast,
}
#[derive(Clone, Debug, PartialEq)]
#[allow(clippy::enum_variant_names)]
enum Test<'a> {
@ -95,6 +102,10 @@ enum Test<'a> {
tag_id: TagIdIntType,
num_alts: usize,
},
IsListLen {
bound: ListLenBound,
len: u64,
},
}
impl<'a> Test<'a> {
@ -110,6 +121,10 @@ impl<'a> Test<'a> {
Test::IsStr(_) => false,
Test::IsBit(_) => true,
Test::IsByte { .. } => true,
Test::IsListLen { bound, .. } => match bound {
ListLenBound::Exact => true,
ListLenBound::AtLeast => false,
},
}
}
}
@ -153,6 +168,10 @@ impl<'a> Hash for Test<'a> {
state.write_u8(6);
v.hash(state);
}
IsListLen { len, bound } => {
state.write_u8(7);
(len, bound).hash(state);
}
}
}
}
@ -331,6 +350,11 @@ fn tests_are_complete_help(last_test: &Test, number_of_tests: usize) -> bool {
Test::IsFloat(_, _) => false,
Test::IsDecimal(_) => false,
Test::IsStr(_) => false,
Test::IsListLen {
bound: ListLenBound::AtLeast,
len: 0,
} => true, // [..] test
Test::IsListLen { .. } => false,
}
}
@ -578,6 +602,18 @@ fn test_at_path<'a>(
arguments: arguments.to_vec(),
},
List {
arity,
element_layout: _,
elements: _,
} => IsListLen {
bound: match arity {
ListArity::Exact(_) => ListLenBound::Exact,
ListArity::Slice(_, _) => ListLenBound::AtLeast,
},
len: arity.min_len() as _,
},
Voided { .. } => internal_error!("unreachable"),
OpaqueUnwrap { opaque, argument } => {
@ -755,6 +791,50 @@ fn to_relevant_branch_help<'a>(
_ => None,
},
List {
arity: my_arity,
elements,
element_layout: _,
} => match test {
IsListLen { bound, len }
if (match (bound, my_arity) {
(ListLenBound::Exact, ListArity::Exact(my_len)) => my_len == *len as _,
(ListLenBound::Exact, ListArity::Slice(my_head, my_tail)) => {
my_head + my_tail <= *len as _
}
(ListLenBound::AtLeast, ListArity::Exact(my_len)) => my_len == *len as _,
(ListLenBound::AtLeast, ListArity::Slice(my_head, my_tail)) => {
my_head + my_tail <= *len as _
}
}) =>
{
if matches!(my_arity, ListArity::Slice(_, n) if n > 0) {
todo!();
}
let sub_positions = elements.into_iter().enumerate().map(|(index, elem_pat)| {
let mut new_path = path.to_vec();
let next_instr = PathInstruction::ListIndex {
// TODO index into back as well
index: index as _,
};
new_path.push(next_instr);
(new_path, elem_pat)
});
start.extend(sub_positions);
start.extend(end);
Some(Branch {
goal: branch.goal,
guard: branch.guard.clone(),
patterns: start,
})
}
_ => None,
},
NewtypeDestructure {
tag_name,
arguments,
@ -1021,7 +1101,8 @@ fn needs_tests(pattern: &Pattern) -> bool {
| IntLiteral(_, _)
| FloatLiteral(_, _)
| DecimalLiteral(_)
| StrLiteral(_) => true,
| StrLiteral(_)
| List { .. } => true,
Voided { .. } => internal_error!("unreachable"),
}
@ -1267,7 +1348,15 @@ pub fn optimize_when<'a>(
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum PathInstruction {
NewType,
TagIndex { index: u64, tag_id: TagIdIntType },
TagIndex {
index: u64,
tag_id: TagIdIntType,
},
ListIndex {
// Positive if it should be indexed from the front, negative otherwise
// (-1 means the last index)
index: i64,
},
}
fn path_to_expr_help<'a>(
@ -1337,19 +1426,51 @@ fn path_to_expr_help<'a>(
}
}
}
PathInstruction::ListIndex { index } => {
let list_sym = symbol;
let usize_layout = Layout::usize(env.target_info);
if index < &0 {
todo!();
}
match layout {
Layout::Builtin(Builtin::List(elem_layout)) => {
let index_sym = env.unique_symbol();
let index_expr =
Expr::Literal(Literal::Int((*index as i128).to_ne_bytes()));
let load_sym = env.unique_symbol();
let load_expr = Expr::Call(Call {
call_type: CallType::LowLevel {
op: LowLevel::ListGetUnsafe,
update_mode: env.next_update_mode_id(),
},
arguments: env.arena.alloc([list_sym, index_sym]),
});
stores.push((index_sym, usize_layout, index_expr));
stores.push((load_sym, *elem_layout, load_expr));
layout = *elem_layout;
}
_ => internal_error!("not a list"),
}
}
}
}
(symbol, stores, layout)
}
fn test_to_equality<'a>(
fn test_to_comparison<'a>(
env: &mut Env<'a, '_>,
cond_symbol: Symbol,
cond_layout: &Layout<'a>,
path: &[PathInstruction],
test: Test<'a>,
) -> (StoresVec<'a>, Symbol, Symbol, Option<ConstructorKnown<'a>>) {
) -> (StoresVec<'a>, Comparison, Option<ConstructorKnown<'a>>) {
let (rhs_symbol, mut stores, test_layout) =
path_to_expr_help(env, cond_symbol, path, *cond_layout);
@ -1379,8 +1500,7 @@ fn test_to_equality<'a>(
(
stores,
lhs_symbol,
rhs_symbol,
(lhs_symbol, Comparator::Eq, rhs_symbol),
Some(ConstructorKnown::OnlyPass {
scrutinee: path_symbol,
layout: *cond_layout,
@ -1397,7 +1517,7 @@ fn test_to_equality<'a>(
let lhs_symbol = env.unique_symbol();
stores.push((lhs_symbol, Layout::int_width(precision), lhs));
(stores, lhs_symbol, rhs_symbol, None)
(stores, (lhs_symbol, Comparator::Eq, rhs_symbol), None)
}
Test::IsFloat(test_int, precision) => {
@ -1407,7 +1527,7 @@ fn test_to_equality<'a>(
let lhs_symbol = env.unique_symbol();
stores.push((lhs_symbol, Layout::float_width(precision), lhs));
(stores, lhs_symbol, rhs_symbol, None)
(stores, (lhs_symbol, Comparator::Eq, rhs_symbol), None)
}
Test::IsDecimal(test_dec) => {
@ -1415,7 +1535,7 @@ fn test_to_equality<'a>(
let lhs_symbol = env.unique_symbol();
stores.push((lhs_symbol, *cond_layout, lhs));
(stores, lhs_symbol, rhs_symbol, None)
(stores, (lhs_symbol, Comparator::Eq, rhs_symbol), None)
}
Test::IsByte {
@ -1427,7 +1547,7 @@ fn test_to_equality<'a>(
let lhs_symbol = env.unique_symbol();
stores.push((lhs_symbol, Layout::u8(), lhs));
(stores, lhs_symbol, rhs_symbol, None)
(stores, (lhs_symbol, Comparator::Eq, rhs_symbol), None)
}
Test::IsBit(test_bit) => {
@ -1435,7 +1555,7 @@ fn test_to_equality<'a>(
let lhs_symbol = env.unique_symbol();
stores.push((lhs_symbol, Layout::Builtin(Builtin::Bool), lhs));
(stores, lhs_symbol, rhs_symbol, None)
(stores, (lhs_symbol, Comparator::Eq, rhs_symbol), None)
}
Test::IsStr(test_str) => {
@ -1444,15 +1564,58 @@ fn test_to_equality<'a>(
stores.push((lhs_symbol, Layout::Builtin(Builtin::Str), lhs));
(stores, lhs_symbol, rhs_symbol, None)
(stores, (lhs_symbol, Comparator::Eq, rhs_symbol), None)
}
Test::IsListLen { bound, len } => {
let list_layout = test_layout;
let list_sym = rhs_symbol;
match list_layout {
Layout::Builtin(Builtin::List(_elem_layout)) => {
let real_len_expr = Expr::Call(Call {
call_type: CallType::LowLevel {
op: LowLevel::ListLen,
update_mode: env.next_update_mode_id(),
},
arguments: env.arena.alloc([list_sym]),
});
let test_len_expr = Expr::Literal(Literal::Int((len as i128).to_ne_bytes()));
let real_len = env.unique_symbol();
let test_len = env.unique_symbol();
let usize_layout = Layout::usize(env.target_info);
stores.push((real_len, usize_layout, real_len_expr));
stores.push((test_len, usize_layout, test_len_expr));
let comparison = match bound {
ListLenBound::Exact => (real_len, Comparator::Eq, test_len),
ListLenBound::AtLeast => (real_len, Comparator::Geq, test_len),
};
(stores, comparison, None)
}
_ => internal_error!(
"test path is not a list: {:#?}",
(cond_layout, test_layout, path)
),
}
}
}
}
enum Comparator {
Eq,
Geq,
}
type Comparison = (Symbol, Comparator, Symbol);
type Tests<'a> = std::vec::Vec<(
bumpalo::collections::Vec<'a, (Symbol, Layout<'a>, Expr<'a>)>,
Symbol,
Symbol,
Comparison,
Option<ConstructorKnown<'a>>,
)>;
@ -1466,7 +1629,13 @@ fn stores_and_condition<'a>(
// Assumption: there is at most 1 guard, and it is the outer layer.
for (path, test) in test_chain {
tests.push(test_to_equality(env, cond_symbol, cond_layout, &path, test))
tests.push(test_to_comparison(
env,
cond_symbol,
cond_layout,
&path,
test,
))
}
tests
@ -1477,6 +1646,7 @@ fn compile_test<'a>(
ret_layout: Layout<'a>,
stores: bumpalo::collections::Vec<'a, (Symbol, Layout<'a>, Expr<'a>)>,
lhs: Symbol,
cmp: Comparator,
rhs: Symbol,
fail: &'a Stmt<'a>,
cond: Stmt<'a>,
@ -1487,6 +1657,7 @@ fn compile_test<'a>(
ret_layout,
stores,
lhs,
cmp,
rhs,
fail,
cond,
@ -1500,6 +1671,7 @@ fn compile_test_help<'a>(
ret_layout: Layout<'a>,
stores: bumpalo::collections::Vec<'a, (Symbol, Layout<'a>, Expr<'a>)>,
lhs: Symbol,
cmp: Comparator,
rhs: Symbol,
fail: &'a Stmt<'a>,
mut cond: Stmt<'a>,
@ -1560,7 +1732,10 @@ fn compile_test_help<'a>(
default_branch,
};
let op = LowLevel::Eq;
let op = match cmp {
Comparator::Eq => LowLevel::Eq,
Comparator::Geq => LowLevel::NumGte,
};
let test = Expr::Call(crate::ir::Call {
call_type: crate::ir::CallType::LowLevel {
op,
@ -1592,13 +1767,15 @@ fn compile_tests<'a>(
fail: &'a Stmt<'a>,
mut cond: Stmt<'a>,
) -> Stmt<'a> {
for (new_stores, lhs, rhs, opt_constructor_info) in tests.into_iter() {
for (new_stores, (lhs, cmp, rhs), opt_constructor_info) in tests.into_iter() {
match opt_constructor_info {
None => {
cond = compile_test(env, ret_layout, new_stores, lhs, rhs, fail, cond);
cond = compile_test(env, ret_layout, new_stores, lhs, cmp, rhs, fail, cond);
}
Some(cinfo) => {
cond = compile_test_help(env, cinfo, ret_layout, new_stores, lhs, rhs, fail, cond);
cond = compile_test_help(
env, cinfo, ret_layout, new_stores, lhs, cmp, rhs, fail, cond,
);
}
}
}
@ -1781,7 +1958,7 @@ fn decide_to_branching<'a>(
if number_of_tests == 1 {
// if there is just one test, compile to a simple if-then-else
let (new_stores, lhs, rhs, _cinfo) = tests.into_iter().next().unwrap();
let (new_stores, (lhs, cmp, rhs), _cinfo) = tests.into_iter().next().unwrap();
compile_test_help(
env,
@ -1789,6 +1966,7 @@ fn decide_to_branching<'a>(
ret_layout,
new_stores,
lhs,
cmp,
rhs,
fail,
pass_expr,
@ -1854,6 +2032,12 @@ fn decide_to_branching<'a>(
Test::IsBit(v) => v as u64,
Test::IsByte { tag_id, .. } => tag_id as u64,
Test::IsCtor { tag_id, .. } => tag_id as u64,
Test::IsListLen { len, bound } => match bound {
ListLenBound::Exact => len as _,
ListLenBound::AtLeast => {
unreachable!("at-least bounds cannot be switched on")
}
},
Test::IsDecimal(_) => unreachable!("decimals cannot be switched on"),
Test::IsStr(_) => unreachable!("strings cannot be switched on"),
};
@ -1911,6 +2095,31 @@ fn decide_to_branching<'a>(
union_layout.tag_id_layout(),
env.arena.alloc(temp),
)
} else if let Layout::Builtin(Builtin::List(_)) = inner_cond_layout {
let len_symbol = env.unique_symbol();
let switch = Stmt::Switch {
cond_layout: Layout::usize(env.target_info),
cond_symbol: len_symbol,
branches: branches.into_bump_slice(),
default_branch: (default_branch_info, env.arena.alloc(default_branch)),
ret_layout,
};
let len_expr = Expr::Call(Call {
call_type: CallType::LowLevel {
op: LowLevel::ListLen,
update_mode: env.next_update_mode_id(),
},
arguments: env.arena.alloc([inner_cond_symbol]),
});
Stmt::Let(
len_symbol,
len_expr,
Layout::usize(env.target_info),
env.arena.alloc(switch),
)
} else {
Stmt::Switch {
cond_layout: inner_cond_layout,

View File

@ -21,7 +21,7 @@ use roc_debug_flags::{
};
use roc_derive::SharedDerivedModule;
use roc_error_macros::{internal_error, todo_abilities};
use roc_exhaustive::{Ctor, CtorName, RenderAs, TagId};
use roc_exhaustive::{Ctor, CtorName, ListArity, RenderAs, TagId};
use roc_intern::Interner;
use roc_late_solve::storage::{ExternalModuleStorage, ExternalModuleStorageSnapshot};
use roc_late_solve::{resolve_ability_specialization, AbilitiesView, Resolved, UnificationFailed};
@ -7289,6 +7289,24 @@ fn store_pattern_help<'a>(
stmt,
);
}
List {
arity,
element_layout,
elements,
} => {
return store_list_pattern(
env,
procs,
layout_cache,
outer_symbol,
*arity,
*element_layout,
elements,
stmt,
)
}
Voided { .. } => {
return StorePattern::NotProductive(stmt);
}
@ -7361,6 +7379,96 @@ fn store_pattern_help<'a>(
StorePattern::Productive(stmt)
}
#[allow(clippy::too_many_arguments)]
fn store_list_pattern<'a>(
env: &mut Env<'a, '_>,
procs: &mut Procs<'a>,
layout_cache: &mut LayoutCache<'a>,
list_sym: Symbol,
list_arity: ListArity,
element_layout: Layout<'a>,
elements: &[Pattern<'a>],
mut stmt: Stmt<'a>,
) -> StorePattern<'a> {
use Pattern::*;
if matches!(list_arity, ListArity::Slice(_, n) if n > 0) {
todo!();
}
let mut is_productive = false;
let usize_layout = Layout::usize(env.target_info);
for (index, element) in elements.iter().enumerate().rev() {
let index_lit = Expr::Literal(Literal::Int((index as i128).to_ne_bytes()));
let index_sym = env.unique_symbol();
let load = Expr::Call(Call {
call_type: CallType::LowLevel {
op: LowLevel::ListGetUnsafe,
update_mode: env.next_update_mode_id(),
},
arguments: env.arena.alloc([list_sym, index_sym]),
});
let store_loaded = match element {
Identifier(symbol) => {
// Pattern can define only one specialization
let symbol = procs
.symbol_specializations
.remove_single(*symbol)
.unwrap_or(*symbol);
// store immediately in the given symbol
Stmt::Let(symbol, load, element_layout, env.arena.alloc(stmt))
}
Underscore
| IntLiteral(_, _)
| FloatLiteral(_, _)
| DecimalLiteral(_)
| EnumLiteral { .. }
| BitLiteral { .. }
| StrLiteral(_) => {
// ignore
continue;
}
_ => {
// store the field in a symbol, and continue matching on it
let symbol = env.unique_symbol();
// first recurse, continuing to unpack symbol
match store_pattern_help(env, procs, layout_cache, element, symbol, stmt) {
StorePattern::Productive(new) => {
stmt = new;
// only if we bind one of its (sub)fields to a used name should we
// extract the field
Stmt::Let(symbol, load, element_layout, env.arena.alloc(stmt))
}
StorePattern::NotProductive(new) => {
// do nothing
stmt = new;
continue;
}
}
}
};
is_productive = true;
stmt = Stmt::Let(
index_sym,
index_lit,
usize_layout,
env.arena.alloc(store_loaded),
);
}
if is_productive {
StorePattern::Productive(stmt)
} else {
StorePattern::NotProductive(stmt)
}
}
#[allow(clippy::too_many_arguments)]
fn store_tag_pattern<'a>(
env: &mut Env<'a, '_>,
@ -8922,6 +9030,11 @@ pub enum Pattern<'a> {
opaque: Symbol,
argument: Box<(Pattern<'a>, Layout<'a>)>,
},
List {
arity: ListArity,
element_layout: Layout<'a>,
elements: Vec<'a, Pattern<'a>>,
},
}
impl<'a> Pattern<'a> {
@ -8958,6 +9071,7 @@ impl<'a> Pattern<'a> {
stack.extend(arguments.iter().map(|(t, _)| t))
}
Pattern::OpaqueUnwrap { argument, .. } => stack.push(&argument.0),
Pattern::List { elements, .. } => stack.extend(elements),
}
}
@ -9709,7 +9823,34 @@ fn from_can_pattern_help<'a>(
))
}
List { .. } => todo!(),
List {
list_var: _,
elem_var,
patterns,
} => {
let element_layout = match layout_cache.from_var(env.arena, *elem_var, env.subs) {
Ok(lay) => lay,
Err(LayoutProblem::UnresolvedTypeVar(_)) => {
return Err(RuntimeError::UnresolvedTypeVar)
}
Err(LayoutProblem::Erroneous) => return Err(RuntimeError::ErroneousType),
};
let arity = patterns.arity();
let mut mono_patterns = Vec::with_capacity_in(patterns.patterns.len(), env.arena);
for loc_pat in patterns.patterns.iter() {
let mono_pat =
from_can_pattern_help(env, procs, layout_cache, &loc_pat.value, assignments)?;
mono_patterns.push(mono_pat);
}
Ok(Pattern::List {
arity,
element_layout,
elements: mono_patterns,
})
}
}
}

View File

@ -0,0 +1,67 @@
procedure Test.0 ():
let Test.42 : Int1 = false;
let Test.43 : Int1 = true;
let Test.1 : List Int1 = Array [Test.42, Test.43];
joinpoint Test.16:
let Test.8 : Str = "E";
ret Test.8;
in
joinpoint Test.10:
let Test.5 : Str = "B";
ret Test.5;
in
let Test.39 : U64 = lowlevel ListLen Test.1;
let Test.40 : U64 = 0i64;
let Test.41 : Int1 = lowlevel Eq Test.39 Test.40;
if Test.41 then
dec Test.1;
let Test.4 : Str = "A";
ret Test.4;
else
let Test.36 : U64 = lowlevel ListLen Test.1;
let Test.37 : U64 = 1i64;
let Test.38 : Int1 = lowlevel Eq Test.36 Test.37;
if Test.38 then
let Test.17 : U64 = 0i64;
let Test.18 : Int1 = lowlevel ListGetUnsafe Test.1 Test.17;
let Test.19 : Int1 = false;
let Test.20 : Int1 = lowlevel Eq Test.19 Test.1;
dec Test.1;
if Test.20 then
jump Test.10;
else
jump Test.16;
else
let Test.33 : U64 = lowlevel ListLen Test.1;
let Test.34 : U64 = 2i64;
let Test.35 : Int1 = lowlevel NumGte Test.33 Test.34;
if Test.35 then
let Test.25 : U64 = 0i64;
let Test.26 : Int1 = lowlevel ListGetUnsafe Test.1 Test.25;
let Test.27 : Int1 = false;
let Test.28 : Int1 = lowlevel Eq Test.27 Test.1;
if Test.28 then
let Test.21 : U64 = 1i64;
let Test.22 : Int1 = lowlevel ListGetUnsafe Test.1 Test.21;
let Test.23 : Int1 = false;
let Test.24 : Int1 = lowlevel Eq Test.23 Test.1;
dec Test.1;
if Test.24 then
let Test.6 : Str = "C";
ret Test.6;
else
let Test.7 : Str = "D";
ret Test.7;
else
dec Test.1;
jump Test.16;
else
let Test.29 : U64 = 0i64;
let Test.30 : Int1 = lowlevel ListGetUnsafe Test.1 Test.29;
let Test.31 : Int1 = false;
let Test.32 : Int1 = lowlevel Eq Test.31 Test.1;
dec Test.1;
if Test.32 then
jump Test.10;
else
jump Test.16;

View File

@ -2000,3 +2000,19 @@ fn unreachable_branch_is_eliminated_but_produces_lambda_specializations() {
"#
)
}
#[mono_test]
fn match_list() {
indoc!(
r#"
l = [A, B]
when l is
[] -> "A"
[A] -> "B"
[A, A, ..] -> "C"
[A, B, ..] -> "D"
[B, ..] -> "E"
"#
)
}