Skip to content

Commit

Permalink
[flow][match] Match array patterns apply refinements
Browse files Browse the repository at this point in the history
Summary:
Match array patterns apply refinements to the match argument.

For this purpose, we add a new `ArrLen` predicate. It can handle both `=== length` and `>= length`

Changelog: [internal]

Reviewed By: SamChou19815

Differential Revision: D67261333

fbshipit-source-id: 76c464f0e580bc987473aaf77b131a9c6777d440
  • Loading branch information
gkz authored and facebook-github-bot committed Dec 18, 2024
1 parent 03e35b3 commit f7453e1
Show file tree
Hide file tree
Showing 14 changed files with 417 additions and 4 deletions.
27 changes: 27 additions & 0 deletions src/analysis/env_builder/__tests__/env_builder_refinement_test.ml
Original file line number Diff line number Diff line change
Expand Up @@ -7507,3 +7507,30 @@ let%expect_test "match_object_pattern" =
{refinement = And (And (object, Not (Null)), SentinelR type); writes = {refinement = Or (Or (Not (And (object, Not (Null))), Not (SentinelR type)), Not (PropExistsR (value))); writes = (2, 1) to (2, 6): (`<match_root>`)}}
}]
|}]

let%expect_test "match_array_pattern" =
(* Test case aligned with object pattern test case above. *)
print_ssa_test {|
(match (x) {
[ 'foo', const a]: a as number,
[ 'bar']: 1,
});
|};
[%expect {|
[
(2, 1) to (2, 6) => {
(2, 1) to (2, 6): (`<match_root>`)
};
(2, 8) to (2, 9) => {
Global x
};
(3, 2) to (3, 45) => {
{refinement = And (And (isArray, array length === 2), SentinelR 0); writes = (2, 1) to (2, 6): (`<match_root>`)}
};
(3, 33) to (3, 34) => {
(3, 29) to (3, 30): (`a`)
};
(4, 2) to (4, 19) => {
{refinement = And (And (isArray, array length === 1), SentinelR 0); writes = {refinement = Or (Not (And (isArray, array length === 2)), Not (SentinelR 0)); writes = (2, 1) to (2, 6): (`<match_root>`)}}
}]
|}]
25 changes: 25 additions & 0 deletions src/analysis/env_builder/env_api.ml
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,11 @@ module type S = sig
type values = read L.LMap.t

module Refi : sig
type array_length_op =
| ArrLenEqual
| ArrLenGreaterThanEqual
[@@deriving show]

type refinement_kind =
| AndR of refinement_kind * refinement_kind
| OrR of refinement_kind * refinement_kind
Expand All @@ -124,6 +129,10 @@ module type S = sig
| MaybeR
| InstanceOfR of (L.t, L.t) Ast.Expression.t
| IsArrayR
| ArrLenR of {
op: array_length_op;
n: int;
}
| BoolR of L.t
| FunctionR
| NumberR of L.t
Expand Down Expand Up @@ -407,6 +416,11 @@ module Make
and write_locs = write_loc list

module Refi = struct
type array_length_op =
| ArrLenEqual
| ArrLenGreaterThanEqual
[@@deriving show]

type refinement_kind =
| AndR of refinement_kind * refinement_kind
| OrR of refinement_kind * refinement_kind
Expand All @@ -417,6 +431,10 @@ module Make
| MaybeR
| InstanceOfR of (L.t, L.t) Ast.Expression.t
| IsArrayR
| ArrLenR of {
op: array_length_op;
n: int;
}
| BoolR of L.t
| FunctionR
| NumberR of L.t
Expand Down Expand Up @@ -671,6 +689,13 @@ module Make
| MaybeR -> "Maybe"
| InstanceOfR _ -> "instanceof"
| IsArrayR -> "isArray"
| ArrLenR { op; n } ->
let op =
match op with
| ArrLenEqual -> "==="
| ArrLenGreaterThanEqual -> ">="
in
Printf.sprintf "array length %s %i" op n
| BoolR _ -> "bool"
| FunctionR -> "function"
| NumberR _ -> "number"
Expand Down
1 change: 1 addition & 0 deletions src/analysis/env_builder/name_def_ordering.ml
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,7 @@ struct
| UndefinedR
| MaybeR
| IsArrayR
| ArrLenR _
| BoolR _
| FunctionR
| NumberR _
Expand Down
48 changes: 45 additions & 3 deletions src/analysis/env_builder/name_resolver.ml
Original file line number Diff line number Diff line change
Expand Up @@ -3170,9 +3170,51 @@ module Make (Context : C) (FlowAPIUtils : F with type cx = Context.t) :
| _ -> ());
recurse member pattern
)
| (_, ArrayPattern _) ->
(* TODO:match *)
()
| (loc, ArrayPattern { ArrayPattern.elements; rest; comments = _ }) ->
(match RefinementKey.of_expression acc with
| Some key ->
let refis =
this#start_refinement key ~refining_locs:(L.LSet.singleton loc) IsArrayR
in
(* Check the length *)
let refis =
this#extend_refinement
key
~refining_locs:(L.LSet.singleton loc)
(ArrLenR
{
op =
( if Base.Option.is_some rest then
ArrLenGreaterThanEqual
else
ArrLenEqual
);
n = Base.List.length elements;
}
)
refis
in
this#commit_refinement refis
| None -> ());
let number_of_i i =
Ast.Expression.NumberLiteral
{ Ast.NumberLiteral.value = float i; raw = string_of_int i; comments = None }
in
Base.List.iteri elements ~f:(fun i { ArrayPattern.Element.pattern; index } ->
let (pat_loc, _) = pattern in
let member =
( index,
let open Ast.Expression in
Member
{
Member._object = acc;
property = Member.PropertyExpression (pat_loc, number_of_i i);
comments = None;
}
)
in
recurse member pattern
)
in
recurse arg root_pattern

Expand Down
11 changes: 11 additions & 0 deletions src/typing/predicate_kit.ml
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,17 @@ and predicate_no_concretization cx trace result_collector l ~p =
| ArrP -> report_filtering_result_to_predicate_result (Type_filter.array l) result_collector
| NotP ArrP ->
report_filtering_result_to_predicate_result (Type_filter.not_array l) result_collector
(*******************)
(* array length *)
(*******************)
| ArrLenP { op; n } ->
report_filtering_result_to_predicate_result
(Type_filter.array_length ~sense:true ~op ~n l)
result_collector
| NotP (ArrLenP { op; n }) ->
report_filtering_result_to_predicate_result
(Type_filter.array_length ~sense:false ~op ~n l)
result_collector
(***********************)
(* typeof _ ~ "undefined" *)
(***********************)
Expand Down
15 changes: 15 additions & 0 deletions src/typing/type.ml
Original file line number Diff line number Diff line change
Expand Up @@ -1014,6 +1014,10 @@ module rec TypeTerm : sig
| SymbolP of ALoc.t (* symbol *)
| VoidP (* undefined *)
| ArrP (* Array.isArray *)
| ArrLenP of {
op: array_length_op;
n: int;
}
(* `if ('b' in a)` yields `flow (a, PredicateT(PropExistsP ("b"), tout))` *)
| PropExistsP of {
propname: string;
Expand Down Expand Up @@ -1049,6 +1053,10 @@ module rec TypeTerm : sig
(* e1 === e2 *)
| EqTest

and array_length_op =
| ArrLenEqual
| ArrLenGreaterThanEqual

and literal =
| Truthy
| AnyLiteral
Expand Down Expand Up @@ -4292,6 +4300,13 @@ let rec string_of_predicate = function
| SymbolP _ -> "symbol"
(* Array.isArray *)
| ArrP -> "array"
| ArrLenP { op; n } ->
let op =
match op with
| ArrLenEqual -> "==="
| ArrLenGreaterThanEqual -> ">="
in
spf "array length %s %i" op n
| PropExistsP { propname; _ } -> spf "prop `%s` exists" propname
| PropTruthyP (key, _) -> spf "prop `%s` is truthy" key
| PropIsExactlyNullP (key, _) -> spf "prop `%s` is exactly null" key
Expand Down
1 change: 1 addition & 0 deletions src/typing/typeUtil.ml
Original file line number Diff line number Diff line change
Expand Up @@ -759,6 +759,7 @@ let rec eq_predicate (p1, p2) =
| (PropExistsP { propname = s1; _ }, PropExistsP { propname = s2; _ }) -> s1 = s2
| (PropTruthyP (s1, _), PropTruthyP (s2, _)) -> s1 = s2
| (PropNonMaybeP (s1, _), PropNonMaybeP (s2, _)) -> s1 = s2
| (ArrLenP { op = op1; n = n1 }, ArrLenP { op = op2; n = n2 }) -> op1 = op2 && n1 = n2
(* Complex *)
| (BinaryP (b1, OpenT (_, id1)), BinaryP (b2, OpenT (_, id2))) -> b1 = b2 && id1 = id2
| (BinaryP _, BinaryP _) -> p1 = p2
Expand Down
7 changes: 7 additions & 0 deletions src/typing/type_env.ml
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,13 @@ let predicate_of_refinement cx =
Type_operation_utils.TypeAssertions.assert_instanceof_rhs cx t;
Some (BinaryP (InstanceofTest, t))
| IsArrayR -> Some ArrP
| ArrLenR { op; n } ->
let op =
match op with
| Env_api.Refi.ArrLenEqual -> Type.ArrLenEqual
| Env_api.Refi.ArrLenGreaterThanEqual -> Type.ArrLenGreaterThanEqual
in
Some (ArrLenP { op; n })
| BoolR loc -> Some (BoolP loc)
| FunctionR -> Some FunP
| NumberR loc -> Some (NumP loc)
Expand Down
38 changes: 38 additions & 0 deletions src/typing/type_filter.ml
Original file line number Diff line number Diff line change
Expand Up @@ -597,6 +597,44 @@ let not_array t =
| DefT (_, ArrT _) -> DefT (reason_of_t t, EmptyT) |> changed_result
| _ -> unchanged_result t

let array_length ~sense ~op ~n t =
match t with
| DefT (_, ArrT (TupleAT { arity = (num_req, num_total); inexact; _ })) ->
(* `None` represents "maybe" a match *)
let matches =
match op with
| ArrLenEqual ->
if n = num_req && n = num_total && not inexact then
Some true
else if n >= num_req && (n <= num_total || inexact) then
None
else
Some false
| ArrLenGreaterThanEqual ->
if n <= num_req then
Some true
else if n <= num_total || inexact then
None
else
Some false
in
(match (matches, sense) with
| (Some true, true)
| (Some false, false)
| (None, _) ->
unchanged_result t
| (Some false, true)
| (Some true, false) ->
DefT (reason_of_t t, EmptyT) |> changed_result)
| DefT (_, ArrT (ArrayAT _ | ROArrayAT _)) ->
(* `[...]` matches every length, so arrays are matched. *)
let matches = n = 0 && op = ArrLenGreaterThanEqual in
if matches = sense then
unchanged_result t
else
DefT (reason_of_t t, EmptyT) |> changed_result
| _ -> unchanged_result t

let sentinel_refinement =
let open UnionEnum in
let enum_match sense = function
Expand Down
2 changes: 2 additions & 0 deletions src/typing/type_filter.mli
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ val array : Type.t -> filter_result

val not_array : Type.t -> filter_result

val array_length : sense:bool -> op:Type.array_length_op -> n:int -> Type.t -> filter_result

val sentinel_refinement :
Type.t -> Reason.t -> Type.t -> bool -> Type.UnionEnum.star -> filter_result

Expand Down
1 change: 1 addition & 0 deletions src/typing/type_mapper.ml
Original file line number Diff line number Diff line change
Expand Up @@ -912,6 +912,7 @@ class virtual ['a] t =
| SymbolP _
| VoidP
| ArrP
| ArrLenP _
| PropNonMaybeP _
| PropNonVoidP _
| PropIsExactlyNullP _
Expand Down
1 change: 1 addition & 0 deletions src/typing/type_visitor.ml
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,7 @@ class ['a] t =
| SymbolP _ -> acc
| VoidP -> acc
| ArrP -> acc
| ArrLenP _ -> acc
| PropTruthyP _ -> acc
| PropExistsP _ -> acc
| PropNonVoidP _ -> acc
Expand Down
Loading

0 comments on commit f7453e1

Please sign in to comment.