From 02e73a5e998cffc17de068842073ade4b7017bc1 Mon Sep 17 00:00:00 2001 From: George Zahariev Date: Tue, 17 Dec 2024 15:02:00 -0800 Subject: [PATCH] [flow][match] Use synthetic binding for match argument to be able to refine Summary: Changelog: [internal] We want to refine the match argument before we destruct it, however it can be an expression, not just an identifier/member. We can solve this by creating a synthetic binding. We create one here, using the location added in the previous diff. We initialize it with the value of the argument. Now in future diffs we will be able to refine this value. Reviewed By: SamChou19815 Differential Revision: D67261334 fbshipit-source-id: 58a9e3abcaf42695a3b7c977a70a3e99c39b61d8 --- .../env_builder/__tests__/name_def_test.ml | 42 +++++++----- src/analysis/env_builder/find_providers.ml | 8 +++ src/analysis/env_builder/name_def.ml | 14 +++- src/analysis/env_builder/name_def_ordering.ml | 13 ++++ src/analysis/env_builder/name_resolver.ml | 65 +++++++++++++------ src/parser/flow_ast_utils.ml | 5 ++ src/parser/flow_ast_utils.mli | 4 ++ src/typing/statement.ml | 8 +-- tests/match/body.js | 12 ++++ 9 files changed, 126 insertions(+), 45 deletions(-) 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 +}