From c130ace959596a0f95e0fa7bac27a8be937da2bb Mon Sep 17 00:00:00 2001 From: Sam Zhou Date: Sun, 29 Oct 2023 20:55:08 -0700 Subject: [PATCH] [lang] Deeply nested pattern matching --- crates/samlang-core/src/ast/source.rs | 6 +- crates/samlang-core/src/ast/source_tests.rs | 4 +- .../samlang-core/src/checker/checker_tests.rs | 86 ++++---- .../samlang-core/src/checker/main_checker.rs | 61 ++---- .../samlang-core/src/checker/ssa_analysis.rs | 4 +- .../src/checker/typing_context.rs | 11 -- .../src/checker/typing_context_tests.rs | 8 +- .../samlang-core/src/compiler/hir_lowering.rs | 184 ++++++++++++------ crates/samlang-core/src/errors.rs | 44 +---- .../loop_invariant_code_motion.rs | 11 +- .../samlang-core/src/parser/source_parser.rs | 30 +-- .../src/printer/source_printer.rs | 11 +- crates/samlang-core/src/services/gc.rs | 6 +- .../src/services/global_searcher.rs | 13 +- .../src/services/variable_definition.rs | 11 +- tests/SortableList.sam | 46 ++--- 16 files changed, 215 insertions(+), 321 deletions(-) diff --git a/crates/samlang-core/src/ast/source.rs b/crates/samlang-core/src/ast/source.rs index 7988d802..9619beeb 100644 --- a/crates/samlang-core/src/ast/source.rs +++ b/crates/samlang-core/src/ast/source.rs @@ -317,7 +317,7 @@ pub(crate) struct AnnotatedId { 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; @@ -477,9 +477,7 @@ pub(crate) mod expr { #[derive(Clone, PartialEq, Eq)] pub(crate) struct VariantPatternToExpression { pub(crate) loc: Location, - pub(crate) tag: Id, - pub(crate) tag_order: usize, - pub(crate) data_variables: Vec>, + pub(crate) pattern: pattern::MatchingPattern, pub(crate) body: Box>, } diff --git a/crates/samlang-core/src/ast/source_tests.rs b/crates/samlang-core/src/ast/source_tests.rs index 926409fc..9c2f8b33 100644 --- a/crates/samlang-core/src/ast/source_tests.rs +++ b/crates/samlang-core/src/ast/source_tests.rs @@ -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()), }], })); diff --git a/crates/samlang-core/src/checker/checker_tests.rs b/crates/samlang-core/src/checker/checker_tests.rs index 618d9029..054dd5ec 100644 --- a/crates/samlang-core/src/checker/checker_tests.rs +++ b/crates/samlang-core/src/checker/checker_tests.rs @@ -2907,9 +2907,22 @@ 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 } ^^^ @@ -2917,13 +2930,13 @@ Cannot resolve member `Foo` on `int`. 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( @@ -2931,9 +2944,22 @@ Found 2 errors. "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, } ^^^ @@ -2941,13 +2967,13 @@ Cannot resolve member `Foo` on `Test`. 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( @@ -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, }; } ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -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 @@ -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`. @@ -3570,7 +3560,7 @@ Name `c1` collides with a previously defined name at [1]. ^^ -Found 15 errors. +Found 13 errors. "#, ); } diff --git a/crates/samlang-core/src/checker/main_checker.rs b/crates/samlang-core/src/checker/main_checker.rs index 379c4579..8224766e 100644 --- a/crates/samlang-core/src/checker/main_checker.rs +++ b/crates/samlang-core/src/checker/main_checker.rs @@ -936,67 +936,30 @@ fn check_match( hint: type_hint::Hint, ) -> expr::E> { 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> = 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 diff --git a/crates/samlang-core/src/checker/ssa_analysis.rs b/crates/samlang-core/src/checker/ssa_analysis.rs index f3def1df..9ac6135b 100644 --- a/crates/samlang-core/src/checker/ssa_analysis.rs +++ b/crates/samlang-core/src/checker/ssa_analysis.rs @@ -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); diff --git a/crates/samlang-core/src/checker/typing_context.rs b/crates/samlang-core/src/checker/typing_context.rs index 4b18b05d..7fe727ae 100644 --- a/crates/samlang-core/src/checker/typing_context.rs +++ b/crates/samlang-core/src/checker/typing_context.rs @@ -316,17 +316,6 @@ impl<'a> TypingContext<'a> { } } - pub(super) fn resolve_enum_definitions( - &self, - type_: &Type, - ) -> Vec { - if let Some((_, _, result)) = self.resolve_detailed_enum_definitions_opt(type_) { - result - } else { - Vec::with_capacity(0) - } - } - fn resolve_type_definition( &self, type_: &Type, diff --git a/crates/samlang-core/src/checker/typing_context_tests.rs b/crates/samlang-core/src/checker/typing_context_tests.rs index 39f79c93..883cb9f6 100644 --- a/crates/samlang-core/src/checker/typing_context_tests.rs +++ b/crates/samlang-core/src/checker/typing_context_tests.rs @@ -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]; diff --git a/crates/samlang-core/src/compiler/hir_lowering.rs b/crates/samlang-core/src/compiler/hir_lowering.rs index de8e930b..e28c067f 100644 --- a/crates/samlang-core/src/compiler/hir_lowering.rs +++ b/crates/samlang-core/src/compiler/hir_lowering.rs @@ -854,49 +854,38 @@ impl<'a> ExpressionLoweringManager<'a> { }], hir::Expression::var_name(unreachable_branch_collector, final_return_type), ); - for source::expr::VariantPatternToExpression { tag_order, data_variables, body, .. } in + for source::expr::VariantPatternToExpression { loc: _, pattern, body } in expression.cases.iter().rev() { let final_assignment_temp = self.allocate_temp_variable(); let lowered_return_type = acc.1.type_().clone(); let (acc_stmts, acc_e) = acc; - let mut local_stmts = vec![]; + let mut new_stmts = vec![]; self.variable_cx.push_scope(); - let mut bindings = vec![]; - for dv in data_variables { - if let Some(( - source::Id { loc: _, associated_comments: _, name: original_name }, - data_var_type, - )) = dv - { - let data_var_type = - self.type_lowering_manager.lower_source_type(self.heap, data_var_type); - let name = self.allocate_temp_variable(); - bind_value( - &mut self.variable_cx, - *original_name, - hir::Expression::var_name(name, data_var_type.clone()), - ); - bindings.push(Some((name, data_var_type))); - } else { - bindings.push(None); - } + let mut binding_names = HashMap::new(); + for (n, t) in pattern.bindings() { + let name = self.allocate_temp_variable(); + binding_names.insert(n, name); + let type_ = self.type_lowering_manager.lower_source_type(self.heap, t); + bind_value(&mut self.variable_cx, n, hir::Expression::var_name(name, type_.clone())); + new_stmts.push(hir::Statement::LateInitDeclaration { name, type_ }); } - let final_expr = self.lowered_and_add_statements(body, &mut local_stmts); + let LoweringResult { statements: mut binding_stmts, expression: match_success_condition } = + self.lower_matching_pattern(pattern, &binding_names, matched_expr.clone()); + new_stmts.append(&mut binding_stmts); + let body_lowering_result = self.lower(body); self.variable_cx.pop_scope(); - let new_stmts = vec![hir::Statement::ConditionalDestructure { - test_expr: matched_expr.clone(), - tag: *tag_order, - bindings, - s1: local_stmts, + new_stmts.push(hir::Statement::IfElse { + condition: match_success_condition, + s1: body_lowering_result.statements, s2: acc_stmts, final_assignments: vec![( final_assignment_temp, lowered_return_type.clone(), - final_expr, + body_lowering_result.expression, acc_e, )], - }]; + }); acc = (new_stmts, hir::Expression::var_name(final_assignment_temp, lowered_return_type)) } @@ -2171,12 +2160,19 @@ return (_t1: DUMMY_Dummy);"#, cases: vec![ source::expr::VariantPatternToExpression { loc: Location::dummy(), - tag: source::Id::from(heap.alloc_str_for_test("Foo")), - tag_order: 0, - data_variables: vec![Some(( - source::Id::from(heap.alloc_str_for_test("bar")), - builder.int_type(), - ))], + pattern: source::pattern::MatchingPattern::Variant(source::pattern::VariantPattern { + loc: Location::dummy(), + tag_order: 0, + tag: source::Id::from(heap.alloc_str_for_test("Foo")), + data_variables: vec![( + source::pattern::MatchingPattern::Id( + source::Id::from(heap.alloc_str_for_test("bar")), + builder.int_type(), + ), + builder.int_type(), + )], + type_: Rc::new(dummy_source_id_type(heap)), + }), body: Box::new(source::expr::E::LocalId( source::expr::ExpressionCommon::dummy(Rc::new(dummy_source_id_type(heap))), source::Id::from(heap.alloc_str_for_test("bar")), @@ -2184,9 +2180,16 @@ return (_t1: DUMMY_Dummy);"#, }, source::expr::VariantPatternToExpression { loc: Location::dummy(), - tag: source::Id::from(heap.alloc_str_for_test("Bar")), - tag_order: 1, - data_variables: vec![None], + pattern: source::pattern::MatchingPattern::Variant(source::pattern::VariantPattern { + loc: Location::dummy(), + tag_order: 1, + tag: source::Id::from(heap.alloc_str_for_test("Bar")), + data_variables: vec![( + source::pattern::MatchingPattern::Wildcard(Location::dummy()), + builder.int_type(), + )], + type_: Rc::new(dummy_source_id_type(heap)), + }), body: Box::new(dummy_source_this(heap)), }, ], @@ -2194,18 +2197,32 @@ return (_t1: DUMMY_Dummy);"#, heap, r#"const GLOBAL_STRING_0 = ''; -let [_t4: int] if tagof((_this: DUMMY_Dummy))==0 { - _t3 = (_t4: int); +let _t6: int; +let [_t7: int] if tagof((_this: DUMMY_Dummy))==0 { + _t6 = (_t7: int); + _t8 = 1; +} else { + _t8 = 0; +} +let _t5: DUMMY_Dummy; +if (_t8: int) { + _t5 = (_t6: int); } else { - let [_] if tagof((_this: DUMMY_Dummy))==1 { + let [_t3: int] if tagof((_this: DUMMY_Dummy))==1 { + _t4 = 1; + } else { + _t4 = 0; + } + let _t2: DUMMY_Dummy; + if (_t4: int) { _t2 = (_this: DUMMY_Dummy); } else { let _t1: DUMMY_Dummy = _Process$panic(0, GLOBAL_STRING_0); _t2 = (_t1: DUMMY_Dummy); } - _t3 = (_t2: DUMMY_Dummy); + _t5 = (_t2: DUMMY_Dummy); } -return (_t3: DUMMY_Dummy);"#, +return (_t5: DUMMY_Dummy);"#, ); let heap = &mut Heap::new(); @@ -2216,19 +2233,33 @@ return (_t3: DUMMY_Dummy);"#, cases: vec![ source::expr::VariantPatternToExpression { loc: Location::dummy(), - tag: source::Id::from(heap.alloc_str_for_test("Foo")), - tag_order: 0, - data_variables: vec![None], + pattern: source::pattern::MatchingPattern::Variant(source::pattern::VariantPattern { + loc: Location::dummy(), + tag_order: 0, + tag: source::Id::from(heap.alloc_str_for_test("Foo")), + data_variables: vec![( + source::pattern::MatchingPattern::Wildcard(Location::dummy()), + builder.int_type(), + )], + type_: Rc::new(dummy_source_id_type(heap)), + }), body: Box::new(dummy_source_this(heap)), }, source::expr::VariantPatternToExpression { loc: Location::dummy(), - tag: source::Id::from(heap.alloc_str_for_test("Bar")), - tag_order: 1, - data_variables: vec![Some(( - source::Id::from(heap.alloc_str_for_test("bar")), - Rc::new(dummy_source_id_type(heap)), - ))], + pattern: source::pattern::MatchingPattern::Variant(source::pattern::VariantPattern { + loc: Location::dummy(), + tag_order: 1, + tag: source::Id::from(heap.alloc_str_for_test("Bar")), + data_variables: vec![( + source::pattern::MatchingPattern::Id( + source::Id::from(heap.alloc_str_for_test("bar")), + builder.int_type(), + ), + builder.int_type(), + )], + type_: Rc::new(dummy_source_id_type(heap)), + }), body: Box::new(id_expr( heap.alloc_str_for_test("bar"), Rc::new(dummy_source_id_type(heap)), @@ -2236,9 +2267,16 @@ return (_t3: DUMMY_Dummy);"#, }, source::expr::VariantPatternToExpression { loc: Location::dummy(), - tag: source::Id::from(heap.alloc_str_for_test("Baz")), - tag_order: 2, - data_variables: vec![None], + pattern: source::pattern::MatchingPattern::Variant(source::pattern::VariantPattern { + loc: Location::dummy(), + tag_order: 2, + tag: source::Id::from(heap.alloc_str_for_test("Baz")), + data_variables: vec![( + source::pattern::MatchingPattern::Wildcard(Location::dummy()), + builder.int_type(), + )], + type_: Rc::new(dummy_source_id_type(heap)), + }), body: Box::new(dummy_source_this(heap)), }, ], @@ -2246,23 +2284,43 @@ return (_t3: DUMMY_Dummy);"#, heap, r#"const GLOBAL_STRING_0 = ''; -let [_] if tagof((_this: DUMMY_Dummy))==0 { - _t5 = (_this: DUMMY_Dummy); +let [_t10: int] if tagof((_this: DUMMY_Dummy))==0 { + _t11 = 1; +} else { + _t11 = 0; +} +let _t9: DUMMY_Dummy; +if (_t11: int) { + _t9 = (_this: DUMMY_Dummy); } else { - let [_t4: DUMMY_Dummy] if tagof((_this: DUMMY_Dummy))==1 { - _t3 = (_t4: DUMMY_Dummy); + let _t6: int; + let [_t7: int] if tagof((_this: DUMMY_Dummy))==1 { + _t6 = (_t7: int); + _t8 = 1; } else { - let [_] if tagof((_this: DUMMY_Dummy))==2 { + _t8 = 0; + } + let _t5: DUMMY_Dummy; + if (_t8: int) { + _t5 = (_t6: int); + } else { + let [_t3: int] if tagof((_this: DUMMY_Dummy))==2 { + _t4 = 1; + } else { + _t4 = 0; + } + let _t2: DUMMY_Dummy; + if (_t4: int) { _t2 = (_this: DUMMY_Dummy); } else { let _t1: DUMMY_Dummy = _Process$panic(0, GLOBAL_STRING_0); _t2 = (_t1: DUMMY_Dummy); } - _t3 = (_t2: DUMMY_Dummy); + _t5 = (_t2: DUMMY_Dummy); } - _t5 = (_t3: DUMMY_Dummy); + _t9 = (_t5: DUMMY_Dummy); } -return (_t5: DUMMY_Dummy);"#, +return (_t9: DUMMY_Dummy);"#, ); } diff --git a/crates/samlang-core/src/errors.rs b/crates/samlang-core/src/errors.rs index 877c29a4..2b93a97e 100644 --- a/crates/samlang-core/src/errors.rs +++ b/crates/samlang-core/src/errors.rs @@ -456,7 +456,6 @@ pub(crate) struct TypeIncompatibilityNode { #[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] pub(crate) enum IncompatibilityNode { Type(Box), - DataVariablesArity(usize, usize), FunctionParametersArity(usize, usize), TypeArgumentsArity(usize, usize), TypeParametersArity(usize, usize), @@ -480,10 +479,6 @@ impl StackableError { self.rev_stack.push(IncompatibilityNode::Type(Box::new(node))); } - pub(crate) fn add_data_variables_arity_error(&mut self, lower: usize, upper: usize) { - self.rev_stack.push(IncompatibilityNode::DataVariablesArity(lower, upper)); - } - pub(crate) fn add_fn_param_arity_error(&mut self, lower: usize, upper: usize) { self.rev_stack.push(IncompatibilityNode::FunctionParametersArity(lower, upper)); } @@ -506,7 +501,6 @@ mod stackable_error_tests { fn boilterplate() { let mut stacked = super::StackableError::new(); stacked.add_type_args_arity_error(0, 0); - stacked.add_data_variables_arity_error(0, 0); stacked.add_fn_param_arity_error(0, 0); stacked.add_type_args_arity_error(0, 0); stacked.add_type_params_arity_error(0, 0); @@ -541,7 +535,6 @@ pub(crate) enum ErrorDetail { NameAlreadyBound { name: PStr, old_loc: Location }, NonExhausiveStructBinding { missing_bindings: Vec }, NonExhausiveTupleBinding { expected_count: usize, actual_count: usize }, - NonExhausiveMatchForMatchExpr { missing_tags: Vec }, NonExhausiveMatch { counter_example: Description }, NotAnEnum { description: Description }, NotAStruct { description: Description }, @@ -650,15 +643,6 @@ impl ErrorDetail { printable_stream.push_size(*actual_count); printable_stream.push_text("."); } - ErrorDetail::NonExhausiveMatchForMatchExpr { missing_tags } => { - printable_stream - .push_text("The match is not exhausive. The following variants have not been handled:"); - for tag in missing_tags { - printable_stream.push_text("\n- `"); - printable_stream.push_pstr(tag); - printable_stream.push_text("`"); - } - } ErrorDetail::NonExhausiveMatch { counter_example } => { printable_stream.push_text( "This pattern-matching is not exhausive.\nHere is an example of a non-matching value: `", @@ -705,13 +689,6 @@ impl ErrorDetail { printable_stream.push_text("`."); } } - IncompatibilityNode::DataVariablesArity(l, u) => { - printable_stream.push_text("Data variable arity of "); - printable_stream.push_size(*l); - printable_stream.push_text(" is incompatible with data variable arity of "); - printable_stream.push_size(*u); - printable_stream.push_text("."); - } IncompatibilityNode::FunctionParametersArity(l, u) => { printable_stream.push_text("Function parameter arity of "); printable_stream.push_size(*l); @@ -1011,14 +988,6 @@ impl ErrorSet { self.report_error(loc, ErrorDetail::NonExhausiveTupleBinding { expected_count, actual_count }) } - pub(crate) fn report_non_exhausive_match_for_match_expr_error( - &mut self, - loc: Location, - missing_tags: Vec, - ) { - self.report_error(loc, ErrorDetail::NonExhausiveMatchForMatchExpr { missing_tags }) - } - pub(crate) fn report_non_exhausive_match_error( &mut self, loc: Location, @@ -1209,10 +1178,6 @@ Found 2 errors."# vec![PStr::UPPER_A, PStr::UPPER_B], ); error_set.report_non_exhausive_tuple_binding_error(Location::dummy(), 7, 4); - error_set.report_non_exhausive_match_for_match_expr_error( - Location::dummy(), - vec![PStr::UPPER_A, PStr::UPPER_B], - ); error_set.report_non_exhausive_match_error(Location::dummy(), Description::IntType); error_set.report_not_an_enum_error(Location::dummy(), Description::IntType); error_set.report_not_a_struct_error(Location::dummy(), Description::IntType); @@ -1310,13 +1275,6 @@ Error ------------------------------------ DUMMY.sam:0:0-0:0 The pattern does not bind all fields. Expected number of elements: 7, actual number of elements: 4. -Error ------------------------------------ DUMMY.sam:0:0-0:0 - -The match is not exhausive. The following variants have not been handled: -- `A` -- `B` - - Error ------------------------------------ DUMMY.sam:0:0-0:0 This pattern-matching is not exhausive. @@ -1372,7 +1330,7 @@ Very/Very/Very/Very/Very/Very/Very/Very/Very/Very/Very/Very/Very/Very/Very/Long. Cannot resolve name `global`. -Found 24 errors. +Found 23 errors. "#; assert_eq!( expected_errors.trim(), diff --git a/crates/samlang-core/src/optimization/loop_invariant_code_motion.rs b/crates/samlang-core/src/optimization/loop_invariant_code_motion.rs index 5337996d..7136fac9 100644 --- a/crates/samlang-core/src/optimization/loop_invariant_code_motion.rs +++ b/crates/samlang-core/src/optimization/loop_invariant_code_motion.rs @@ -48,8 +48,7 @@ pub(super) fn optimize( inner_stmts.push(stmt); } } - Statement::Cast { name, type_: _, assigned_expression } - | Statement::LateInitAssignment { name, assigned_expression } => { + Statement::Cast { name, type_: _, assigned_expression } => { if expression_is_loop_invariant(assigned_expression, &non_loop_invariant_variables) { hoisted_stmts.push(stmt); } else { @@ -57,7 +56,11 @@ pub(super) fn optimize( inner_stmts.push(stmt); } } - Statement::LateInitDeclaration { .. } => hoisted_stmts.push(stmt), + Statement::LateInitDeclaration { name, type_: _ } + | Statement::LateInitAssignment { name, assigned_expression: _ } => { + non_loop_invariant_variables.insert(*name); + inner_stmts.push(stmt) + } Statement::StructInit { struct_variable_name, type_name: _, expression_list } => { if expression_list .iter() @@ -322,7 +325,6 @@ let d: int = (c: int)[0]; let h: _I = Closure { fun: (__$f: () -> int), context: (d: int) }; let kk: _I = [0]; let l1 = 0 as int; -let l3: int; let i: int = 0; let j: int = 0; let x: int = 0; @@ -347,6 +349,7 @@ while (true) { let g: _I = Closure { fun: (__$f: () -> int), context: (x: int) }; let kk2: _I = [(g: int)]; let l2 = (i: int) as int; + let l3: int; l3 = (i: int); let bad: int; if 0 { diff --git a/crates/samlang-core/src/parser/source_parser.rs b/crates/samlang-core/src/parser/source_parser.rs index e241fc60..b793a2c4 100644 --- a/crates/samlang-core/src/parser/source_parser.rs +++ b/crates/samlang-core/src/parser/source_parser.rs @@ -697,7 +697,7 @@ impl<'a> SourceParser<'a> { let match_expression = self.parse_expression_with_ending_comments(); self.assert_and_consume_operator(TokenOp::LBRACE); let mut matching_list = vec![self.parse_pattern_to_expression()]; - while let TokenContent::UpperId(_) = self.peek().1 { + while !matches!(self.peek().1, TokenContent::Operator(TokenOp::RBRACE)) { matching_list.push(self.parse_pattern_to_expression()); } let loc = peeked_loc.union(&self.assert_and_consume_operator(TokenOp::RBRACE)); @@ -716,26 +716,10 @@ impl<'a> SourceParser<'a> { } fn parse_pattern_to_expression(&mut self) -> expr::VariantPatternToExpression<()> { - let tag = self.parse_upper_id(); - let data_variables = if let Token(_, TokenContent::Operator(TokenOp::LPAREN)) = self.peek() { - self.consume(); - let results = self.parse_comma_separated_list(Some(TokenOp::RPAREN), &mut |s: &mut Self| { - if let TokenContent::Operator(TokenOp::UNDERSCORE) = s.peek().1 { - s.consume(); - None - } else { - let name = s.parse_lower_id(); - Some((name, ())) - } - }); - self.assert_and_consume_operator(TokenOp::RPAREN); - results - } else { - vec![] - }; + let pattern = self.parse_matching_pattern(); self.assert_and_consume_operator(TokenOp::ARROW); let expression = self.parse_expression(); - let mut loc = tag.loc.union(&expression.loc()); + let mut loc = pattern.loc().union(&expression.loc()); if matches!(expression, expr::E::Block(_) | expr::E::Match(_)) || matches!(self.peek().1, TokenContent::Operator(TokenOp::RBRACE)) { @@ -745,13 +729,7 @@ impl<'a> SourceParser<'a> { } else { loc = loc.union(&self.assert_and_consume_operator(TokenOp::COMMA)); } - expr::VariantPatternToExpression { - loc, - tag, - tag_order: 0, - data_variables, - body: Box::new(expression), - } + expr::VariantPatternToExpression { loc, pattern, body: Box::new(expression) } } fn parse_if_else(&mut self) -> expr::E<()> { diff --git a/crates/samlang-core/src/printer/source_printer.rs b/crates/samlang-core/src/printer/source_printer.rs index da2eaa99..ad58790d 100644 --- a/crates/samlang-core/src/printer/source_printer.rs +++ b/crates/samlang-core/src/printer/source_printer.rs @@ -567,16 +567,7 @@ impl expr::E<()> { expr::E::Match(e) => { let mut list = vec![]; for case in &e.cases { - list.push(Document::Text(rc_pstr(heap, case.tag.name))); - if !case.data_variables.is_empty() { - list.push(parenthesis_surrounded_doc(comma_sep_list(&case.data_variables, |v| { - Document::Text(if let Some((data_var, _)) = v { - rc_pstr(heap, data_var.name) - } else { - rcs("_") - }) - }))); - } + list.push(matching_pattern_to_document(heap, &case.pattern)); list.push(Document::Text(rcs(" -> "))); list.push(case.body.create_doc(heap, comment_store)); if !matches!(case.body.deref(), expr::E::Block(_) | expr::E::Match(_)) { diff --git a/crates/samlang-core/src/services/gc.rs b/crates/samlang-core/src/services/gc.rs index a42050b3..830fe512 100644 --- a/crates/samlang-core/src/services/gc.rs +++ b/crates/samlang-core/src/services/gc.rs @@ -152,11 +152,7 @@ fn mark_expression(heap: &mut Heap, expr: &expr::E>) { expr::E::Match(e) => { mark_expression(heap, &e.matched); for case in &e.cases { - mark_id(heap, &case.tag); - for (v, t) in case.data_variables.iter().filter_map(|it| it.as_ref()) { - mark_id(heap, v); - mark_type(heap, t); - } + mark_matching_pattern(heap, &case.pattern); mark_expression(heap, &case.body); } } diff --git a/crates/samlang-core/src/services/global_searcher.rs b/crates/samlang-core/src/services/global_searcher.rs index 7508581a..de1e013a 100644 --- a/crates/samlang-core/src/services/global_searcher.rs +++ b/crates/samlang-core/src/services/global_searcher.rs @@ -161,18 +161,7 @@ fn search_expression( expr::E::Match(e) => { search_expression(&e.matched, request, collector); for case in &e.cases { - match (request, e.matched.type_().as_nominal()) { - ( - GlobalNameSearchRequest::InterfaceMember(mod_ref, toplevel_name, fn_name, false), - Some(nominal_type), - ) if mod_ref.eq(&nominal_type.module_reference) - && toplevel_name.eq(&nominal_type.id) - && fn_name.eq(&case.tag.name) => - { - collector.push(case.tag.loc); - } - _ => {} - } + search_matching_pattern(&case.pattern, e.matched.type_(), request, collector); search_expression(&case.body, request, collector); } } diff --git a/crates/samlang-core/src/services/variable_definition.rs b/crates/samlang-core/src/services/variable_definition.rs index 33c7eebd..af84008d 100644 --- a/crates/samlang-core/src/services/variable_definition.rs +++ b/crates/samlang-core/src/services/variable_definition.rs @@ -230,17 +230,10 @@ fn apply_expr_renaming( cases: e .cases .iter() - .map(|expr::VariantPatternToExpression { loc, tag, tag_order, data_variables, body }| { + .map(|expr::VariantPatternToExpression { loc, pattern, body }| { expr::VariantPatternToExpression { loc: *loc, - tag: *tag, - tag_order: *tag_order, - data_variables: data_variables - .iter() - .map(|dv| { - dv.as_ref().map(|(id, t)| (mod_def_id(id, definition_and_uses, new_name), *t)) - }) - .collect(), + pattern: apply_matching_pattern_renaming(pattern, definition_and_uses, new_name), body: Box::new(apply_expr_renaming(body, definition_and_uses, new_name)), } }) diff --git a/tests/SortableList.sam b/tests/SortableList.sam index e9fb30fc..6d6f8922 100644 --- a/tests/SortableList.sam +++ b/tests/SortableList.sam @@ -10,18 +10,17 @@ class BoxedInt(val i: int) : Comparable, Useless { method compare(other: BoxedInt): int = this.i - other.i } -class List>(Nil(unit), Cons(Pair>)) { - function > nil(): List = List.Nil({ }) +class List>(Nil, Cons(T, List)) { + function > nil(): List = List.Nil() - function > of(t: T): List = List.Cons(Pair.init(t, List.Nil({ }))) + function > of(t: T): List = List.Cons(t, List.Nil()) - method cons(t: T): List = List.Cons(Pair.init(t, this)) + method cons(t: T): List = List.Cons(t, this) method iter(f: (T) -> unit): unit = match this { - Nil(_) -> { } - Cons(pair) -> { - let (v, rest) = pair; + Nil -> { } + Cons(v, rest) -> { let _ = f(v); rest.iter(f) } @@ -29,36 +28,27 @@ class List>(Nil(unit), Cons(Pair>)) { method sort(): List = match this { - Nil(_) -> this, - Cons(pair) -> match pair.e1 { - Nil(_) -> this, - Cons(_) -> { - let (l1, l2) = this.split(List.nil(), List.nil()); - l1.sort().merge(l2.sort()) - } + Nil -> this, + Cons(_, Nil) -> this, + Cons(_, Cons(_, _)) -> { + let (l1, l2) = this.split(List.nil(), List.nil()); + l1.sort().merge(l2.sort()) } } private method merge(other: List): List = - match this { - Nil(_) -> other, - Cons(pair1) -> match other { - Nil(_) -> this, - Cons(pair2) -> { - let (h1, t1) = pair1; - let (h2, t2) = pair2; - if h1.compare(h2) < 0 then t1.merge(other).cons(h1) else t2.merge(this).cons(h2) - } + match (this, other) { + (Nil, _) -> other, + (_, Nil) -> this, + (Cons(h1, t1), Cons(h2, t2)) -> { + if h1.compare(h2) < 0 then t1.merge(other).cons(h1) else t2.merge(this).cons(h2) } } private method split(y: List, z: List): Pair, List> = match this { - Nil(_) -> Pair.init(y, z), - Cons(pair) -> { - let (x, rest) = pair; - rest.split(z, y.cons(x)) - } + Nil -> Pair.init(y, z), + Cons(x, rest) -> { rest.split(z, y.cons(x)) } } }