mirror of
https://github.com/roc-lang/roc.git
synced 2025-08-04 12:18:19 +00:00
implement standard as patterns in mono IR
This commit is contained in:
parent
726d8d80c4
commit
cf15654ee5
7 changed files with 347 additions and 128 deletions
|
@ -521,138 +521,142 @@ fn tests_at_path<'a>(
|
|||
unique
|
||||
}
|
||||
|
||||
fn test_for_pattern<'a>(pattern: &Pattern<'a>) -> Option<Test<'a>> {
|
||||
use Pattern::*;
|
||||
use Test::*;
|
||||
|
||||
let test = match pattern {
|
||||
Identifier(_) | Underscore => {
|
||||
return None;
|
||||
}
|
||||
|
||||
As(subpattern, _) => return test_for_pattern(subpattern),
|
||||
|
||||
RecordDestructure(destructs, _) => {
|
||||
// not rendered, so pick the easiest
|
||||
let union = Union {
|
||||
render_as: RenderAs::Tag,
|
||||
alternatives: vec![Ctor {
|
||||
tag_id: TagId(0),
|
||||
name: CtorName::Tag(TagName(RECORD_TAG_NAME.into())),
|
||||
arity: destructs.len(),
|
||||
}],
|
||||
};
|
||||
|
||||
let mut arguments = std::vec::Vec::new();
|
||||
|
||||
for destruct in destructs {
|
||||
match &destruct.typ {
|
||||
DestructType::Guard(guard) => {
|
||||
arguments.push((guard.clone(), destruct.layout));
|
||||
}
|
||||
DestructType::Required(_) => {
|
||||
arguments.push((Pattern::Underscore, destruct.layout));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
IsCtor {
|
||||
tag_id: 0,
|
||||
ctor_name: CtorName::Tag(TagName(RECORD_TAG_NAME.into())),
|
||||
union,
|
||||
arguments,
|
||||
}
|
||||
}
|
||||
|
||||
NewtypeDestructure {
|
||||
tag_name,
|
||||
arguments,
|
||||
} => {
|
||||
let tag_id = 0;
|
||||
let union = Union::newtype_wrapper(CtorName::Tag(tag_name.clone()), arguments.len());
|
||||
|
||||
IsCtor {
|
||||
tag_id,
|
||||
ctor_name: CtorName::Tag(tag_name.clone()),
|
||||
union,
|
||||
arguments: arguments.to_vec(),
|
||||
}
|
||||
}
|
||||
|
||||
AppliedTag {
|
||||
tag_name,
|
||||
tag_id,
|
||||
arguments,
|
||||
union,
|
||||
..
|
||||
} => IsCtor {
|
||||
tag_id: *tag_id,
|
||||
ctor_name: CtorName::Tag(tag_name.clone()),
|
||||
union: union.clone(),
|
||||
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 } => {
|
||||
let union = Union {
|
||||
render_as: RenderAs::Tag,
|
||||
alternatives: vec![Ctor {
|
||||
tag_id: TagId(0),
|
||||
name: CtorName::Opaque(*opaque),
|
||||
arity: 1,
|
||||
}],
|
||||
};
|
||||
|
||||
IsCtor {
|
||||
tag_id: 0,
|
||||
ctor_name: CtorName::Opaque(*opaque),
|
||||
union,
|
||||
arguments: vec![(**argument).clone()],
|
||||
}
|
||||
}
|
||||
|
||||
BitLiteral { value, .. } => IsBit(*value),
|
||||
EnumLiteral { tag_id, union, .. } => IsByte {
|
||||
tag_id: *tag_id as _,
|
||||
num_alts: union.alternatives.len(),
|
||||
},
|
||||
IntLiteral(v, precision) => IsInt(*v, *precision),
|
||||
FloatLiteral(v, precision) => IsFloat(*v, *precision),
|
||||
DecimalLiteral(v) => IsDecimal(*v),
|
||||
StrLiteral(v) => IsStr(v.clone()),
|
||||
};
|
||||
|
||||
Some(test)
|
||||
}
|
||||
|
||||
fn test_at_path<'a>(
|
||||
selected_path: &[PathInstruction],
|
||||
branch: &Branch<'a>,
|
||||
) -> Option<GuardedTest<'a>> {
|
||||
use Pattern::*;
|
||||
use Test::*;
|
||||
|
||||
match branch
|
||||
let (_, pattern) = branch
|
||||
.patterns
|
||||
.iter()
|
||||
.find(|(path, _)| path == selected_path)
|
||||
{
|
||||
None => None,
|
||||
Some((_, pattern)) => {
|
||||
let test = match pattern {
|
||||
Identifier(_) | Underscore => {
|
||||
if let Guard::Guard { .. } = &branch.guard {
|
||||
// no tests for this pattern remain, but we cannot discard it yet
|
||||
// because it has a guard!
|
||||
return Some(GuardedTest::Placeholder);
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
.find(|(path, _)| path == selected_path)?;
|
||||
|
||||
RecordDestructure(destructs, _) => {
|
||||
// not rendered, so pick the easiest
|
||||
let union = Union {
|
||||
render_as: RenderAs::Tag,
|
||||
alternatives: vec![Ctor {
|
||||
tag_id: TagId(0),
|
||||
name: CtorName::Tag(TagName(RECORD_TAG_NAME.into())),
|
||||
arity: destructs.len(),
|
||||
}],
|
||||
};
|
||||
|
||||
let mut arguments = std::vec::Vec::new();
|
||||
|
||||
for destruct in destructs {
|
||||
match &destruct.typ {
|
||||
DestructType::Guard(guard) => {
|
||||
arguments.push((guard.clone(), destruct.layout));
|
||||
}
|
||||
DestructType::Required(_) => {
|
||||
arguments.push((Pattern::Underscore, destruct.layout));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
IsCtor {
|
||||
tag_id: 0,
|
||||
ctor_name: CtorName::Tag(TagName(RECORD_TAG_NAME.into())),
|
||||
union,
|
||||
arguments,
|
||||
}
|
||||
}
|
||||
|
||||
NewtypeDestructure {
|
||||
tag_name,
|
||||
arguments,
|
||||
} => {
|
||||
let tag_id = 0;
|
||||
let union =
|
||||
Union::newtype_wrapper(CtorName::Tag(tag_name.clone()), arguments.len());
|
||||
|
||||
IsCtor {
|
||||
tag_id,
|
||||
ctor_name: CtorName::Tag(tag_name.clone()),
|
||||
union,
|
||||
arguments: arguments.to_vec(),
|
||||
}
|
||||
}
|
||||
|
||||
AppliedTag {
|
||||
tag_name,
|
||||
tag_id,
|
||||
arguments,
|
||||
union,
|
||||
..
|
||||
} => IsCtor {
|
||||
tag_id: *tag_id,
|
||||
ctor_name: CtorName::Tag(tag_name.clone()),
|
||||
union: union.clone(),
|
||||
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 } => {
|
||||
let union = Union {
|
||||
render_as: RenderAs::Tag,
|
||||
alternatives: vec![Ctor {
|
||||
tag_id: TagId(0),
|
||||
name: CtorName::Opaque(*opaque),
|
||||
arity: 1,
|
||||
}],
|
||||
};
|
||||
|
||||
IsCtor {
|
||||
tag_id: 0,
|
||||
ctor_name: CtorName::Opaque(*opaque),
|
||||
union,
|
||||
arguments: vec![(**argument).clone()],
|
||||
}
|
||||
}
|
||||
|
||||
BitLiteral { value, .. } => IsBit(*value),
|
||||
EnumLiteral { tag_id, union, .. } => IsByte {
|
||||
tag_id: *tag_id as _,
|
||||
num_alts: union.alternatives.len(),
|
||||
},
|
||||
IntLiteral(v, precision) => IsInt(*v, *precision),
|
||||
FloatLiteral(v, precision) => IsFloat(*v, *precision),
|
||||
DecimalLiteral(v) => IsDecimal(*v),
|
||||
StrLiteral(v) => IsStr(v.clone()),
|
||||
};
|
||||
|
||||
let guarded_test = GuardedTest::TestNotGuarded { test };
|
||||
|
||||
Some(guarded_test)
|
||||
match test_for_pattern(pattern) {
|
||||
Some(test) => Some(GuardedTest::TestNotGuarded { test }),
|
||||
None => {
|
||||
if let Guard::Guard { .. } = &branch.guard {
|
||||
// no tests for this pattern remain, but we cannot discard it yet
|
||||
// because it has a guard!
|
||||
Some(GuardedTest::Placeholder)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -732,6 +736,10 @@ fn to_relevant_branch_help<'a>(
|
|||
match pattern {
|
||||
Identifier(_) | Underscore => Some(branch.clone()),
|
||||
|
||||
As(subpattern, _symbol) => {
|
||||
to_relevant_branch_help(test, path, start, end, branch, *subpattern)
|
||||
}
|
||||
|
||||
RecordDestructure(destructs, _) => match test {
|
||||
IsCtor {
|
||||
ctor_name: test_name,
|
||||
|
@ -1099,6 +1107,8 @@ fn needs_tests(pattern: &Pattern) -> bool {
|
|||
match pattern {
|
||||
Identifier(_) | Underscore => false,
|
||||
|
||||
As(subpattern, _) => needs_tests(subpattern),
|
||||
|
||||
NewtypeDestructure { .. }
|
||||
| RecordDestructure(..)
|
||||
| AppliedTag { .. }
|
||||
|
|
|
@ -7411,6 +7411,28 @@ fn store_pattern_help<'a>(
|
|||
// do nothing
|
||||
return StorePattern::NotProductive(stmt);
|
||||
}
|
||||
As(subpattern, symbol) => {
|
||||
let stored_subpattern =
|
||||
store_pattern_help(env, procs, layout_cache, subpattern, outer_symbol, stmt);
|
||||
|
||||
let mut stmt = match stored_subpattern {
|
||||
StorePattern::Productive(stmt) => stmt,
|
||||
StorePattern::NotProductive(stmt) => stmt,
|
||||
};
|
||||
|
||||
// An identifier in a pattern can define at most one specialization!
|
||||
// Remove any requested specializations for this name now, since this is the definition site.
|
||||
let specialization_symbol = procs
|
||||
.symbol_specializations
|
||||
.remove_single(*symbol)
|
||||
// Can happen when the symbol was never used under this body, and hence has no
|
||||
// requested specialization.
|
||||
.unwrap_or(*symbol);
|
||||
|
||||
substitute_in_exprs(env.arena, &mut stmt, specialization_symbol, outer_symbol);
|
||||
|
||||
return StorePattern::Productive(stmt);
|
||||
}
|
||||
IntLiteral(_, _)
|
||||
| FloatLiteral(_, _)
|
||||
| DecimalLiteral(_)
|
||||
|
@ -9259,6 +9281,7 @@ fn call_specialized_proc<'a>(
|
|||
pub enum Pattern<'a> {
|
||||
Identifier(Symbol),
|
||||
Underscore,
|
||||
As(Box<Pattern<'a>>, Symbol),
|
||||
IntLiteral([u8; 16], IntWidth),
|
||||
FloatLiteral(u64, FloatWidth),
|
||||
DecimalLiteral([u8; 16]),
|
||||
|
@ -9316,6 +9339,7 @@ impl<'a> Pattern<'a> {
|
|||
| Pattern::BitLiteral { .. }
|
||||
| Pattern::EnumLiteral { .. }
|
||||
| Pattern::StrLiteral(_) => { /* terminal */ }
|
||||
Pattern::As(subpattern, _) => stack.push(subpattern),
|
||||
Pattern::RecordDestructure(destructs, _) => {
|
||||
for destruct in destructs {
|
||||
match &destruct.typ {
|
||||
|
@ -9394,7 +9418,12 @@ fn from_can_pattern_help<'a>(
|
|||
match can_pattern {
|
||||
Underscore => Ok(Pattern::Underscore),
|
||||
Identifier(symbol) => Ok(Pattern::Identifier(*symbol)),
|
||||
As(_, _) => todo!(),
|
||||
As(subpattern, symbol) => {
|
||||
let mono_subpattern =
|
||||
from_can_pattern_help(env, procs, layout_cache, &subpattern.value, assignments)?;
|
||||
|
||||
Ok(Pattern::As(Box::new(mono_subpattern), *symbol))
|
||||
}
|
||||
AbilityMemberSpecialization { ident, .. } => Ok(Pattern::Identifier(*ident)),
|
||||
IntLiteral(var, _, int_str, int, _bound) => Ok(make_num_literal_pattern(
|
||||
env,
|
||||
|
|
|
@ -4145,3 +4145,63 @@ fn issue_4712() {
|
|||
RocStr
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
fn pattern_as_toplevel() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" provides [main] to "./platform"
|
||||
|
||||
record = { a: 42i64, b: "foo" }
|
||||
|
||||
main =
|
||||
when record is
|
||||
{ a: 42i64 } as r -> record == r
|
||||
_ -> Bool.false
|
||||
"#
|
||||
),
|
||||
true,
|
||||
bool
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
fn pattern_as_nested() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" provides [main] to "./platform"
|
||||
|
||||
record = { a: 42i64, b: "foo" }
|
||||
|
||||
main =
|
||||
when Pair {} record is
|
||||
Pair {} ({ a: 42i64 } as r) -> record == r
|
||||
_ -> Bool.false
|
||||
"#
|
||||
),
|
||||
true,
|
||||
bool
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
|
||||
fn pattern_as_of_symbol() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" provides [main] to "./platform"
|
||||
|
||||
main =
|
||||
when "foo" is
|
||||
a as b -> a == b
|
||||
"#
|
||||
),
|
||||
true,
|
||||
bool
|
||||
);
|
||||
}
|
||||
|
|
37
crates/compiler/test_mono/generated/pattern_as_nested.txt
Normal file
37
crates/compiler/test_mono/generated/pattern_as_nested.txt
Normal file
|
@ -0,0 +1,37 @@
|
|||
procedure Bool.1 ():
|
||||
let Bool.24 : Int1 = false;
|
||||
ret Bool.24;
|
||||
|
||||
procedure Bool.11 (#Attr.2, #Attr.3):
|
||||
let Bool.23 : Int1 = lowlevel Eq #Attr.2 #Attr.3;
|
||||
dec #Attr.3;
|
||||
dec #Attr.2;
|
||||
ret Bool.23;
|
||||
|
||||
procedure Test.1 ():
|
||||
let Test.9 : I64 = 42i64;
|
||||
let Test.10 : Str = "foo";
|
||||
let Test.8 : {I64, Str} = Struct {Test.9, Test.10};
|
||||
ret Test.8;
|
||||
|
||||
procedure Test.0 ():
|
||||
let Test.18 : {} = Struct {};
|
||||
let Test.19 : {I64, Str} = CallByName Test.1;
|
||||
let Test.5 : {{I64, Str}, {}} = Struct {Test.19, Test.18};
|
||||
let Test.14 : {I64, Str} = StructAtIndex 0 Test.5;
|
||||
inc Test.14;
|
||||
let Test.15 : I64 = StructAtIndex 0 Test.14;
|
||||
dec Test.14;
|
||||
let Test.16 : I64 = 42i64;
|
||||
let Test.17 : Int1 = lowlevel Eq Test.16 Test.15;
|
||||
if Test.17 then
|
||||
let Test.13 : {I64, Str} = StructAtIndex 0 Test.5;
|
||||
inc Test.13;
|
||||
dec Test.5;
|
||||
let Test.7 : {I64, Str} = CallByName Test.1;
|
||||
let Test.6 : Int1 = CallByName Bool.11 Test.7 Test.13;
|
||||
ret Test.6;
|
||||
else
|
||||
dec Test.5;
|
||||
let Test.11 : Int1 = CallByName Bool.1;
|
||||
ret Test.11;
|
|
@ -0,0 +1,9 @@
|
|||
procedure Bool.11 (#Attr.2, #Attr.3):
|
||||
let Bool.23 : Int1 = lowlevel Eq #Attr.2 #Attr.3;
|
||||
ret Bool.23;
|
||||
|
||||
procedure Test.0 ():
|
||||
let Test.3 : Str = "foo";
|
||||
let Test.4 : Int1 = CallByName Bool.11 Test.3 Test.3;
|
||||
dec Test.3;
|
||||
ret Test.4;
|
29
crates/compiler/test_mono/generated/pattern_as_toplevel.txt
Normal file
29
crates/compiler/test_mono/generated/pattern_as_toplevel.txt
Normal file
|
@ -0,0 +1,29 @@
|
|||
procedure Bool.1 ():
|
||||
let Bool.24 : Int1 = false;
|
||||
ret Bool.24;
|
||||
|
||||
procedure Bool.11 (#Attr.2, #Attr.3):
|
||||
let Bool.23 : Int1 = lowlevel Eq #Attr.2 #Attr.3;
|
||||
dec #Attr.3;
|
||||
dec #Attr.2;
|
||||
ret Bool.23;
|
||||
|
||||
procedure Test.1 ():
|
||||
let Test.8 : I64 = 42i64;
|
||||
let Test.9 : Str = "foo";
|
||||
let Test.7 : {I64, Str} = Struct {Test.8, Test.9};
|
||||
ret Test.7;
|
||||
|
||||
procedure Test.0 ():
|
||||
let Test.4 : {I64, Str} = CallByName Test.1;
|
||||
let Test.11 : I64 = StructAtIndex 0 Test.4;
|
||||
let Test.12 : I64 = 42i64;
|
||||
let Test.13 : Int1 = lowlevel Eq Test.12 Test.11;
|
||||
if Test.13 then
|
||||
let Test.6 : {I64, Str} = CallByName Test.1;
|
||||
let Test.5 : Int1 = CallByName Bool.11 Test.6 Test.4;
|
||||
ret Test.5;
|
||||
else
|
||||
dec Test.4;
|
||||
let Test.10 : Int1 = CallByName Bool.1;
|
||||
ret Test.10;
|
|
@ -2217,7 +2217,7 @@ fn issue_4749() {
|
|||
|
||||
expect
|
||||
input = [82, 111, 99]
|
||||
got = Decode.fromBytes input Json.fromUtf8
|
||||
got = Decode.fromBytes input Json.fromUtf8
|
||||
got == Ok "Roc"
|
||||
"###
|
||||
)
|
||||
|
@ -2246,7 +2246,7 @@ fn lambda_set_with_imported_toplevels_issue_4733() {
|
|||
fn order_list_size_tests_issue_4732() {
|
||||
indoc!(
|
||||
r###"
|
||||
when [] is
|
||||
when [] is
|
||||
[1, ..] -> "B1"
|
||||
[2, 1, ..] -> "B2"
|
||||
[3, 2, 1, ..] -> "B3"
|
||||
|
@ -2363,3 +2363,48 @@ fn nullable_wrapped_with_nullable_not_last_index() {
|
|||
"###
|
||||
)
|
||||
}
|
||||
|
||||
#[mono_test]
|
||||
fn pattern_as_toplevel() {
|
||||
indoc!(
|
||||
r###"
|
||||
app "test" provides [main] to "./platform"
|
||||
|
||||
record = { a: 42i64, b: "foo" }
|
||||
|
||||
main =
|
||||
when record is
|
||||
{ a: 42i64 } as r -> record == r
|
||||
_ -> Bool.false
|
||||
"###
|
||||
)
|
||||
}
|
||||
|
||||
#[mono_test]
|
||||
fn pattern_as_nested() {
|
||||
indoc!(
|
||||
r###"
|
||||
app "test" provides [main] to "./platform"
|
||||
|
||||
record = { a: 42i64, b: "foo" }
|
||||
|
||||
main =
|
||||
when Pair {} record is
|
||||
Pair {} ({ a: 42i64 } as r) -> record == r
|
||||
_ -> Bool.false
|
||||
"###
|
||||
)
|
||||
}
|
||||
|
||||
#[mono_test]
|
||||
fn pattern_as_of_symbol() {
|
||||
indoc!(
|
||||
r###"
|
||||
app "test" provides [main] to "./platform"
|
||||
|
||||
main =
|
||||
when "foo" is
|
||||
a as b -> a == b
|
||||
"###
|
||||
)
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue