Skip to content

Commit

Permalink
[flow][match] Use synthetic binding for match argument to be able to …
Browse files Browse the repository at this point in the history
…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
  • Loading branch information
gkz authored and facebook-github-bot committed Dec 17, 2024
1 parent 2e6b498 commit 02e73a5
Show file tree
Hide file tree
Showing 9 changed files with 126 additions and 45 deletions.
42 changes: 25 additions & 17 deletions src/analysis/env_builder/__tests__/name_def_test.ml
Original file line number Diff line number Diff line change
Expand Up @@ -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" =
Expand All @@ -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" =
Expand All @@ -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" =
Expand All @@ -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" =
Expand All @@ -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" =
Expand All @@ -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" =
Expand All @@ -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" =
Expand All @@ -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]
] |}]
8 changes: 8 additions & 0 deletions src/analysis/env_builder/find_providers.ml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
14 changes: 11 additions & 3 deletions src/analysis/env_builder/name_def.ml
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
13 changes: 13 additions & 0 deletions src/analysis/env_builder/name_def_ordering.ml
Original file line number Diff line number Diff line change
Expand Up @@ -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) =
Expand Down
65 changes: 44 additions & 21 deletions src/analysis/env_builder/name_resolver.ml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 5 additions & 0 deletions src/parser/flow_ast_utils.ml
Original file line number Diff line number Diff line change
Expand Up @@ -674,3 +674,8 @@ let hook_call { E.Call.callee; _ } =
| _ -> false
in
hook_callee true callee

(* Match *)
let match_root_name = "<match_root>"

let match_root_ident loc = (loc, { Identifier.name = match_root_name; comments = None })
4 changes: 4 additions & 0 deletions src/parser/flow_ast_utils.mli
Original file line number Diff line number Diff line change
Expand Up @@ -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
8 changes: 4 additions & 4 deletions src/typing/statement.ml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
12 changes: 12 additions & 0 deletions tests/match/body.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

0 comments on commit 02e73a5

Please sign in to comment.