Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[lang] Deeply nested pattern matching #1126

Merged
merged 1 commit into from
Oct 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions crates/samlang-core/src/ast/source.rs
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,7 @@ pub(crate) struct AnnotatedId<T: Clone> {

pub(crate) mod expr {
use super::super::loc::Location;
use super::{annotation, CommentReference, Id, Literal};
use super::{annotation, pattern, CommentReference, Id, Literal};
use samlang_heap::{ModuleReference, PStr};
use std::collections::HashMap;

Expand Down Expand Up @@ -477,9 +477,7 @@ pub(crate) mod expr {
#[derive(Clone, PartialEq, Eq)]
pub(crate) struct VariantPatternToExpression<T: Clone> {
pub(crate) loc: Location,
pub(crate) tag: Id,
pub(crate) tag_order: usize,
pub(crate) data_variables: Vec<Option<(Id, T)>>,
pub(crate) pattern: pattern::MatchingPattern<T>,
pub(crate) body: Box<E<T>>,
}

Expand Down
4 changes: 1 addition & 3 deletions crates/samlang-core/src/ast/source_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -314,9 +314,7 @@ mod tests {
matched: Box::new(zero_expr.clone()),
cases: vec![expr::VariantPatternToExpression {
loc: Location::dummy(),
tag: Id::from(heap.alloc_str_for_test("name")),
tag_order: 1,
data_variables: vec![],
pattern: pattern::MatchingPattern::Wildcard(Location::dummy()),
body: Box::new(zero_expr.clone()),
}],
}));
Expand Down
86 changes: 38 additions & 48 deletions crates/samlang-core/src/checker/checker_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2907,47 +2907,73 @@ Found 12 errors.
"match (3) { Foo(_) -> 1, Bar(s) -> 2 }",
&builder.unit_type(),
r#"
Error ----------------------------------- DUMMY.sam:1:1-1:39

`int` [1] is incompatible with `unit` .

1| match (3) { Foo(_) -> 1, Bar(s) -> 2 }
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

[1] DUMMY.sam:1:1-1:39
----------------------
1| match (3) { Foo(_) -> 1, Bar(s) -> 2 }
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^


Error ---------------------------------- DUMMY.sam:1:13-1:16

Cannot resolve member `Foo` on `int`.
`int` is not an instance of an enum class.

1| match (3) { Foo(_) -> 1, Bar(s) -> 2 }
^^^


Error ---------------------------------- DUMMY.sam:1:26-1:29

Cannot resolve member `Bar` on `int`.
`int` is not an instance of an enum class.

1| match (3) { Foo(_) -> 1, Bar(s) -> 2 }
^^^


Found 2 errors.
Found 3 errors.
"#,
);
assert_errors(
heap,
"match (Test.init(true, 3)) { Foo(_) -> 1, Bar(s) -> 2, }",
&builder.unit_type(),
r#"
Error ----------------------------------- DUMMY.sam:1:1-1:57

`int` [1] is incompatible with `unit` .

1| match (Test.init(true, 3)) { Foo(_) -> 1, Bar(s) -> 2, }
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

[1] DUMMY.sam:1:1-1:57
----------------------
1| match (Test.init(true, 3)) { Foo(_) -> 1, Bar(s) -> 2, }
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^


Error ---------------------------------- DUMMY.sam:1:30-1:33

Cannot resolve member `Foo` on `Test`.
`Test` is not an instance of an enum class.

1| match (Test.init(true, 3)) { Foo(_) -> 1, Bar(s) -> 2, }
^^^


Error ---------------------------------- DUMMY.sam:1:43-1:46

Cannot resolve member `Bar` on `Test`.
`Test` is not an instance of an enum class.

1| match (Test.init(true, 3)) { Foo(_) -> 1, Bar(s) -> 2, }
^^^


Found 2 errors.
Found 3 errors.
"#,
);
assert_errors_full_customization(
Expand All @@ -2957,8 +2983,8 @@ Found 2 errors.
r#"
Error ---------------------------------- DUMMY.sam:1:25-1:64

The match is not exhausive. The following variants have not been handled:
- `Bar`
This pattern-matching is not exhausive.
Here is an example of a non-matching value: `Bar(_)`.

1| { let _ = (t: Test2) -> match (t) { Foo(_) -> 1, Baz(s) -> 2, }; }
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Expand Down Expand Up @@ -3242,12 +3268,12 @@ Function parameter arity of 1 is incompatible with function parameter arity of 0
^^^^^^^^^^^^^^


Error ---------------------------------- DUMMY.sam:6:32-6:51
Error ---------------------------------- DUMMY.sam:6:39-6:40

Data variable arity of 2 is incompatible with data variable arity of 1.
Cannot access member of `Test2` at index 1.

6| match (Test2.Foo(false)) { Foo(_, _) -> false, Bar(_) -> false, }
^^^^^^^^^^^^^^^^^^^
^


Error ---------------------------------- DUMMY.sam:8:11-8:64
Expand Down Expand Up @@ -3513,42 +3539,6 @@ Type argument arity of 0 is incompatible with type argument arity of 2.
^


Error -------------------------------------- C.sam:5:30-5:83

`bool` [1] is incompatible with `int` [2].

5| method intValue(): int = match (this) { Int(v) -> v, Boo(b) -> b.intValue(), }
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

[1] C.sam:5:30-5:83
-------------------
5| method intValue(): int = match (this) { Int(v) -> v, Boo(b) -> b.intValue(), }
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

[2] C.sam:5:24-5:27
-------------------
5| method intValue(): int = match (this) { Int(v) -> v, Boo(b) -> b.intValue(), }
^^^


Error -------------------------------------- C.sam:5:58-5:81

`int` [1] is incompatible with `bool` [2].

5| method intValue(): int = match (this) { Int(v) -> v, Boo(b) -> b.intValue(), }
^^^^^^^^^^^^^^^^^^^^^^^

[1] C.sam:5:68-5:80
-------------------
5| method intValue(): int = match (this) { Int(v) -> v, Boo(b) -> b.intValue(), }
^^^^^^^^^^^^

[2] C.sam:5:55-5:56
-------------------
5| method intValue(): int = match (this) { Int(v) -> v, Boo(b) -> b.intValue(), }
^


Error -------------------------------------- D.sam:2:15-2:16

There is no `D` export in `B`.
Expand All @@ -3570,7 +3560,7 @@ Name `c1` collides with a previously defined name at [1].
^^


Found 15 errors.
Found 13 errors.
"#,
);
}
Expand Down
61 changes: 12 additions & 49 deletions crates/samlang-core/src/checker/main_checker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -936,67 +936,30 @@ fn check_match(
hint: type_hint::Hint,
) -> expr::E<Rc<Type>> {
let checked_matched = type_check_expression(cx, &expression.matched, type_hint::MISSING);
let checked_matched_type = checked_matched.type_().deref();
let variants = cx.resolve_enum_definitions(checked_matched_type);
let mut orders = HashMap::new();
let mut unused_mappings = HashMap::new();
for (i, variant) in variants.into_iter().enumerate() {
orders.insert(variant.name, i);
unused_mappings.insert(variant.name, variant.types);
}
let checked_matched_type = checked_matched.type_();
let mut checked_cases = vec![];
let mut matching_list_type: Option<Rc<Type>> = None;
for expr::VariantPatternToExpression { loc, tag, tag_order: _, data_variables, body } in
&expression.cases
{
let mapping_data_types = match unused_mappings.remove(&tag.name) {
Some(types) => types,
None => {
cx.error_set.report_cannot_resolve_member_error(
tag.loc,
checked_matched_type.to_description(),
tag.name,
);
continue;
}
};
if data_variables.len() != mapping_data_types.len() {
let mut error = StackableError::new();
error.add_data_variables_arity_error(data_variables.len(), mapping_data_types.len());
cx.error_set.report_stackable_error(*loc, error);
}
let checked_data_variables = data_variables
.iter()
.zip(mapping_data_types)
.map(|(dv, t)| {
if let Some((data_variable, _)) = dv {
cx.local_typing_context.write(data_variable.loc, t.clone());
Some((*data_variable, t))
} else {
None
}
})
.collect_vec();
let mut abstract_pattern_nodes = Vec::with_capacity(expression.cases.len());
for expr::VariantPatternToExpression { loc, pattern, body } in &expression.cases {
let (pattern, abstract_pattern_node) =
check_matching_pattern(cx, pattern, true, checked_matched_type);
abstract_pattern_nodes.push(abstract_pattern_node);
let checked_body = type_check_expression(cx, body, hint);
let tag_order = *orders.get(&tag.name).unwrap();
match &matching_list_type {
Some(expected) => assignability_check(cx, *loc, checked_body.type_(), expected),
None => matching_list_type = Some(checked_body.type_().clone()),
}
checked_cases.push(expr::VariantPatternToExpression {
loc: *loc,
tag: *tag,
tag_order,
data_variables: checked_data_variables,
pattern,
body: Box::new(checked_body),
});
}
if !unused_mappings.is_empty() {
cx.error_set.report_non_exhausive_match_for_match_expr_error(
expression.common.loc,
unused_mappings.keys().copied().collect(),
);
};
if let Some(description) =
pattern_matching::incomplete_counterexample(cx, &abstract_pattern_nodes)
{
cx.error_set.report_non_exhausive_match_error(expression.common.loc, description);
}
expr::E::Match(expr::Match {
common: expression.common.with_new_type(Rc::new(
matching_list_type
Expand Down
4 changes: 1 addition & 3 deletions crates/samlang-core/src/checker/ssa_analysis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -290,9 +290,7 @@ impl<'a> SsaAnalysisState<'a> {
self.visit_expression(&e.matched);
for case in &e.cases {
self.context.push_scope();
for (id, _) in case.data_variables.iter().filter_map(|it| it.as_ref()) {
self.define_id(id.name, id.loc);
}
self.visit_matching_pattern(&case.pattern);
self.visit_expression(&case.body);
let (local_defs, _) = self.context.pop_scope();
self.local_scoped_def_locs.insert(case.loc, local_defs);
Expand Down
11 changes: 0 additions & 11 deletions crates/samlang-core/src/checker/typing_context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -316,17 +316,6 @@ impl<'a> TypingContext<'a> {
}
}

pub(super) fn resolve_enum_definitions(
&self,
type_: &Type,
) -> Vec<EnumVariantDefinitionSignature> {
if let Some((_, _, result)) = self.resolve_detailed_enum_definitions_opt(type_) {
result
} else {
Vec::with_capacity(0)
}
}

fn resolve_type_definition(
&self,
type_: &Type,
Expand Down
8 changes: 5 additions & 3 deletions crates/samlang-core/src/checker/typing_context_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -821,9 +821,11 @@ Found 3 errors.
}))
.is_none());

let resolved = cx.resolve_enum_definitions(
&builder.general_nominal_type(PStr::UPPER_A, vec![builder.int_type(), builder.int_type()]),
);
let (_, _, resolved) = cx
.resolve_detailed_enum_definitions_opt(
&builder.general_nominal_type(PStr::UPPER_A, vec![builder.int_type(), builder.int_type()]),
)
.unwrap();
assert_eq!(2, resolved.len());
let resolved_a = &resolved[0];
let resolved_b = &resolved[1];
Expand Down
Loading