From 69df01e7af23f51779502947ea13333fa0b8a7ad Mon Sep 17 00:00:00 2001 From: Sam Zhou Date: Sun, 29 Oct 2023 09:06:53 -0700 Subject: [PATCH] [checker] Ban irrefutable patterns in `if let` --- .../samlang-core/src/checker/checker_tests.rs | 74 +++++- .../samlang-core/src/checker/main_checker.rs | 231 ++++++++++++++---- crates/samlang-core/src/checker/type_.rs | 1 + .../src/checker/typing_context.rs | 116 +++++++-- crates/samlang-core/src/errors.rs | 34 ++- .../src/services/global_searcher.rs | 38 ++- .../src/services/location_cover.rs | 4 +- 7 files changed, 404 insertions(+), 94 deletions(-) diff --git a/crates/samlang-core/src/checker/checker_tests.rs b/crates/samlang-core/src/checker/checker_tests.rs index 50cdab72a..d3014d840 100644 --- a/crates/samlang-core/src/checker/checker_tests.rs +++ b/crates/samlang-core/src/checker/checker_tests.rs @@ -2588,18 +2588,29 @@ Found 1 error. ); assert_checks( heap, - "{ let _ = (t: Test) -> if let {foo, bar as _, fff as _} = t then 1 else 2; }", + "{ let _ = (t: Test2) -> match (t) { Foo(_) -> 1, Bar(s) -> 2 }; }", &builder.unit_type(), ); assert_checks( heap, - "{ let _ = (t: Test2) -> match (t) { Foo(_) -> 1, Bar(s) -> 2 }; }", + "{ let _ = (t: Test2) -> if let Foo(_) = t then 1 else 2; }", &builder.unit_type(), ); - assert_checks( + assert_errors( heap, - "{ let _ = (t: Test2) -> if let Foo(_) = t then 1 else 2; }", + "{ let _ = (t: Test) -> if let {foo, bar as _, fff as _} = t then 1 else 2; }", &builder.unit_type(), + r#" +Error ---------------------------------- DUMMY.sam:1:31-1:56 + +The pattern is irrefutable. + + 1| { let _ = (t: Test) -> if let {foo, bar as _, fff as _} = t then 1 else 2; } + ^^^^^^^^^^^^^^^^^^^^^^^^^ + + +Found 1 error. +"#, ); assert_errors( heap, @@ -2747,6 +2758,14 @@ Found 1 error. "{ let _ = (t: Test) -> if let [a, b, _] = [1, 2] then 1 else 2; }", &builder.unit_type(), r#" +Error ---------------------------------- DUMMY.sam:1:31-1:40 + +The pattern is irrefutable. + + 1| { let _ = (t: Test) -> if let [a, b, _] = [1, 2] then 1 else 2; } + ^^^^^^^^^ + + Error ---------------------------------- DUMMY.sam:1:38-1:39 Cannot access member of `Pair` at index 2. @@ -2755,7 +2774,7 @@ Cannot access member of `Pair` at index 2. ^ -Found 1 error. +Found 2 errors. "#, "Test", true, @@ -2767,6 +2786,9 @@ let _ = (t: Test) -> if let [_, bar] = t then 1 else 2; let _ = (t: Test2) -> if let Foo(_) = t then 1 else 2; let _ = (t: Test2) -> if let Foo(_, _) = t then 1 else 2; let _ = (t: Test2) -> if let Foo111(_) = t then 1 else 2; +let _ = (t: Test2) -> if let Foo111(_, a, {bar as baz, b}, [eee, fff]) = t then 1 else 2; +let _ = (t: Test2) -> if let {s} = t then 1 else 2; +let _ = if let F = 1 then 1 else 2; }"#, &builder.unit_type(), r#" @@ -2780,6 +2802,14 @@ The pattern does not bind all fields. The following names have not been mentione ^^^^^^^^^^ +Error ---------------------------------- DUMMY.sam:1:31-1:41 + +The pattern is irrefutable. + + 1| { let _ = (t: Test) -> if let {bar, boo} = t then 1 else 2; + ^^^^^^^^^^ + + Error ---------------------------------- DUMMY.sam:1:32-1:35 Cannot resolve member `bar` on `Test`. @@ -2804,6 +2834,14 @@ The pattern does not bind all fields. Expected number of elements: 3, actual num ^^^^^^^^ +Error ---------------------------------- DUMMY.sam:2:29-2:37 + +The pattern is irrefutable. + + 2| let _ = (t: Test) -> if let [_, bar] = t then 1 else 2; + ^^^^^^^^ + + Error ---------------------------------- DUMMY.sam:2:33-2:36 Cannot access member of `Test` at index 1. @@ -2828,7 +2866,31 @@ Cannot resolve member `Foo111` on `Test2`. ^^^^^^ -Found 7 errors. +Error ---------------------------------- DUMMY.sam:6:30-6:36 + +Cannot resolve member `Foo111` on `Test2`. + + 6| let _ = (t: Test2) -> if let Foo111(_, a, {bar as baz, b}, [eee, fff]) = t then 1 else 2; + ^^^^^^ + + +Error ---------------------------------- DUMMY.sam:7:30-7:33 + +`Test2` is not an instance of a struct class. + + 7| let _ = (t: Test2) -> if let {s} = t then 1 else 2; + ^^^ + + +Error ---------------------------------- DUMMY.sam:8:16-8:17 + +`int` is not an instance of an enum class. + + 8| let _ = if let F = 1 then 1 else 2; + ^ + + +Found 12 errors. "#, "Test2", false, diff --git a/crates/samlang-core/src/checker/main_checker.rs b/crates/samlang-core/src/checker/main_checker.rs index ad248bfb1..ea5aa2ef8 100644 --- a/crates/samlang-core/src/checker/main_checker.rs +++ b/crates/samlang-core/src/checker/main_checker.rs @@ -1,5 +1,6 @@ use super::{ global_signature, + pattern_matching::{self, AbstractPatternNode}, ssa_analysis::perform_ssa_analysis_on_module, type_::{ FunctionType, GlobalSignature, ISourceType, MemberSignature, NominalType, PrimitiveTypeKind, @@ -901,13 +902,20 @@ fn check_if_else( hint: type_hint::Hint, ) -> expr::E> { let condition = Box::new(match expression.condition.as_ref() { - expr::IfElseCondition::Expression(e) => { - expr::IfElseCondition::Expression(type_check_expression(cx, e, type_hint::MISSING)) + expr::IfElseCondition::Expression(expr) => { + expr::IfElseCondition::Expression(type_check_expression(cx, expr, type_hint::MISSING)) } - expr::IfElseCondition::Guard(p, e) => { - let e = type_check_expression(cx, e, type_hint::MISSING); - let p = check_matching_pattern(cx, p, e.type_()); - expr::IfElseCondition::Guard(p, e) + expr::IfElseCondition::Guard(p, expr) => { + let expr = type_check_expression(cx, expr, type_hint::MISSING); + let (pattern, abstract_pattern_node) = check_matching_pattern(cx, p, expr.type_()); + if !pattern_matching::is_additional_pattern_useful( + cx, + &[abstract_pattern_node], + AbstractPatternNode::wildcard(), + ) { + cx.error_set.report_useless_pattern_error(*p.loc(), true); + } + expr::IfElseCondition::Guard(pattern, expr) } }); let e1 = Box::new(type_check_expression(cx, &expression.e1, hint)); @@ -1177,15 +1185,90 @@ fn check_destructuring_pattern( } } +fn any_typed_invalid_matching_pattern( + cx: &mut TypingContext, + pattern: &pattern::MatchingPattern<()>, +) -> pattern::MatchingPattern> { + match pattern { + pattern::MatchingPattern::Tuple(pattern_loc, destructured_names) => { + let mut checked_destructured_names = vec![]; + for pattern::TuplePatternElement { pattern, type_: _ } in destructured_names { + let loc = pattern.loc(); + checked_destructured_names.push(pattern::TuplePatternElement { + pattern: Box::new(any_typed_invalid_matching_pattern(cx, pattern)), + type_: Rc::new(Type::Any(Reason::new(*loc, Some(*loc)), false)), + }); + } + pattern::MatchingPattern::Tuple(*pattern_loc, checked_destructured_names) + } + pattern::MatchingPattern::Object(pattern_loc, destructed_names) => { + let mut checked_destructured_names = vec![]; + for pattern::ObjectPatternElement { + loc, + field_order, + field_name, + pattern, + shorthand, + type_: _, + } in destructed_names + { + checked_destructured_names.push(pattern::ObjectPatternElement { + loc: *loc, + field_order: *field_order, + field_name: *field_name, + pattern: Box::new(any_typed_invalid_matching_pattern(cx, pattern)), + shorthand: *shorthand, + type_: Rc::new(Type::Any(Reason::new(*loc, Some(*loc)), false)), + }); + } + pattern::MatchingPattern::Object(*pattern_loc, checked_destructured_names) + } + pattern::MatchingPattern::Variant(pattern::VariantPattern { + loc, + tag_order, + tag, + data_variables, + type_: _, + }) => pattern::MatchingPattern::Variant(pattern::VariantPattern { + loc: *loc, + tag_order: *tag_order, + tag: *tag, + data_variables: data_variables + .iter() + .map(|(p, ())| { + ( + any_typed_invalid_matching_pattern(cx, p), + Rc::new(Type::Any(Reason::new(*p.loc(), Some(*p.loc())), false)), + ) + }) + .collect(), + type_: Rc::new(Type::Any(Reason::new(*loc, Some(*loc)), false)), + }), + pattern::MatchingPattern::Id(id, ()) => { + let type_ = Rc::new(Type::Any(Reason::new(id.loc, Some(id.loc)), false)); + cx.local_typing_context.write(id.loc, type_.clone()); + pattern::MatchingPattern::Id(*id, type_) + } + pattern::MatchingPattern::Wildcard(loc) => pattern::MatchingPattern::Wildcard(*loc), + } +} + fn check_matching_pattern( cx: &mut TypingContext, pattern: &pattern::MatchingPattern<()>, pattern_type: &Rc, -) -> pattern::MatchingPattern> { +) -> (pattern::MatchingPattern>, pattern_matching::AbstractPatternNode) { match pattern { pattern::MatchingPattern::Tuple(pattern_loc, destructured_names) => { - let fields = cx.resolve_struct_definitions(pattern_type); + let Some((_, _, fields)) = cx.resolve_detailed_struct_definitions_opt(pattern_type) else { + cx.error_set.report_not_a_struct_error(*pattern_loc, pattern_type.to_description()); + return ( + any_typed_invalid_matching_pattern(cx, pattern), + pattern_matching::AbstractPatternNode::nothing(), + ); + }; let mut checked_destructured_names = vec![]; + let mut abstract_pattern_nodes = vec![]; for (index, pattern::TuplePatternElement { pattern, type_: _ }) in destructured_names.iter().enumerate() { @@ -1194,17 +1277,20 @@ fn check_matching_pattern( if !field_sig.is_public { cx.error_set.report_element_missing_error(*loc, pattern_type.to_description(), index); } - let checked = Box::new(check_matching_pattern(cx, pattern, &field_sig.type_)); + let (checked, abstract_node) = check_matching_pattern(cx, pattern, &field_sig.type_); checked_destructured_names.push(pattern::TuplePatternElement { - pattern: checked, + pattern: Box::new(checked), type_: Rc::new(field_sig.type_.reposition(*loc)), }); + abstract_pattern_nodes.push(abstract_node); continue; } cx.error_set.report_element_missing_error(*loc, pattern_type.to_description(), index); let type_ = Rc::new(Type::Any(Reason::new(*loc, Some(*loc)), false)); - let checked = Box::new(check_matching_pattern(cx, pattern, &type_)); - checked_destructured_names.push(pattern::TuplePatternElement { pattern: checked, type_ }); + let (checked, abstract_node) = check_matching_pattern(cx, pattern, &type_); + checked_destructured_names + .push(pattern::TuplePatternElement { pattern: Box::new(checked), type_ }); + abstract_pattern_nodes.push(abstract_node); } if fields.len() > checked_destructured_names.len() { cx.error_set.report_non_exhausive_tuple_binding_error( @@ -1212,18 +1298,32 @@ fn check_matching_pattern( fields.len(), checked_destructured_names.len(), ); + for _ in checked_destructured_names.len()..fields.len() { + abstract_pattern_nodes.push(pattern_matching::AbstractPatternNode::wildcard()); + } } - pattern::MatchingPattern::Tuple(*pattern_loc, checked_destructured_names) + ( + pattern::MatchingPattern::Tuple(*pattern_loc, checked_destructured_names), + pattern_matching::AbstractPatternNode::tuple(abstract_pattern_nodes), + ) } pattern::MatchingPattern::Object(pattern_loc, destructed_names) => { - let fields = cx.resolve_struct_definitions(pattern_type); + let Some((_, _, fields)) = cx.resolve_detailed_struct_definitions_opt(pattern_type) else { + cx.error_set.report_not_a_struct_error(*pattern_loc, pattern_type.to_description()); + return ( + any_typed_invalid_matching_pattern(cx, pattern), + pattern_matching::AbstractPatternNode::nothing(), + ); + }; let mut not_mentioned_fields = BTreeSet::new(); let mut field_order_mapping = HashMap::new(); let mut field_mappings = HashMap::new(); + let mut abstract_pattern_nodes = Vec::with_capacity(fields.len()); for (i, field) in fields.into_iter().enumerate() { field_order_mapping.insert(field.name, i); not_mentioned_fields.insert(field.name); field_mappings.insert(field.name, (field.type_, field.is_public)); + abstract_pattern_nodes.push(AbstractPatternNode::wildcard()); } let mut checked_destructured_names = vec![]; for pattern::ObjectPatternElement { @@ -1244,16 +1344,17 @@ fn check_matching_pattern( ); } not_mentioned_fields.remove(&field_name.name); - let checked = Box::new(check_matching_pattern(cx, pattern, field_type)); + let (checked, abstract_node) = check_matching_pattern(cx, pattern, field_type); let field_order = field_order_mapping.get(&field_name.name).unwrap(); checked_destructured_names.push(pattern::ObjectPatternElement { loc: *loc, field_order: *field_order, field_name: *field_name, - pattern: checked, + pattern: Box::new(checked), shorthand: *shorthand, type_: Rc::new(field_type.reposition(*loc)), }); + abstract_pattern_nodes[*field_order] = abstract_node; continue; } cx.error_set.report_cannot_resolve_member_error( @@ -1262,15 +1363,16 @@ fn check_matching_pattern( field_name.name, ); let type_ = Rc::new(Type::Any(Reason::new(*loc, Some(*loc)), false)); - let checked = Box::new(check_matching_pattern(cx, pattern, &type_)); + let (checked, abstract_node) = check_matching_pattern(cx, pattern, &type_); checked_destructured_names.push(pattern::ObjectPatternElement { loc: *loc, field_order: *field_order, field_name: *field_name, - pattern: checked, + pattern: Box::new(checked), shorthand: *shorthand, type_: Rc::new(Type::Any(Reason::new(*loc, Some(*loc)), false)), }); + abstract_pattern_nodes[*field_order] = abstract_node; } if !not_mentioned_fields.is_empty() { cx.error_set.report_non_exhausive_struct_binding_error( @@ -1278,68 +1380,97 @@ fn check_matching_pattern( not_mentioned_fields.into_iter().collect(), ); } - pattern::MatchingPattern::Object(*pattern_loc, checked_destructured_names) + ( + pattern::MatchingPattern::Object(*pattern_loc, checked_destructured_names), + pattern_matching::AbstractPatternNode::tuple(abstract_pattern_nodes), + ) } pattern::MatchingPattern::Variant(pattern::VariantPattern { loc, - tag_order, + tag_order: _, tag, data_variables, type_: _, }) => { - let Some((tag_order, resolved_enum)) = - cx.resolve_enum_definitions(pattern_type).into_iter().find_position(|e| e.name == tag.name) + let Some(( + abstract_variant_constructor_mod_ref, + abstract_variant_constructor_class_name, + resolved_enum, + )) = cx.resolve_detailed_enum_definitions_opt(pattern_type) + else { + cx.error_set.report_not_an_enum_error(tag.loc, pattern_type.to_description()); + return ( + any_typed_invalid_matching_pattern(cx, pattern), + pattern_matching::AbstractPatternNode::nothing(), + ); + }; + let Some((tag_order, resolved_enum_variant)) = + resolved_enum.into_iter().find_position(|e| e.name == tag.name) else { cx.error_set.report_cannot_resolve_member_error( tag.loc, pattern_type.to_description(), tag.name, ); - let type_ = Rc::new(Type::Any(Reason::new(*loc, Some(*loc)), false)); - return pattern::MatchingPattern::Variant(pattern::VariantPattern { - loc: *loc, - tag_order: *tag_order, - tag: *tag, - data_variables: data_variables - .iter() - .map(|(p, ())| (check_matching_pattern(cx, p, &type_), type_.clone())) - .collect(), - type_, - }); + return ( + any_typed_invalid_matching_pattern(cx, pattern), + pattern_matching::AbstractPatternNode::nothing(), + ); }; let mut checked_data_variables = Vec::with_capacity(data_variables.len()); + let abstract_variant_constructor = pattern_matching::VariantPatternConstructor { + module_reference: abstract_variant_constructor_mod_ref, + class_name: abstract_variant_constructor_class_name, + variant_name: resolved_enum_variant.name, + }; + let mut abstract_pattern_nodes = vec![]; for (index, (p, ())) in data_variables.iter().enumerate() { - if let Some(resolved_pattern_type) = resolved_enum.types.get(index) { - checked_data_variables.push(( - check_matching_pattern(cx, p, resolved_pattern_type), - resolved_pattern_type.clone(), - )); + if let Some(resolved_pattern_type) = resolved_enum_variant.types.get(index) { + let (checked, abstract_node) = check_matching_pattern(cx, p, resolved_pattern_type); + checked_data_variables.push((checked, resolved_pattern_type.clone())); + abstract_pattern_nodes.push(abstract_node); } else { cx.error_set.report_element_missing_error(*p.loc(), pattern_type.to_description(), index); let type_ = Rc::new(Type::Any(Reason::new(*p.loc(), Some(*p.loc())), false)); - checked_data_variables.push((check_matching_pattern(cx, p, &type_), type_)); + let (checked, abstract_node) = check_matching_pattern(cx, p, &type_); + checked_data_variables.push((checked, type_)); + abstract_pattern_nodes.push(abstract_node); } } - if resolved_enum.types.len() > checked_data_variables.len() { + if resolved_enum_variant.types.len() > checked_data_variables.len() { cx.error_set.report_non_exhausive_tuple_binding_error( *loc, - resolved_enum.types.len(), + resolved_enum_variant.types.len(), checked_data_variables.len(), ); + for _ in checked_data_variables.len()..resolved_enum_variant.types.len() { + abstract_pattern_nodes.push(pattern_matching::AbstractPatternNode::wildcard()); + } } - pattern::MatchingPattern::Variant(pattern::VariantPattern { - loc: *loc, - tag_order, - tag: *tag, - data_variables: checked_data_variables, - type_: pattern_type.clone(), - }) + ( + pattern::MatchingPattern::Variant(pattern::VariantPattern { + loc: *loc, + tag_order, + tag: *tag, + data_variables: checked_data_variables, + type_: pattern_type.clone(), + }), + pattern_matching::AbstractPatternNode::variant( + abstract_variant_constructor, + abstract_pattern_nodes, + ), + ) } pattern::MatchingPattern::Id(id, ()) => { cx.local_typing_context.write(id.loc, pattern_type.clone()); - pattern::MatchingPattern::Id(*id, pattern_type.clone()) + ( + pattern::MatchingPattern::Id(*id, pattern_type.clone()), + pattern_matching::AbstractPatternNode::wildcard(), + ) + } + pattern::MatchingPattern::Wildcard(loc) => { + (pattern::MatchingPattern::Wildcard(*loc), pattern_matching::AbstractPatternNode::wildcard()) } - pattern::MatchingPattern::Wildcard(loc) => pattern::MatchingPattern::Wildcard(*loc), } } diff --git a/crates/samlang-core/src/checker/type_.rs b/crates/samlang-core/src/checker/type_.rs index 6d1f37f4b..00bf274f7 100644 --- a/crates/samlang-core/src/checker/type_.rs +++ b/crates/samlang-core/src/checker/type_.rs @@ -526,6 +526,7 @@ pub(crate) struct EnumVariantDefinitionSignature { pub(crate) types: Vec>, } +#[derive(EnumAsInner)] pub(crate) enum TypeDefinitionSignature { Struct(Vec), Enum(Vec), diff --git a/crates/samlang-core/src/checker/typing_context.rs b/crates/samlang-core/src/checker/typing_context.rs index 3c9b67cc4..0bb87d4c4 100644 --- a/crates/samlang-core/src/checker/typing_context.rs +++ b/crates/samlang-core/src/checker/typing_context.rs @@ -11,7 +11,10 @@ use crate::{ errors::{ErrorSet, StackableError}, }; use samlang_heap::{ModuleReference, PStr}; -use std::{collections::HashMap, rc::Rc}; +use std::{ + collections::{HashMap, HashSet}, + rc::Rc, +}; pub(crate) struct LocalTypingContext { type_map: HashMap>, @@ -277,13 +280,36 @@ impl<'a> TypingContext<'a> { self.current_module_reference == module_reference && class_name == self.current_class } + pub(super) fn resolve_detailed_struct_definitions_opt( + &self, + type_: &Type, + ) -> Option<(ModuleReference, PStr, Vec)> { + match self.resolve_type_definition(type_) { + None | Some((_, _, TypeDefinitionSignature::Enum(_))) => None, + Some((mod_ref, t_id, TypeDefinitionSignature::Struct(items))) => Some((mod_ref, t_id, items)), + } + } + + pub(super) fn resolve_detailed_enum_definitions_opt( + &self, + type_: &Type, + ) -> Option<(ModuleReference, PStr, Vec)> { + match self.resolve_type_definition(type_) { + None | Some((_, _, TypeDefinitionSignature::Struct(_))) => None, + Some((mod_ref, t_id, TypeDefinitionSignature::Enum(variants))) => { + Some((mod_ref, t_id, variants)) + } + } + } + pub(super) fn resolve_struct_definitions( &self, type_: &Type, ) -> Vec { - match self.resolve_type_definition(type_) { - None | Some(TypeDefinitionSignature::Enum(_)) => vec![], - Some(TypeDefinitionSignature::Struct(items)) => items, + if let Some((_, _, result)) = self.resolve_detailed_struct_definitions_opt(type_) { + result + } else { + Vec::with_capacity(0) } } @@ -291,13 +317,17 @@ impl<'a> TypingContext<'a> { &self, type_: &Type, ) -> Vec { - match self.resolve_type_definition(type_) { - None | Some(TypeDefinitionSignature::Struct(_)) => vec![], - Some(TypeDefinitionSignature::Enum(variants)) => variants, + if let Some((_, _, result)) = self.resolve_detailed_enum_definitions_opt(type_) { + result + } else { + Vec::with_capacity(0) } } - fn resolve_type_definition(&self, type_: &Type) -> Option { + fn resolve_type_definition( + &self, + type_: &Type, + ) -> Option<(ModuleReference, PStr, TypeDefinitionSignature)> { let nominal_type = self.nominal_type_upper_bound(type_)?; let resolved_type_def = global_signature::resolve_interface_cx( self.global_signature, @@ -318,26 +348,58 @@ impl<'a> TypingContext<'a> { { subst_map.insert(tparam.name, targ.clone()); } + let mod_ref = nominal_type.module_reference; + let t_id = nominal_type.id; match resolved_type_def { - TypeDefinitionSignature::Struct(items) => Some(TypeDefinitionSignature::Struct( - items - .iter() - .map(|item| StructItemDefinitionSignature { - name: item.name, - type_: type_system::subst_type(&item.type_, &subst_map), - is_public: item.is_public || nominal_type.id.eq(&self.current_class), - }) - .collect(), - )), - TypeDefinitionSignature::Enum(variants) => Some(TypeDefinitionSignature::Enum( - variants - .iter() - .map(|variant| EnumVariantDefinitionSignature { - name: variant.name, - types: variant.types.iter().map(|it| type_system::subst_type(it, &subst_map)).collect(), - }) - .collect(), - )), + TypeDefinitionSignature::Struct(items) => { + let def = TypeDefinitionSignature::Struct( + items + .iter() + .map(|item| StructItemDefinitionSignature { + name: item.name, + type_: type_system::subst_type(&item.type_, &subst_map), + is_public: item.is_public || nominal_type.id.eq(&self.current_class), + }) + .collect(), + ); + Some((mod_ref, t_id, def)) + } + TypeDefinitionSignature::Enum(variants) => { + let def = TypeDefinitionSignature::Enum( + variants + .iter() + .map(|variant| EnumVariantDefinitionSignature { + name: variant.name, + types: variant + .types + .iter() + .map(|it| type_system::subst_type(it, &subst_map)) + .collect(), + }) + .collect(), + ); + Some((mod_ref, t_id, def)) + } } } } + +impl<'a> super::pattern_matching::PatternMatchingContext for TypingContext<'a> { + fn is_variant_signature_complete( + &self, + module_reference: ModuleReference, + class_name: PStr, + variant_name: &[PStr], + ) -> bool { + let set = variant_name.iter().copied().collect::>(); + global_signature::resolve_interface_cx(self.global_signature, module_reference, class_name) + .expect("Should not be called with invalid enum.") + .type_definition + .as_ref() + .expect("Should not be called with invalid enum.") + .as_enum() + .expect("Should not be called with invalid enum.") + .iter() + .all(|v| set.contains(&v.name)) + } +} diff --git a/crates/samlang-core/src/errors.rs b/crates/samlang-core/src/errors.rs index dae4bbb33..61bdfbbdb 100644 --- a/crates/samlang-core/src/errors.rs +++ b/crates/samlang-core/src/errors.rs @@ -542,6 +542,8 @@ pub(crate) enum ErrorDetail { NonExhausiveStructBinding { missing_bindings: Vec }, NonExhausiveTupleBinding { expected_count: usize, actual_count: usize }, NonExhausiveMatch { missing_tags: Vec }, + NotAnEnum { description: Description }, + NotAStruct { description: Description }, Stacked(StackableError), TypeParameterNameMismatch { expected: Vec }, Underconstrained, @@ -656,6 +658,16 @@ impl ErrorDetail { printable_stream.push_text("`"); } } + ErrorDetail::NotAnEnum { description } => { + printable_stream.push_text("`"); + printable_stream.push_description(description); + printable_stream.push_text("` is not an instance of an enum class."); + } + ErrorDetail::NotAStruct { description } => { + printable_stream.push_text("`"); + printable_stream.push_description(description); + printable_stream.push_text("` is not an instance of a struct class."); + } ErrorDetail::Stacked(s) => { for (i, e) in s.rev_stack.iter().rev().enumerate() { if i >= 1 { @@ -999,6 +1011,14 @@ impl ErrorSet { self.report_error(loc, ErrorDetail::NonExhausiveMatch { missing_tags }) } + pub(crate) fn report_not_an_enum_error(&mut self, loc: Location, description: Description) { + self.report_error(loc, ErrorDetail::NotAnEnum { description }) + } + + pub(crate) fn report_not_a_struct_error(&mut self, loc: Location, description: Description) { + self.report_error(loc, ErrorDetail::NotAStruct { description }) + } + pub(crate) fn report_stackable_error(&mut self, loc: Location, stackable: StackableError) { self.report_error(loc, ErrorDetail::Stacked(stackable)) } @@ -1175,6 +1195,8 @@ Found 2 errors."# error_set.report_non_exhausive_tuple_binding_error(Location::dummy(), 7, 4); error_set .report_non_exhausive_match_error(Location::dummy(), vec![PStr::UPPER_A, PStr::UPPER_B]); + error_set.report_not_an_enum_error(Location::dummy(), Description::IntType); + error_set.report_not_a_struct_error(Location::dummy(), Description::IntType); error_set.report_stackable_error(Location::dummy(), { let mut stacked = StackableError::new(); stacked.add_type_error(super::TypeIncompatibilityNode { @@ -1276,6 +1298,16 @@ The match is not exhausive. The following variants have not been handled: - `B` +Error ------------------------------------ DUMMY.sam:0:0-0:0 + +`int` is not an instance of an enum class. + + +Error ------------------------------------ DUMMY.sam:0:0-0:0 + +`int` is not an instance of a struct class. + + Error ------------------------------------ DUMMY.sam:0:0-0:0 `any` is incompatible with `any`. @@ -1315,7 +1347,7 @@ Very/Very/Very/Very/Very/Very/Very/Very/Very/Very/Very/Very/Very/Very/Very/Long. Cannot resolve name `global`. -Found 21 errors. +Found 23 errors. "#; assert_eq!( expected_errors.trim(), diff --git a/crates/samlang-core/src/services/global_searcher.rs b/crates/samlang-core/src/services/global_searcher.rs index 0e7ce3037..c5bd97364 100644 --- a/crates/samlang-core/src/services/global_searcher.rs +++ b/crates/samlang-core/src/services/global_searcher.rs @@ -304,15 +304,16 @@ mod tests { builtin_parsed_std_sources, checker::type_check_sources, errors::ErrorSet, parser::parse_source_module_from_text, }; + use pretty_assertions::assert_eq; use samlang_heap::{Heap, PStr}; + use std::collections::HashMap; #[test] fn searcher_coverage_test() { let heap = &mut Heap::new(); let mut error_set = ErrorSet::new(); let mod_ref = heap.alloc_module_reference_from_string_vec(vec!["foo".to_string()]); - let parsed = parse_source_module_from_text( - r#"class Foo(val a: int, val b: bool) { + let source = r#"class Foo(val a: int, val b: bool) { function bar(): int = 3 method foo(): unit = { @@ -390,15 +391,36 @@ mod tests { ))); Main.main() } - }"#, - mod_ref, - heap, - &mut error_set, - ); + }"#; + let parsed = parse_source_module_from_text(source, mod_ref, heap, &mut error_set); let mut modules = builtin_parsed_std_sources(heap); modules.insert(mod_ref, parsed); let (checked_sources, _) = type_check_sources(&modules, &mut error_set); - assert_eq!("", error_set.pretty_print_error_messages_no_frame(heap)); + assert_eq!( + r#" +Error ---------------------------------- foo.sam:33:24-33:44 + +The pattern is irrefutable. + + 33| let _ = if let { a as d3, b as d4 } = Foo.init(5, false) then {} else {}; + ^^^^^^^^^^^^^^^^^^^^ + + +Error ---------------------------------- foo.sam:35:24-35:30 + +The pattern is irrefutable. + + 35| let _ = if let [_, _] = [1,2] then {} else {}; + ^^^^^^ + + +Found 2 errors. +"# + .trim(), + error_set + .pretty_print_error_messages(heap, &HashMap::from([(mod_ref, source.to_string())])) + .trim() + ); super::search_modules_globally( &checked_sources, &super::GlobalNameSearchRequest::Toplevel(mod_ref, heap.alloc_str_for_test("Foo")), diff --git a/crates/samlang-core/src/services/location_cover.rs b/crates/samlang-core/src/services/location_cover.rs index 36a8c7bbc..85e019bdd 100644 --- a/crates/samlang-core/src/services/location_cover.rs +++ b/crates/samlang-core/src/services/location_cover.rs @@ -336,9 +336,9 @@ mod tests { let { d as _, e as d } = Obj.init(5, 4); // d = 4 let f = Obj.init(5, 4); // d = 4 let g = Obj.init(d, 4); // d = 4 - let _ = if let { a as d3 } = Foo.init(5) then {} else {}; + let _ = if let Some({ a as d3 }) = Option.Some(Foo.init(5)) then {} else {}; let _ = if let Some(_) = Option.Some(1) then {} else {}; - let _ = if let [_, _] = [1,2] then {} else {}; + let _ = if let Some([_, _]) = Option.Some([1,2]) then {} else {}; let _ = f.d; let [h, i] = [111, 122]; // 1 + 2 * 3 / 4 = 1 + 6/4 = 1 + 1 = 2