From f48b56d5b2fa41c5e3881f13b0ef374890416d2d Mon Sep 17 00:00:00 2001 From: George Zahariev Date: Tue, 17 Dec 2024 15:02:00 -0800 Subject: [PATCH] [flow][match] Simple check for match expression exhaustiveness based on refinements Summary: Adds a simple check for match expression exhaustiveness based on the argument being refined to `empty`. In the future, once we have a more advanced analysis, this could serve as an optimization (if the refinements systems has already refined the argument to `empty` by the end of the `match`, we don't need to run the advanced analysis). Changelog: [internal] Reviewed By: SamChou19815 Differential Revision: D67261340 fbshipit-source-id: e22f918dc958f140dd36a104b6652f244d92b225 --- .../__tests__/env_builder_refinement_test.ml | 4 +- src/analysis/env_builder/find_providers.ml | 10 +- src/analysis/env_builder/name_def.ml | 3 +- src/analysis/env_builder/name_def_ordering.ml | 5 +- src/analysis/env_builder/name_resolver.ml | 10 +- src/common/errors/error_codes.ml | 2 + src/parser/estree_translator.ml | 2 +- src/parser/expression_parser.ml | 9 +- src/parser/flow_ast.ml | 3 + src/parser/flow_ast_mapper.ml | 4 +- .../flow_polymorphic_ast_mapper.ml | 11 +- .../output/js_layout_generator.ml | 2 +- src/typing/debug_js.ml | 2 + src/typing/errors/error_message.ml | 14 +- src/typing/errors/flow_intermediate_error.ml | 7 + .../errors/flow_intermediate_error_types.ml | 1 + src/typing/merge_js.ml | 30 ++ src/typing/statement.ml | 16 +- tests/match/match.exp | 346 ++++++++---------- tests/match/matching.js | 48 +-- tests/match/patterns.js | 1 + 21 files changed, 287 insertions(+), 243 deletions(-) diff --git a/src/analysis/env_builder/__tests__/env_builder_refinement_test.ml b/src/analysis/env_builder/__tests__/env_builder_refinement_test.ml index 5d3a318f76c..a9338cae642 100644 --- a/src/analysis/env_builder/__tests__/env_builder_refinement_test.ml +++ b/src/analysis/env_builder/__tests__/env_builder_refinement_test.ml @@ -7492,7 +7492,7 @@ let%expect_test "match_object_pattern" = [%expect {| [ (2, 1) to (2, 6) => { - (2, 1) to (2, 6): (``) + {refinement = Or (Not (And (object, Not (Null))), Not (SentinelR type)); writes = {refinement = Or (Or (Not (And (object, Not (Null))), Not (SentinelR type)), Not (PropExistsR (value))); writes = (2, 1) to (2, 6): (``)}} }; (2, 8) to (2, 9) => { Global x @@ -7519,7 +7519,7 @@ let%expect_test "match_array_pattern" = [%expect {| [ (2, 1) to (2, 6) => { - (2, 1) to (2, 6): (``) + {refinement = Or (Not (And (isArray, array length === 1)), Not (SentinelR 0)); writes = {refinement = Or (Not (And (isArray, array length === 2)), Not (SentinelR 0)); writes = (2, 1) to (2, 6): (``)}} }; (2, 8) to (2, 9) => { Global x diff --git a/src/analysis/env_builder/find_providers.ml b/src/analysis/env_builder/find_providers.ml index 8324450cc9f..fb25d096db8 100644 --- a/src/analysis/env_builder/find_providers.ml +++ b/src/analysis/env_builder/find_providers.ml @@ -987,7 +987,15 @@ end = struct expr method! match_expression loc x = - let { Ast.Expression.Match.arg_internal; arg = _; cases = _; comments = _ } = x in + let { + Ast.Expression.Match.arg_internal; + arg = _; + cases = _; + match_keyword_loc = _; + comments = _; + } = + x + in ignore @@ this#pattern_identifier ~kind:Ast.Variable.Const diff --git a/src/analysis/env_builder/name_def.ml b/src/analysis/env_builder/name_def.ml index 999a2c6ed42..e504f9d052f 100644 --- a/src/analysis/env_builder/name_def.ml +++ b/src/analysis/env_builder/name_def.ml @@ -3122,8 +3122,7 @@ class def_finder ~autocomplete_hooks ~react_jsx env_info toplevel_scope = method private visit_match_expression x = let open Ast.Expression.Match in - let { arg; cases; arg_internal; comments = _ } = x in - ignore @@ this#expression arg; + let { arg; cases; arg_internal; match_keyword_loc = _; comments = _ } = x in this#add_ordinary_binding arg_internal (mk_reason RMatchExpression arg_internal) diff --git a/src/analysis/env_builder/name_def_ordering.ml b/src/analysis/env_builder/name_def_ordering.ml index 83bf41c005e..7bb588e0469 100644 --- a/src/analysis/env_builder/name_def_ordering.ml +++ b/src/analysis/env_builder/name_def_ordering.ml @@ -276,7 +276,9 @@ struct super#yield loc yield method! match_expression _ x = - let { Ast.Expression.Match.arg; cases; arg_internal; comments = _ } = x in + let { Ast.Expression.Match.arg; cases; arg_internal; match_keyword_loc; comments = _ } = + x + in ignore @@ this#expression arg; ignore @@ this#pattern_identifier @@ -286,6 +288,7 @@ struct ignore @@ this#identifier (Flow_ast_utils.match_root_ident case_loc); ignore @@ super#match_expression_case (case_loc, case) ); + ignore @@ this#identifier (Flow_ast_utils.match_root_ident match_keyword_loc); x (* In order to resolve a def containing a variable write, the diff --git a/src/analysis/env_builder/name_resolver.ml b/src/analysis/env_builder/name_resolver.ml index 7119a7e86ae..8d643f03096 100644 --- a/src/analysis/env_builder/name_resolver.ml +++ b/src/analysis/env_builder/name_resolver.ml @@ -2913,7 +2913,7 @@ module Make (Context : C) (FlowAPIUtils : F with type cx = Context.t) : method! match_expression match_loc x = let open Flow_ast.Expression.Match in - let { arg; cases; arg_internal; comments = _ } = x in + let { arg; cases; arg_internal; match_keyword_loc; comments = _ } = x in let match_root_ident = Flow_ast_utils.match_root_ident in ignore @@ this#expression arg; let env0 = this#env_snapshot in @@ -2969,7 +2969,13 @@ module Make (Context : C) (FlowAPIUtils : F with type cx = Context.t) : this#negate_new_refinements () )) () - )) + ); + let arg_out = + ( match_keyword_loc, + Ast.Expression.Identifier (Flow_ast_utils.match_root_ident match_keyword_loc) + ) + in + ignore @@ this#expression arg_out) (); let completion_states = !completion_states |> List.rev in this#reset_env env0; diff --git a/src/common/errors/error_codes.ml b/src/common/errors/error_codes.ml index f55247ca782..03fbb0df067 100644 --- a/src/common/errors/error_codes.ml +++ b/src/common/errors/error_codes.ml @@ -107,6 +107,7 @@ type error_code = | InvalidTempType | LintSetting | MalformedPackage + | MatchNotExhaustive | MethodUnbinding | MissingLocalAnnot | MissingThisAnnot @@ -319,6 +320,7 @@ let string_of_code : error_code -> string = function | InvalidTempType -> "invalid-temp-type" | LintSetting -> "lint-setting" | MalformedPackage -> "malformed-package" + | MatchNotExhaustive -> "match-not-exhaustive" | MethodUnbinding -> "method-unbinding" | MissingLocalAnnot -> "missing-local-annot" | MissingThisAnnot -> "missing-this-annot" diff --git a/src/parser/estree_translator.ml b/src/parser/estree_translator.ml index 3896e6af681..979cb27921b 100644 --- a/src/parser/estree_translator.ml +++ b/src/parser/estree_translator.ml @@ -640,7 +640,7 @@ with type t = Impl.t = struct | (loc, Class c) -> class_expression (loc, c) | (loc, JSXElement element) -> jsx_element (loc, element) | (loc, JSXFragment fragment) -> jsx_fragment (loc, fragment) - | (loc, Match { Match.arg; cases; comments; arg_internal = _ }) -> + | (loc, Match { Match.arg; cases; comments; arg_internal = _; match_keyword_loc = _ }) -> node ?comments "MatchExpression" diff --git a/src/parser/expression_parser.ml b/src/parser/expression_parser.ml index 1b34efb7711..429a9ab6376 100644 --- a/src/parser/expression_parser.ml +++ b/src/parser/expression_parser.ml @@ -1337,16 +1337,16 @@ module Expression let leading = Peek.comments env in let start_loc = Peek.loc env in (* Consume `match` as an identifier, in case it's a call expression. *) - let id = Parse.identifier env in + let ((id_loc, _) as id) = Parse.identifier env in (* Allows trailing comma. *) let args = arguments env in (* `match () {` *) if (not (Peek.is_line_terminator env)) && Peek.token env = T_LCURLY then let arg = Parser_common.reparse_arguments_as_match_argument env args in - Cover_expr (match_expression ~start_loc ~leading ~arg env) + Cover_expr (match_expression ~start_loc ~id_loc ~leading ~arg env) else (* It's actually a call expression of the form `match(...)` *) - let callee = (fst id, Expression.Identifier id) in + let callee = (id_loc, Expression.Identifier id) in let (args_loc, _) = args in let loc = Loc.btwn start_loc args_loc in let comments = Flow_ast_utils.mk_comments_opt ~leading () in @@ -1382,7 +1382,7 @@ module Expression and primary env = as_expression env (primary_cover env) - and match_expression env ~start_loc ~leading ~arg = + and match_expression env ~start_loc ~id_loc ~leading ~arg = let case env = let leading = Peek.comments env in let pattern = Parse.match_pattern env in @@ -1422,6 +1422,7 @@ module Expression Expression.Match.arg; cases; arg_internal = start_loc; + match_keyword_loc = id_loc; comments = Flow_ast_utils.mk_comments_opt ~leading ~trailing (); }) env diff --git a/src/parser/flow_ast.ml b/src/parser/flow_ast.ml index 1b6dfe958ae..ba080be13e8 100644 --- a/src/parser/flow_ast.ml +++ b/src/parser/flow_ast.ml @@ -1758,6 +1758,9 @@ and Expression : sig arg: ('M, 'T) Expression.t; cases: ('M, 'T) Case.t list; arg_internal: 'M; + (* The type here is used to store the resulting type after the patterns + refine the arg type. *) + match_keyword_loc: 'T; comments: ('M, unit) Syntax.t option; } [@@deriving show] diff --git a/src/parser/flow_ast_mapper.ml b/src/parser/flow_ast_mapper.ml index a6ea8481623..831ff2aaa9a 100644 --- a/src/parser/flow_ast_mapper.ml +++ b/src/parser/flow_ast_mapper.ml @@ -2521,14 +2521,14 @@ class ['loc] mapper = method match_expression _loc (expr : ('loc, 'loc) Ast.Expression.Match.t) = let open Ast.Expression.Match in - let { arg; cases; arg_internal; comments } = expr in + let { arg; cases; arg_internal; match_keyword_loc; comments } = expr in let arg' = this#expression arg in let cases' = map_list this#match_expression_case cases in let comments' = this#syntax_opt comments in if arg == arg' && cases == cases' && comments == comments' then expr else - { arg = arg'; cases = cases'; arg_internal; comments = comments' } + { arg = arg'; cases = cases'; arg_internal; match_keyword_loc; comments = comments' } method match_expression_case (case : ('loc, 'loc) Ast.Expression.Match.Case.t) = let open Ast.Expression.Match.Case in diff --git a/src/parser_utils/flow_polymorphic_ast_mapper.ml b/src/parser_utils/flow_polymorphic_ast_mapper.ml index 67adb6602dc..0c5c05afc5c 100644 --- a/src/parser_utils/flow_polymorphic_ast_mapper.ml +++ b/src/parser_utils/flow_polymorphic_ast_mapper.ml @@ -2062,12 +2062,19 @@ class virtual ['M, 'T, 'N, 'U] mapper = method match_expression (x : ('M, 'T) Ast.Expression.Match.t) : ('N, 'U) Ast.Expression.Match.t = let open Ast.Expression.Match in - let { arg; cases; arg_internal; comments } = x in + let { arg; cases; arg_internal; match_keyword_loc; comments } = x in let arg' = this#expression arg in let cases' = List.map ~f:(this#on_loc_annot * this#match_expression_case) cases in let arg_internal' = this#on_loc_annot arg_internal in + let match_keyword_loc' = this#on_type_annot match_keyword_loc in let comments' = this#syntax_opt comments in - { arg = arg'; cases = cases'; arg_internal = arg_internal'; comments = comments' } + { + arg = arg'; + cases = cases'; + arg_internal = arg_internal'; + match_keyword_loc = match_keyword_loc'; + comments = comments'; + } method match_expression_case (case : ('M, 'T) Ast.Expression.Match.Case.t') : ('N, 'U) Ast.Expression.Match.Case.t' = diff --git a/src/parser_utils/output/js_layout_generator.ml b/src/parser_utils/output/js_layout_generator.ml index 4c6eeb8585b..d334774ac95 100644 --- a/src/parser_utils/output/js_layout_generator.ml +++ b/src/parser_utils/output/js_layout_generator.ml @@ -1270,7 +1270,7 @@ and expression ?(ctxt = normal_context) ~opts (root_expr : (Loc.t, Loc.t) Ast.Ex | Some arg -> fuse [space; expression ~ctxt ~opts arg] | None -> Empty); ] - | E.Match { E.Match.arg; cases; comments; arg_internal = _ } -> + | E.Match { E.Match.arg; cases; comments; arg_internal = _; match_keyword_loc = _ } -> let cases = List.map (fun ((loc, _) as case) -> diff --git a/src/typing/debug_js.ml b/src/typing/debug_js.ml index 95bf0e0ab07..8468d74e179 100644 --- a/src/typing/debug_js.ml +++ b/src/typing/debug_js.ml @@ -1898,6 +1898,8 @@ let dump_error_message = spf "ECannotCallReactComponent (%s)" (dump_reason cx reason) | ENegativeTypeGuardConsistency { reason; _ } -> spf "ENegativeTypeGuardConsistency (%s)" (dump_reason cx reason) + | EMatchNotExhaustive { loc; reason } -> + spf "EMatchNotExhaustive (%s) (%s)" (string_of_aloc loc) (dump_reason cx reason) | EDevOnlyRefinedLocInfo { refined_loc; refining_locs = _ } -> spf "EDevOnlyRefinedLocInfo {refined_loc=%s}" (string_of_aloc refined_loc) | EDevOnlyInvalidatedRefinementInfo { read_loc; invalidation_info = _ } -> diff --git a/src/typing/errors/error_message.ml b/src/typing/errors/error_message.ml index d359cc1eef0..893ce0ec7ec 100644 --- a/src/typing/errors/error_message.ml +++ b/src/typing/errors/error_message.ml @@ -613,6 +613,12 @@ and 'loc t' = arg: 'loc virtual_reason; } | ECannotCallReactComponent of { reason: 'loc virtual_reason } + (* Match *) + | EMatchNotExhaustive of { + loc: 'loc; + reason: 'loc virtual_reason; + } + (* Dev only *) | EDevOnlyRefinedLocInfo of { refined_loc: 'loc; refining_locs: 'loc list; @@ -1413,6 +1419,8 @@ let rec map_loc_of_error_message (f : 'a -> 'b) : 'a t' -> 'b t' = | ECannotCallReactComponent { reason } -> ECannotCallReactComponent { reason = map_reason reason } | EDevOnlyRefinedLocInfo { refined_loc; refining_locs } -> EDevOnlyRefinedLocInfo { refined_loc = f refined_loc; refining_locs = List.map f refining_locs } + | EMatchNotExhaustive { loc; reason } -> + EMatchNotExhaustive { loc = f loc; reason = map_reason reason } | EDevOnlyInvalidatedRefinementInfo { read_loc; invalidation_info } -> EDevOnlyInvalidatedRefinementInfo { @@ -1709,7 +1717,8 @@ let util_use_op_of_msg nope util = function | EUnionPartialOptimizationNonUniqueKey _ | EUnionOptimization _ | EUnionOptimizationOnNonUnion _ - | ECannotCallReactComponent _ -> + | ECannotCallReactComponent _ + | EMatchNotExhaustive _ -> nope (* Not all messages (i.e. those whose locations are based on use_ops) have locations that can be @@ -1910,6 +1919,7 @@ let loc_of_msg : 'loc t' -> 'loc option = function | EDuplicateClassMember { loc; _ } -> Some loc | EEmptyArrayNoProvider { loc } -> Some loc | EUnusedPromise { loc; _ } -> Some loc + | EMatchNotExhaustive { loc; _ } -> Some loc | EDevOnlyRefinedLocInfo { refined_loc; refining_locs = _ } -> Some refined_loc | EDevOnlyInvalidatedRefinementInfo { read_loc; invalidation_info = _ } -> Some read_loc | EUnableToSpread _ @@ -2857,6 +2867,7 @@ let friendly_message_of_msg = function | EUnionOptimizationOnNonUnion { loc = _; arg } -> Normal (MessageInvalidUseOfFlowEnforceOptimized arg) | ECannotCallReactComponent { reason } -> Normal (MessageCannotCallReactComponent reason) + | EMatchNotExhaustive { loc = _; reason } -> Normal (MessageMatchNotExhaustive reason) let defered_in_speculation = function | EUntypedTypeImport _ @@ -3193,3 +3204,4 @@ let error_code_of_message err : error_code option = | EUnionOptimization _ -> Some UnionUnoptimizable | EUnionOptimizationOnNonUnion _ -> Some UnionUnoptimizable | ECannotCallReactComponent _ -> Some ReactRuleCallComponent + | EMatchNotExhaustive _ -> Some MatchNotExhaustive diff --git a/src/typing/errors/flow_intermediate_error.ml b/src/typing/errors/flow_intermediate_error.ml index 2b41c2cc02a..48e8a635ebf 100644 --- a/src/typing/errors/flow_intermediate_error.ml +++ b/src/typing/errors/flow_intermediate_error.ml @@ -3982,6 +3982,13 @@ let to_printable_error : code ": null"; text " to disambiguate."; ] + | MessageMatchNotExhaustive reason -> + [ + code "match"; + text " is not exhaustively checked: "; + ref reason; + text " has not been fully checked against by the match patterns below."; + ] in let rec convert_error_message { kind; loc; error_code; root; message; misplaced_source_file = _ } = diff --git a/src/typing/errors/flow_intermediate_error_types.ml b/src/typing/errors/flow_intermediate_error_types.ml index 79c4b5e613c..8913bea2b1b 100644 --- a/src/typing/errors/flow_intermediate_error_types.ml +++ b/src/typing/errors/flow_intermediate_error_types.ml @@ -897,6 +897,7 @@ type 'loc message = reason: 'loc virtual_reason; null_loc: 'loc option; } + | MessageMatchNotExhaustive of 'loc virtual_reason type 'loc intermediate_error = { kind: Flow_errors_utils.error_kind; diff --git a/src/typing/merge_js.ml b/src/typing/merge_js.ml index a045c5f1780..a5f88ef0227 100644 --- a/src/typing/merge_js.ml +++ b/src/typing/merge_js.ml @@ -618,6 +618,35 @@ let check_spread_prop_keys cx tast = let (_ : _ Ast.Program.t) = checker#program tast in () +let check_match_exhaustiveness cx tast = + let checker = + object + inherit + [ALoc.t, ALoc.t * Type.t, ALoc.t, ALoc.t * Type.t] Flow_polymorphic_ast_mapper.mapper as super + + method on_type_annot x = x + + method on_loc_annot x = x + + method! match_expression x = + let { Ast.Expression.Match.match_keyword_loc = (loc, t); _ } = x in + (match Flow_js.possible_concrete_types_for_inspection cx (TypeUtil.reason_of_t t) t with + | [] -> () + | remaining_ts -> + Base.List.iter remaining_ts ~f:(fun remaining_t -> + Flow_js.add_output + cx + (Error_message.EMatchNotExhaustive + { loc; reason = TypeUtil.reason_of_t remaining_t } + ) + )); + super#match_expression x + end + in + if Context.enable_pattern_matching_expressions cx then + let (_ : _ Ast.Program.t) = checker#program tast in + () + let emit_refinement_information_as_errors = let open Loc_collections in let emit_refined_locations_info cx = @@ -680,6 +709,7 @@ let post_merge_checks cx ast tast metadata = detect_unused_promises cx; check_union_opt cx; check_spread_prop_keys cx tast; + check_match_exhaustiveness cx tast; emit_refinement_information_as_errors cx (* Check will lazily create types for the checked file's dependencies. These diff --git a/src/typing/statement.ml b/src/typing/statement.ml index 8a48bf8e053..4dbb98ddb61 100644 --- a/src/typing/statement.ml +++ b/src/typing/statement.ml @@ -2867,7 +2867,7 @@ module Make ); let t = AnyT.at (AnyError None) loc in ((loc, t), TSSatisfies (Tast_utils.error_mapper#ts_satisfies cast)) - | Match { Match.arg; cases; arg_internal; comments } -> + | Match { Match.arg; cases; arg_internal; match_keyword_loc; comments } -> if not @@ Context.enable_pattern_matching_expressions cx then ( Flow.add_output cx @@ -2920,9 +2920,21 @@ module Make (case_ast :: cases, ts, all_throws) ) in + let match_keyword_loc = + ( match_keyword_loc, + Type_env.var_ref + ~lookup_mode:ForValue + cx + (OrdinaryName Flow_ast_utils.match_root_name) + match_keyword_loc + ) + in let match_t = union_of_ts reason (List.rev ts_rev) in let ast = - ((loc, match_t), Match { Match.arg; cases = List.rev cases_rev; arg_internal; comments }) + ( (loc, match_t), + Match + { Match.arg; cases = List.rev cases_rev; arg_internal; match_keyword_loc; comments } + ) in if (not (List.is_empty cases)) && all_throws then Abnormal.throw_expr_control_flow_exception loc ast diff --git a/tests/match/match.exp b/tests/match/match.exp index a9fe5c8fe68..b4d319efbe0 100644 --- a/tests/match/match.exp +++ b/tests/match/match.exp @@ -108,327 +108,293 @@ References: ^^^^^ [2] -Error ------------------------------------------------------------------------------------------------ matching.js:20:14 +Error ------------------------------------------------------------------------------------------------ matching.js:15:14 -Cannot cast `d` to empty because number literal `-2` [1] is incompatible with empty [2]. [incompatible-cast] +`match` is not exhaustively checked: number literal `-2` [1] has not been fully checked against by the match patterns +below. [match-not-exhaustive] - matching.js:20:14 - 20| const d: d as empty, // ERROR: not all members checked - ^ + matching.js:15:14 + 15| const e2 = match (x) { // ERROR: not all members checked + ^^^^^ References: matching.js:3:24 3| declare const x: 1 | -2 | 3n | 's' | false | null; ^^ [1] - matching.js:20:19 - 20| const d: d as empty, // ERROR: not all members checked - ^^^^^ [2] -Error ------------------------------------------------------------------------------------------------ matching.js:20:14 +Error ------------------------------------------------------------------------------------------------ matching.js:15:14 -Cannot cast `d` to empty because string literal `s` [1] is incompatible with empty [2]. [incompatible-cast] +`match` is not exhaustively checked: string literal `s` [1] has not been fully checked against by the match patterns +below. [match-not-exhaustive] - matching.js:20:14 - 20| const d: d as empty, // ERROR: not all members checked - ^ + matching.js:15:14 + 15| const e2 = match (x) { // ERROR: not all members checked + ^^^^^ References: matching.js:3:34 3| declare const x: 1 | -2 | 3n | 's' | false | null; ^^^ [1] - matching.js:20:19 - 20| const d: d as empty, // ERROR: not all members checked - ^^^^^ [2] -Error ------------------------------------------------------------------------------------------------ matching.js:39:14 +Error ------------------------------------------------------------------------------------------------ matching.js:36:14 -Cannot cast `d` to empty because number literal `2` [1] is incompatible with empty [2]. [incompatible-cast] +`match` is not exhaustively checked: number literal `2` [1] has not been fully checked against by the match patterns +below. [match-not-exhaustive] - matching.js:39:14 - 39| const d: d as empty, // ERROR: `2` not checked - ^ + matching.js:36:14 + 36| const e2 = match (x) { // ERROR: `2` not checked + ^^^^^ References: - matching.js:26:24 - 26| declare const x: 1 | 2; + matching.js:25:24 + 25| declare const x: 1 | 2; ^ [1] - matching.js:39:19 - 39| const d: d as empty, // ERROR: `2` not checked - ^^^^^ [2] -Error ------------------------------------------------------------------------------------------------ matching.js:55:14 +Error ------------------------------------------------------------------------------------------------ matching.js:51:14 -Cannot cast `d` to empty because undefined [1] is incompatible with empty [2]. [incompatible-cast] +`match` is not exhaustively checked: undefined [1] has not been fully checked against by the match patterns below. +[match-not-exhaustive] - matching.js:55:14 - 55| const d: d as empty, // ERROR: `undefined` not checked - ^ + matching.js:51:14 + 51| const e2 = match (x) { // ERROR: `undefined` not checked + ^^^^^ References: - matching.js:45:24 - 45| declare const x: 1 | void; + matching.js:43:24 + 43| declare const x: 1 | void; ^^^^ [1] - matching.js:55:19 - 55| const d: d as empty, // ERROR: `undefined` not checked - ^^^^^ [2] -Error ------------------------------------------------------------------------------------------------ matching.js:72:14 +Error ------------------------------------------------------------------------------------------------ matching.js:67:14 -Cannot cast `d` to empty because null or undefined [1] is incompatible with empty [2]. [incompatible-cast] +`match` is not exhaustively checked: null or undefined [1] has not been fully checked against by the match patterns +below. [match-not-exhaustive] - matching.js:72:14 - 72| const d: d as empty, // ERROR: `null` and `undefined` not checked - ^ + matching.js:67:14 + 67| const e2 = match (x) { // ERROR: `null` and `undefined` not checked + ^^^^^ References: - matching.js:61:20 - 61| declare const x: ?1; + matching.js:58:20 + 58| declare const x: ?1; ^^ [1] - matching.js:72:19 - 72| const d: d as empty, // ERROR: `null` and `undefined` not checked - ^^^^^ [2] -Error ------------------------------------------------------------------------------------------------ matching.js:93:14 +Error ------------------------------------------------------------------------------------------------ matching.js:87:14 -Cannot cast `d` to empty because number literal `2` [1] is incompatible with empty [2]. [incompatible-cast] +`match` is not exhaustively checked: number literal `2` [1] has not been fully checked against by the match patterns +below. [match-not-exhaustive] - matching.js:93:14 - 93| const d: d as empty, // ERROR: `2` not checked - ^ + matching.js:87:14 + 87| const e2 = match (x) { // ERROR: `2` not checked + ^^^^^ References: - matching.js:78:24 - 78| declare const x: 1 | 2; + matching.js:74:24 + 74| declare const x: 1 | 2; ^ [1] - matching.js:93:19 - 93| const d: d as empty, // ERROR: `2` not checked - ^^^^^ [2] -Error ----------------------------------------------------------------------------------------------- matching.js:138:14 +Error ----------------------------------------------------------------------------------------------- matching.js:131:14 -Cannot cast `d` to empty because number literal `2` [1] is incompatible with empty [2]. [incompatible-cast] +`match` is not exhaustively checked: number literal `2` [1] has not been fully checked against by the match patterns +below. [match-not-exhaustive] - matching.js:138:14 - 138| const d: d as empty, // ERROR: `2` not checked - ^ + matching.js:131:14 + 131| const e2 = match (f()) { // ERROR: `2` not checked + ^^^^^ References: - matching.js:128:30 - 128| declare const f: () => 1 | 2; + matching.js:123:30 + 123| declare const f: () => 1 | 2; ^ [1] - matching.js:138:19 - 138| const d: d as empty, // ERROR: `2` not checked - ^^^^^ [2] -Error ----------------------------------------------------------------------------------------------- matching.js:153:14 +Error ----------------------------------------------------------------------------------------------- matching.js:145:14 -Cannot cast `d` to empty because number literal `3` [1] is incompatible with empty [2]. [incompatible-cast] +`match` is not exhaustively checked: number literal `3` [1] has not been fully checked against by the match patterns +below. [match-not-exhaustive] - matching.js:153:14 - 153| const d: d as empty, // ERROR: `3` not checked - ^ + matching.js:145:14 + 145| const e2 = match (x) { // ERROR: `3` not checked + ^^^^^ References: - matching.js:144:28 - 144| declare const x: 1 | 2 | 3; + matching.js:138:28 + 138| declare const x: 1 | 2 | 3; ^ [1] - matching.js:153:19 - 153| const d: d as empty, // ERROR: `3` not checked - ^^^^^ [2] -Error ----------------------------------------------------------------------------------------------- matching.js:166:14 +Error ----------------------------------------------------------------------------------------------- matching.js:156:14 -Cannot cast `d` to empty because number literal `2` [1] is incompatible with empty [2]. [incompatible-cast] +`match` is not exhaustively checked: number literal `2` [1] has not been fully checked against by the match patterns +below. [match-not-exhaustive] - matching.js:166:14 - 166| const d: d as empty, // ERROR: `2` not checked - ^ + matching.js:156:14 + 156| const e1 = match (x) { // ERROR: `2` not checked + ^^^^^ References: - matching.js:159:24 - 159| declare const x: 1 | 2; + matching.js:152:24 + 152| declare const x: 1 | 2; ^ [1] - matching.js:166:19 - 166| const d: d as empty, // ERROR: `2` not checked - ^^^^^ [2] -Error ----------------------------------------------------------------------------------------------- matching.js:204:14 +Error ----------------------------------------------------------------------------------------------- matching.js:193:14 -Cannot cast `d` to empty because object type [1] is incompatible with empty [2]. [incompatible-cast] +`match` is not exhaustively checked: object type [1] has not been fully checked against by the match patterns below. +[match-not-exhaustive] - matching.js:204:14 - 204| const d: d as empty, // ERROR: `type: 'baz'` not checked - ^ + matching.js:193:14 + 193| const e2 = match (x) { // ERROR: `type: 'baz'` not checked + ^^^^^ References: - matching.js:192:20 - 192| | {type: 'baz', val: boolean}; + matching.js:184:20 + 184| | {type: 'baz', val: boolean}; ^^^^^^^^^^^^^^^^^^^^^^^^^^^ [1] - matching.js:204:19 - 204| const d: d as empty, // ERROR: `type: 'baz'` not checked - ^^^^^ [2] -Error ----------------------------------------------------------------------------------------------- matching.js:254:14 +Error ----------------------------------------------------------------------------------------------- matching.js:242:14 -Cannot cast `d` to empty because object type [1] is incompatible with empty [2]. [incompatible-cast] +`match` is not exhaustively checked: object type [1] has not been fully checked against by the match patterns below. +[match-not-exhaustive] - matching.js:254:14 - 254| const d: d as empty, // ERROR: `type: 'bar', n: 2` not checked - ^ + matching.js:242:14 + 242| const e3 = match (x) { // ERROR: `type: 'bar', n: 2` not checked + ^^^^^ References: - matching.js:236:20 - 236| | {type: 'bar', n: 2, val: boolean}; + matching.js:227:20 + 227| | {type: 'bar', n: 2, val: boolean}; ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ [1] - matching.js:254:19 - 254| const d: d as empty, // ERROR: `type: 'bar', n: 2` not checked - ^^^^^ [2] -Error ----------------------------------------------------------------------------------------------- matching.js:282:14 +Error ----------------------------------------------------------------------------------------------- matching.js:270:14 -Cannot cast `d` to empty because object type [1] is incompatible with empty [2]. [incompatible-cast] +`match` is not exhaustively checked: object type [1] has not been fully checked against by the match patterns below. +[match-not-exhaustive] - matching.js:282:14 - 282| const d: d as empty, // ERROR: `type: 'bar'` not checked - ^ + matching.js:270:14 + 270| const e2 = match (x) { // ERROR: `type: 'bar'` not checked + ^^^^^ References: - matching.js:272:20 - 272| | {type: 'bar', val: string} + matching.js:262:20 + 262| | {type: 'bar', val: string} ^^^^^^^^^^^^^^^^^^^^^^^^^^ [1] - matching.js:282:19 - 282| const d: d as empty, // ERROR: `type: 'bar'` not checked - ^^^^^ [2] -Error ----------------------------------------------------------------------------------------------- matching.js:302:14 +Error ----------------------------------------------------------------------------------------------- matching.js:288:14 -Cannot cast `d` to empty because tuple type [1] is incompatible with empty [2]. [incompatible-cast] +`match` is not exhaustively checked: tuple type [1] has not been fully checked against by the match patterns below. +[match-not-exhaustive] - matching.js:302:14 - 302| const d: d as empty, // ERROR: `'baz'` element not checked - ^ + matching.js:288:14 + 288| const e2 = match (x) { // ERROR: `'baz'` element not checked + ^^^^^ References: - matching.js:290:20 - 290| | ['baz', boolean]; + matching.js:279:20 + 279| | ['baz', boolean]; ^^^^^^^^^^^^^^^^ [1] - matching.js:302:19 - 302| const d: d as empty, // ERROR: `'baz'` element not checked - ^^^^^ [2] -Error ----------------------------------------------------------------------------------------------- matching.js:338:22 +Error ----------------------------------------------------------------------------------------------- matching.js:326:22 Cannot cast `a` to empty because boolean [1] is incompatible with empty [2]. [incompatible-cast] - matching.js:338:22 - 338| [const a, _, _]: a as empty, // ERROR: `boolean` is not `empty` + matching.js:326:22 + 326| [const a, _, _]: a as empty, // ERROR: `boolean` is not `empty` ^ References: - matching.js:333:21 - 333| | [boolean, boolean, boolean]; + matching.js:321:21 + 321| | [boolean, boolean, boolean]; ^^^^^^^ [1] - matching.js:338:27 - 338| [const a, _, _]: a as empty, // ERROR: `boolean` is not `empty` + matching.js:326:27 + 326| [const a, _, _]: a as empty, // ERROR: `boolean` is not `empty` ^^^^^ [2] -Error ----------------------------------------------------------------------------------------------- matching.js:358:16 +Error ----------------------------------------------------------------------------------------------- matching.js:346:16 Cannot cast `a` to string because number [1] is incompatible with string [2]. [incompatible-cast] - matching.js:358:16 - 358| [const a]: a as string, // ERROR: `number` is not `string` + matching.js:346:16 + 346| [const a]: a as string, // ERROR: `number` is not `string` ^ References: - matching.js:354:21 - 354| declare const x: [number] | Array; + matching.js:342:21 + 342| declare const x: [number] | Array; ^^^^^^ [1] - matching.js:358:21 - 358| [const a]: a as string, // ERROR: `number` is not `string` + matching.js:346:21 + 346| [const a]: a as string, // ERROR: `number` is not `string` ^^^^^^ [2] -Error ----------------------------------------------------------------------------------------------- matching.js:380:14 +Error ----------------------------------------------------------------------------------------------- matching.js:366:14 -Cannot cast `d` to empty because tuple type [1] is incompatible with empty [2]. [incompatible-cast] +`match` is not exhaustively checked: tuple type [1] has not been fully checked against by the match patterns below. +[match-not-exhaustive] - matching.js:380:14 - 380| const d: d as empty, // ERROR: does not match all possibilities - ^ + matching.js:366:14 + 366| const e2 = match (x) { // ERROR: does not match all possibilities + ^^^^^ References: - matching.js:371:20 - 371| declare const x: [a: 0, b?: 1, c?: 2]; + matching.js:359:20 + 359| declare const x: [a: 0, b?: 1, c?: 2]; ^^^^^^^^^^^^^^^^^^^^ [1] - matching.js:380:19 - 380| const d: d as empty, // ERROR: does not match all possibilities - ^^^^^ [2] -Error ----------------------------------------------------------------------------------------------- matching.js:385:14 +Error ----------------------------------------------------------------------------------------------- matching.js:370:14 -Cannot cast `d` to empty because tuple type [1] is incompatible with empty [2]. [incompatible-cast] +`match` is not exhaustively checked: tuple type [1] has not been fully checked against by the match patterns below. +[match-not-exhaustive] - matching.js:385:14 - 385| const d: d as empty, // ERROR: does not match all possibilities - ^ + matching.js:370:14 + 370| const e3 = match (x) { // ERROR: does not match all possibilities + ^^^^^ References: - matching.js:371:20 - 371| declare const x: [a: 0, b?: 1, c?: 2]; + matching.js:359:20 + 359| declare const x: [a: 0, b?: 1, c?: 2]; ^^^^^^^^^^^^^^^^^^^^ [1] - matching.js:385:19 - 385| const d: d as empty, // ERROR: does not match all possibilities - ^^^^^ [2] -Error ----------------------------------------------------------------------------------------------- matching.js:390:14 +Error ----------------------------------------------------------------------------------------------- matching.js:374:14 -Cannot cast `d` to empty because tuple type [1] is incompatible with empty [2]. [incompatible-cast] +`match` is not exhaustively checked: tuple type [1] has not been fully checked against by the match patterns below. +[match-not-exhaustive] - matching.js:390:14 - 390| const d: d as empty, // ERROR: does not match all possibilities - ^ + matching.js:374:14 + 374| const e4 = match (x) { // ERROR: does not match all possibilities + ^^^^^ References: - matching.js:371:20 - 371| declare const x: [a: 0, b?: 1, c?: 2]; + matching.js:359:20 + 359| declare const x: [a: 0, b?: 1, c?: 2]; ^^^^^^^^^^^^^^^^^^^^ [1] - matching.js:390:19 - 390| const d: d as empty, // ERROR: does not match all possibilities - ^^^^^ [2] -Error ----------------------------------------------------------------------------------------------- matching.js:405:14 +Error ----------------------------------------------------------------------------------------------- matching.js:388:14 -Cannot cast `d` to empty because tuple type [1] is incompatible with empty [2]. [incompatible-cast] +`match` is not exhaustively checked: tuple type [1] has not been fully checked against by the match patterns below. +[match-not-exhaustive] - matching.js:405:14 - 405| const d: d as empty, // ERROR: does not match all elements - ^ + matching.js:388:14 + 388| const e2 = match (x) { // ERROR: does not match all elements + ^^^^^ References: - matching.js:396:20 - 396| declare const x: [a: 0, ...]; + matching.js:381:20 + 381| declare const x: [a: 0, ...]; ^^^^^^^^^^^ [1] - matching.js:405:19 - 405| const d: d as empty, // ERROR: does not match all elements - ^^^^^ [2] Error -------------------------------------------------------------------------------------------------- patterns.js:9:3 @@ -555,21 +521,21 @@ References: ^ [1] -Error ------------------------------------------------------------------------------------------------ patterns.js:99:23 +Error ----------------------------------------------------------------------------------------------- patterns.js:100:23 Cannot cast `n` to empty because number [1] is incompatible with empty [2]. [incompatible-cast] - patterns.js:99:23 - 99| {foo: const n} if n as empty: n, // ERROR - ^ + patterns.js:100:23 + 100| {foo: const n} if n as empty: n, // ERROR + ^ References: - patterns.js:93:26 - 93| declare const x: {foo: number}; - ^^^^^^ [1] - patterns.js:99:28 - 99| {foo: const n} if n as empty: n, // ERROR - ^^^^^ [2] + patterns.js:94:26 + 94| declare const x: {foo: number}; + ^^^^^^ [1] + patterns.js:100:28 + 100| {foo: const n} if n as empty: n, // ERROR + ^^^^^ [2] diff --git a/tests/match/matching.js b/tests/match/matching.js index ddd90a3cdff..a093c27b1d2 100644 --- a/tests/match/matching.js +++ b/tests/match/matching.js @@ -12,12 +12,11 @@ const d: d as empty, // OK: all members checked }; - const e2 = match (x) { + const e2 = match (x) { // ERROR: not all members checked 1: 0, false: 0, 3n: 0, null: 0, - const d: d as empty, // ERROR: not all members checked }; } @@ -34,9 +33,8 @@ const d: d as empty, // OK: all members checked }; - const e2 = match (x) { + const e2 = match (x) { // ERROR: `2` not checked one: 0, - const d: d as empty, // ERROR: `2` not checked }; } @@ -50,9 +48,8 @@ const d: d as empty, // OK: all members checked }; - const e2 = match (x) { + const e2 = match (x) { // ERROR: `undefined` not checked 1: 0, - const d: d as empty, // ERROR: `undefined` not checked }; } @@ -67,9 +64,8 @@ const d: d as empty, // OK: all members checked }; - const e2 = match (x) { + const e2 = match (x) { // ERROR: `null` and `undefined` not checked 1: 0, - const d: d as empty, // ERROR: `null` and `undefined` not checked }; } @@ -88,9 +84,8 @@ const d: d as empty, // OK: all members checked }; - const e2 = match (x) { + const e2 = match (x) { // ERROR: `2` not checked o.one: 0, - const d: d as empty, // ERROR: `2` not checked }; } @@ -133,9 +128,8 @@ const d: d as empty, // OK: all members checked }; - const e2 = match (f()) { + const e2 = match (f()) { // ERROR: `2` not checked 1: 0, - const d: d as empty, // ERROR: `2` not checked }; } @@ -148,9 +142,8 @@ const d: d as empty, // OK }; - const e2 = match (x) { + const e2 = match (x) { // ERROR: `3` not checked 1 | 2: true, - const d: d as empty, // ERROR: `3` not checked }; } @@ -160,10 +153,9 @@ declare function f(): boolean; - const e1 = match (x) { + const e1 = match (x) { // ERROR: `2` not checked 1: 0, 2 if f(): 0, - const d: d as empty, // ERROR: `2` not checked }; const e2 = match (x) { @@ -198,10 +190,9 @@ const d: d as empty, // OK: all members checked }; - const e2 = match (x) { + const e2 = match (x) { // ERROR: `type: 'baz'` not checked {type: 'foo', val: const a}: a as number, // OK {type: 'bar', val: const a}: a as string, // OK - const d: d as empty, // ERROR: `type: 'baz'` not checked }; // Using idents as pattern @@ -248,10 +239,9 @@ const d: d as empty, // OK: all members checked }; - const e3 = match (x) { + const e3 = match (x) { // ERROR: `type: 'bar', n: 2` not checked {type: 'foo', val: const a}: a as number, // OK {type: 'bar', n: 1, val: const a}: a as string, // OK - const d: d as empty, // ERROR: `type: 'bar', n: 2` not checked }; } @@ -277,9 +267,8 @@ const d: d as empty, // OK }; - const e2 = match (x) { + const e2 = match (x) { // ERROR: `type: 'bar'` not checked {type: 'foo'} | {type: 'baz'}: 0, - const d: d as empty, // ERROR: `type: 'bar'` not checked }; } @@ -296,10 +285,9 @@ const d: d as empty, // OK: all members checked }; - const e2 = match (x) { + const e2 = match (x) { // ERROR: `'baz'` element not checked ['foo', const a]: a as number, // OK ['bar', const a]: a as string, // OK - const d: d as empty, // ERROR: `'baz'` element not checked }; // Using idents as pattern @@ -375,19 +363,16 @@ const d: d as empty, // OK: all elements matched }; - const e2 = match (x) { + const e2 = match (x) { // ERROR: does not match all possibilities [_, _, ...]: 0, - const d: d as empty, // ERROR: does not match all possibilities }; - const e3 = match (x) { + const e3 = match (x) { // ERROR: does not match all possibilities [_]: 0, - const d: d as empty, // ERROR: does not match all possibilities }; - const e4 = match (x) { + const e4 = match (x) { // ERROR: does not match all possibilities [_, _, _]: 0, - const d: d as empty, // ERROR: does not match all possibilities }; } @@ -400,8 +385,7 @@ const d: d as empty, // OK: all elements matched }; - const e2 = match (x) { + const e2 = match (x) { // ERROR: does not match all elements [_]: 0, - const d: d as empty, // ERROR: does not match all elements }; } diff --git a/tests/match/patterns.js b/tests/match/patterns.js index 3430804a4a9..2d52763ae12 100644 --- a/tests/match/patterns.js +++ b/tests/match/patterns.js @@ -85,6 +85,7 @@ const out = match (x) { [a, const a]: a, // ERROR: reference before declaration + _: 0, }; }