diff --git a/src/analysis/env_builder/__tests__/name_def_test.ml b/src/analysis/env_builder/__tests__/name_def_test.ml index 0c615559ab7..81f203e1ef5 100644 --- a/src/analysis/env_builder/__tests__/name_def_test.ml +++ b/src/analysis/env_builder/__tests__/name_def_test.ml @@ -1633,7 +1633,8 @@ let%expect_test "match_pattern_binding" = |}; [%expect {| [ - (3, 8) to (3, 9) => val (2, 8) to (2, 9) + (2, 1) to (2, 6) => val (2, 8) to (2, 9); + (3, 8) to (3, 9) => val (3, 2) to (3, 13) ] |}] let%expect_test "match_pattern_tuple" = @@ -1644,8 +1645,9 @@ let%expect_test "match_pattern_tuple" = |}; [%expect {| [ - (3, 9) to (3, 10) => (val (2, 8) to (2, 9))[0]; - (3, 2) to (3, 11) => val (2, 8) to (2, 9) + (2, 1) to (2, 6) => val (2, 8) to (2, 9); + (3, 9) to (3, 10) => (val (3, 2) to (3, 15))[0]; + (3, 2) to (3, 11) => val (3, 2) to (3, 15) ] |}] let%expect_test "match_pattern_tuple_rest" = @@ -1656,8 +1658,9 @@ let%expect_test "match_pattern_tuple_rest" = |}; [%expect {| [ - (3, 15) to (3, 16) => (val (2, 8) to (2, 9))[...]; - (3, 2) to (3, 17) => val (2, 8) to (2, 9) + (2, 1) to (2, 6) => val (2, 8) to (2, 9); + (3, 15) to (3, 16) => (val (3, 2) to (3, 21))[...]; + (3, 2) to (3, 17) => val (3, 2) to (3, 21) ] |}] let%expect_test "match_pattern_object" = @@ -1668,8 +1671,9 @@ let%expect_test "match_pattern_object" = |}; [%expect {| [ - (3, 14) to (3, 15) => (val (2, 8) to (2, 9)).foo; - (3, 2) to (3, 16) => val (2, 8) to (2, 9) + (2, 1) to (2, 6) => val (2, 8) to (2, 9); + (3, 14) to (3, 15) => (val (3, 2) to (3, 20)).foo; + (3, 2) to (3, 16) => val (3, 2) to (3, 20) ] |}] let%expect_test "match_pattern_object_shorthand" = @@ -1680,8 +1684,9 @@ let%expect_test "match_pattern_object_shorthand" = |}; [%expect {| [ - (3, 9) to (3, 12) => (val (2, 8) to (2, 9)).foo; - (3, 2) to (3, 13) => val (2, 8) to (2, 9) + (2, 1) to (2, 6) => val (2, 8) to (2, 9); + (3, 9) to (3, 12) => (val (3, 2) to (3, 19)).foo; + (3, 2) to (3, 13) => val (3, 2) to (3, 19) ] |}] let%expect_test "match_pattern_object_rest" = @@ -1692,8 +1697,9 @@ let%expect_test "match_pattern_object_rest" = |}; [%expect {| [ - (3, 20) to (3, 24) => (val (2, 8) to (2, 9)){ ... }; - (3, 2) to (3, 25) => val (2, 8) to (2, 9) + (2, 1) to (2, 6) => val (2, 8) to (2, 9); + (3, 20) to (3, 24) => (val (3, 2) to (3, 32)){ ... }; + (3, 2) to (3, 25) => val (3, 2) to (3, 32) ] |}] let%expect_test "match_pattern_as" = @@ -1704,8 +1710,9 @@ let%expect_test "match_pattern_as" = |}; [%expect {| [ - (3, 13) to (3, 14) => (val (2, 8) to (2, 9)).foo; - (3, 2) to (3, 15) => val (2, 8) to (2, 9) + (2, 1) to (2, 6) => val (2, 8) to (2, 9); + (3, 13) to (3, 14) => (val (3, 2) to (3, 19)).foo; + (3, 2) to (3, 15) => val (3, 2) to (3, 19) ] |}] let%expect_test "match_pattern_nested" = @@ -1716,8 +1723,9 @@ let%expect_test "match_pattern_nested" = |}; [%expect {| [ - (3, 27) to (3, 28) => (((val (2, 8) to (2, 9)).foo)[2]).bar; - (3, 2) to (3, 31) => val (2, 8) to (2, 9); - (3, 8) to (3, 30) => (val (2, 8) to (2, 9)).foo; - (3, 15) to (3, 29) => ((val (2, 8) to (2, 9)).foo)[2] + (2, 1) to (2, 6) => val (2, 8) to (2, 9); + (3, 27) to (3, 28) => (((val (3, 2) to (3, 35)).foo)[2]).bar; + (3, 2) to (3, 31) => val (3, 2) to (3, 35); + (3, 8) to (3, 30) => (val (3, 2) to (3, 35)).foo; + (3, 15) to (3, 29) => ((val (3, 2) to (3, 35)).foo)[2] ] |}] diff --git a/src/analysis/env_builder/find_providers.ml b/src/analysis/env_builder/find_providers.ml index caca9de2295..8324450cc9f 100644 --- a/src/analysis/env_builder/find_providers.ml +++ b/src/analysis/env_builder/find_providers.ml @@ -986,6 +986,14 @@ end = struct loc expr + method! match_expression loc x = + let { Ast.Expression.Match.arg_internal; arg = _; cases = _; comments = _ } = x in + ignore + @@ this#pattern_identifier + ~kind:Ast.Variable.Const + (Flow_ast_utils.match_root_ident arg_internal); + super#match_expression loc x + method! pattern_identifier ?kind ((loc, { Ast.Identifier.name; comments = _ }) as ident) = begin match kind with diff --git a/src/analysis/env_builder/name_def.ml b/src/analysis/env_builder/name_def.ml index 6ae15310791..41b551c912c 100644 --- a/src/analysis/env_builder/name_def.ml +++ b/src/analysis/env_builder/name_def.ml @@ -3142,10 +3142,18 @@ 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 + let { arg; cases; arg_internal; comments = _ } = x in ignore @@ this#expression arg; - Base.List.iter cases ~f:(function (_, { Case.pattern; body; guard; comments = _ }) -> - let acc = Value { hints = []; expr = arg } in + this#add_ordinary_binding + arg_internal + (mk_reason RMatchExpression arg_internal) + (Binding (Root (Value { hints = []; expr = arg }))); + Base.List.iter cases ~f:(function (case_loc, { Case.pattern; body; guard; comments = _ }) -> + let match_root = + (case_loc, Ast.Expression.Identifier (Flow_ast_utils.match_root_ident case_loc)) + in + ignore @@ this#expression match_root; + let acc = Value { hints = []; expr = match_root } in this#add_match_destructure_bindings acc pattern; ignore @@ super#match_pattern pattern; Base.Option.iter guard ~f:(fun guard -> ignore @@ this#expression guard); diff --git a/src/analysis/env_builder/name_def_ordering.ml b/src/analysis/env_builder/name_def_ordering.ml index 2a2346cf9b8..53e01b50c7e 100644 --- a/src/analysis/env_builder/name_def_ordering.ml +++ b/src/analysis/env_builder/name_def_ordering.ml @@ -273,6 +273,19 @@ struct Base.List.iter ~f:(this#add ~why:loc) writes; super#yield loc yield + method! match_expression _ x = + let { Ast.Expression.Match.arg; cases; arg_internal; comments = _ } = x in + ignore @@ this#expression arg; + ignore + @@ this#pattern_identifier + ~kind:Ast.Variable.Const + (Flow_ast_utils.match_root_ident arg_internal); + Base.List.iter cases ~f:(fun (case_loc, case) -> + ignore @@ this#identifier (Flow_ast_utils.match_root_ident case_loc); + ignore @@ super#match_expression_case (case_loc, case) + ); + x + (* In order to resolve a def containing a variable write, the write itself should first be resolved *) method! pattern_identifier ?kind:_ ((loc, _) as id) = diff --git a/src/analysis/env_builder/name_resolver.ml b/src/analysis/env_builder/name_resolver.ml index ef7e62f09ff..2600b1ffc25 100644 --- a/src/analysis/env_builder/name_resolver.ml +++ b/src/analysis/env_builder/name_resolver.ml @@ -2911,31 +2911,54 @@ module Make (Context : C) (FlowAPIUtils : F with type cx = Context.t) : this#merge_completion_states conditional_completion_states; expr - method! match_expression _ x = + 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; comments = _ } = x in + let match_root_ident = Flow_ast_utils.match_root_ident in ignore @@ this#expression arg; let env0 = this#env_snapshot in + let bindings = Bindings.singleton (match_root_ident arg_internal, Bindings.Internal) in let completion_states = ref [] in - Base.List.iter cases ~f:(fun case -> - let (case_loc, { Case.pattern; body; guard; comments = _ }) = case in - let lexical_hoist = new lexical_hoister ~flowmin_compatibility:false ~enable_enums in - let bindings = lexical_hoist#eval lexical_hoist#match_pattern pattern in - this#with_bindings - ~lexical:true - case_loc - bindings - (fun () -> - ignore @@ this#match_pattern pattern; - let completion_state = - this#run_to_completion (fun () -> - Base.Option.iter guard ~f:(fun guard -> ignore @@ this#expression guard); - ignore @@ this#expression body - ) - in - completion_states := completion_state :: !completion_states) - () - ); + ignore + @@ this#with_bindings + ~lexical:true + match_loc + bindings + (fun () -> + this#pattern_identifier_with_annot_check + ~kind:Flow_ast.Variable.Const + arg_internal + (match_root_ident arg_internal) + (Ast.Type.Missing ALoc.none); + ignore @@ this#identifier (match_root_ident arg_internal); + Base.List.iter cases ~f:(fun case -> + let (case_loc, { Case.pattern; body; guard; comments = _ }) = case in + let lexical_hoist = + new lexical_hoister ~flowmin_compatibility:false ~enable_enums + in + let bindings = lexical_hoist#eval lexical_hoist#match_pattern pattern in + this#with_bindings + ~lexical:true + case_loc + bindings + (fun () -> + let arg = + ( case_loc, + Ast.Expression.Identifier (Flow_ast_utils.match_root_ident case_loc) + ) + in + ignore @@ this#match_pattern pattern; + ignore @@ this#expression arg; + let completion_state = + this#run_to_completion (fun () -> + Base.Option.iter guard ~f:(fun guard -> ignore @@ this#expression guard); + ignore @@ this#expression body + ) + in + completion_states := completion_state :: !completion_states) + () + )) + (); let completion_states = !completion_states |> List.rev in this#reset_env env0; (match completion_states with diff --git a/src/parser/flow_ast_utils.ml b/src/parser/flow_ast_utils.ml index cd1b4a5c5ab..e2df1cd8d45 100644 --- a/src/parser/flow_ast_utils.ml +++ b/src/parser/flow_ast_utils.ml @@ -674,3 +674,8 @@ let hook_call { E.Call.callee; _ } = | _ -> false in hook_callee true callee + +(* Match *) +let match_root_name = "" + +let match_root_ident loc = (loc, { Identifier.name = match_root_name; comments = None }) diff --git a/src/parser/flow_ast_utils.mli b/src/parser/flow_ast_utils.mli index f7e340e2437..0517e81a3a8 100644 --- a/src/parser/flow_ast_utils.mli +++ b/src/parser/flow_ast_utils.mli @@ -172,3 +172,7 @@ val hook_function : ('a, 'b) Flow_ast.Function.t -> 'b option val hook_call : ('a, 'b) Flow_ast.Expression.Call.t -> bool val hook_name : string -> bool + +val match_root_name : string + +val match_root_ident : 'loc -> ('loc, 'loc) Flow_ast.Identifier.t diff --git a/src/typing/statement.ml b/src/typing/statement.ml index 3d7bdc2b5e1..8a48bf8e053 100644 --- a/src/typing/statement.ml +++ b/src/typing/statement.ml @@ -2875,15 +2875,16 @@ module Make Tast_utils.error_mapper#expression ex ) else let reason = mk_reason RMatchExpression loc in - let arg_orig = arg in let arg = expression cx arg in + let ((_, arg_t), _) = arg in + Type_env.init_const cx ~use_op:unknown_use arg_t arg_internal; let (cases_rev, ts_rev, all_throws) = Base.List.fold cases ~init:([], [], true) ~f:(fun (cases, ts, all_throws) case -> let (case_loc, { Match.Case.pattern; body; guard; comments }) = case in let pattern = Match_pattern.pattern cx - arg_orig + (case_loc, Identifier (Flow_ast_utils.match_root_ident case_loc)) pattern ~on_identifier:identifier ~on_expression:expression @@ -2893,8 +2894,7 @@ module Make ~default:(Type_env.get_var_declared_type cx (OrdinaryName name) name_loc) cx name - name_loc - ) + name_loc) in let (guard, guard_throws) = match guard with diff --git a/tests/match/body.js b/tests/match/body.js index 54d2f2938be..8cb293fafce 100644 --- a/tests/match/body.js +++ b/tests/match/body.js @@ -55,3 +55,15 @@ function f2() { out as string; // OK out as empty; // ERROR } + +// Nested matches +{ + const out = match (x) { + 1: 1, + const a: match (a) { + const a: a, + }, + }; + + out as number; // OK +}