Skip to content

Commit

Permalink
[flow] add option to create sound versions of literal types in type_f…
Browse files Browse the repository at this point in the history
…ilter

Summary:
Without this change in functions like
```
const fn = (x: 'a'| 'b') => x !== 'a';
```
after D67983681 we'd infer a `StrT_UNSOUND('b')` as the type guard for `x`. This is not ideal as this type is directly lifted to an annotation where these unsound types behave particularly poorly.

So, instead, we introduce a mode where:
* the coercions of `SingletonStrT ~> StrT_UNSOUND`, etc. are disallowed
* Type_filter never produces `StrT_UNSOUND`, etc.

For now this change has no effect, because `with_disallowed_unsound_literal_coercsion` is never called. This starts being used in D67983681.

Changelog: [internal]

Reviewed By: SamChou19815

Differential Revision: D68339060

fbshipit-source-id: ebb5ed7e7f808f025b7ce4b7665005ffb1fd2cac
  • Loading branch information
panagosg7 authored and facebook-github-bot committed Jan 18, 2025
1 parent 4dd90e3 commit 9eccbcd
Show file tree
Hide file tree
Showing 7 changed files with 96 additions and 55 deletions.
12 changes: 12 additions & 0 deletions src/typing/context.ml
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,8 @@ type component_t = {
mutable ctor_callee: Type.t ALocMap.t;
(* Union optimization checks *)
mutable union_opt: Type.t ALocMap.t;
(* Natural inference (enables SingletonStrT ~> StrT_UNSOUND coercions) *)
mutable allow_unsound_literal_coercsion: bool;
}
[@@warning "-69"]

Expand Down Expand Up @@ -411,6 +413,7 @@ let make_ccx () =
signature_help_callee = ALocMap.empty;
ctor_callee = ALocMap.empty;
union_opt = ALocMap.empty;
allow_unsound_literal_coercsion = true;
}

let make ccx metadata file aloc_table resolve_require mk_builtins =
Expand Down Expand Up @@ -801,6 +804,15 @@ let set_union_opt cx loc t = cx.ccx.union_opt <- ALocMap.add loc t cx.ccx.union_

let iter_union_opt cx ~f = ALocMap.iter f cx.ccx.union_opt

let allow_unsound_literal_coercsion cx = cx.ccx.allow_unsound_literal_coercsion

let with_disallowed_unsound_literal_coercsion cx ~f =
let old_allow_unsound_literal_coercsion = cx.ccx.allow_unsound_literal_coercsion in
cx.ccx.allow_unsound_literal_coercsion <- false;
Exception.protect ~f ~finally:(fun () ->
cx.ccx.allow_unsound_literal_coercsion <- old_allow_unsound_literal_coercsion
)

let add_exists_check cx loc t =
let tset =
match ALocMap.find_opt loc cx.ccx.exists_checks with
Expand Down
4 changes: 4 additions & 0 deletions src/typing/context.mli
Original file line number Diff line number Diff line change
Expand Up @@ -544,6 +544,10 @@ val get_ctor_callee : t -> ALoc.t -> Type.t option

val iter_union_opt : t -> f:(ALocMap.key -> Type.t -> unit) -> unit

val allow_unsound_literal_coercsion : t -> bool

val with_disallowed_unsound_literal_coercsion : t -> f:(unit -> 'a) -> 'a

val remove_avar : t -> int -> unit

val iter_annot_dependent_set : t -> (int -> Type.AConstraint.op -> unit) -> ISet.t -> unit
Expand Down
9 changes: 5 additions & 4 deletions src/typing/flow_js.ml
Original file line number Diff line number Diff line change
Expand Up @@ -2256,13 +2256,14 @@ struct
ReposLowerT { reason; use_desc; use_t = u }
) ->
rec_flow cx trace (reposition_reason cx ~trace reason ~use_desc l, u)
| (DefT (reason, SingletonStrT key), _) ->
| (DefT (reason, SingletonStrT key), _) when Context.allow_unsound_literal_coercsion cx ->
rec_flow cx trace (DefT (reason, StrT_UNSOUND (None, key)), u)
| (DefT (reason, SingletonNumT lit), _) ->
| (DefT (reason, SingletonNumT lit), _) when Context.allow_unsound_literal_coercsion cx ->
rec_flow cx trace (DefT (reason, NumT_UNSOUND (None, lit)), u)
| (DefT (reason, SingletonBoolT b), _) ->
| (DefT (reason, SingletonBoolT b), _) when Context.allow_unsound_literal_coercsion cx ->
rec_flow cx trace (DefT (reason, BoolT_UNSOUND b), u)
| (DefT (reason, SingletonBigIntT lit), _) ->
| (DefT (reason, SingletonBigIntT lit), _) when Context.allow_unsound_literal_coercsion cx
->
rec_flow cx trace (DefT (reason, BigIntT_UNSOUND (None, lit)), u)
(* NullProtoT is necessary as an upper bound, to distinguish between
(ObjT _, NullProtoT _) constraints and (ObjT _, DefT (_, NullT)), but as
Expand Down
30 changes: 15 additions & 15 deletions src/typing/predicate_kit.ml
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ and predicate_no_concretization cx trace result_collector l ~p =
(* typeof _ ~ "boolean" *)
(***********************)
| BoolP loc ->
report_filtering_result_to_predicate_result (Type_filter.boolean loc l) result_collector
report_filtering_result_to_predicate_result (Type_filter.boolean cx loc l) result_collector
| NotP (BoolP _) ->
report_filtering_result_to_predicate_result (Type_filter.not_boolean l) result_collector
(***********************)
Expand All @@ -178,7 +178,7 @@ and predicate_no_concretization cx trace result_collector l ~p =
(* _ ~ "some string" *)
(*********************)
| SingletonStrP (expected_loc, sense, lit) ->
let filtered_str = Type_filter.string_literal expected_loc sense (OrdinaryName lit) l in
let filtered_str = Type_filter.string_literal cx expected_loc sense (OrdinaryName lit) l in
report_filtering_result_to_predicate_result filtered_str result_collector
| NotP (SingletonStrP (_, _, lit)) ->
let filtered_str = Type_filter.not_string_literal (OrdinaryName lit) l in
Expand All @@ -187,7 +187,7 @@ and predicate_no_concretization cx trace result_collector l ~p =
(* _ ~ some number n *)
(*********************)
| SingletonNumP (expected_loc, sense, lit) ->
let filtered_num = Type_filter.number_literal expected_loc sense lit l in
let filtered_num = Type_filter.number_literal cx expected_loc sense lit l in
report_filtering_result_to_predicate_result filtered_num result_collector
| NotP (SingletonNumP (_, _, lit)) ->
let filtered_num = Type_filter.not_number_literal lit l in
Expand All @@ -203,7 +203,7 @@ and predicate_no_concretization cx trace result_collector l ~p =
(* _ ~ some bigint n *)
(*********************)
| SingletonBigIntP (expected_loc, sense, lit) ->
let filtered_bigint = Type_filter.bigint_literal expected_loc sense lit l in
let filtered_bigint = Type_filter.bigint_literal cx expected_loc sense lit l in
report_filtering_result_to_predicate_result filtered_bigint result_collector
| NotP (SingletonBigIntP (_, _, lit)) ->
let filtered_bigint = Type_filter.not_bigint_literal lit l in
Expand Down Expand Up @@ -275,19 +275,19 @@ and predicate_no_concretization cx trace result_collector l ~p =
(* true *)
(********)
| SingletonBoolP (_, true) ->
let filtered = Type_filter.true_ l in
let filtered = Type_filter.true_ cx l in
report_filtering_result_to_predicate_result filtered result_collector
| NotP (SingletonBoolP (_, true)) ->
let filtered = Type_filter.not_true l in
let filtered = Type_filter.not_true cx l in
report_filtering_result_to_predicate_result filtered result_collector
(*********)
(* false *)
(*********)
| SingletonBoolP (_, false) ->
let filtered = Type_filter.false_ l in
let filtered = Type_filter.false_ cx l in
report_filtering_result_to_predicate_result filtered result_collector
| NotP (SingletonBoolP (_, false)) ->
let filtered = Type_filter.not_false l in
let filtered = Type_filter.not_false cx l in
report_filtering_result_to_predicate_result filtered result_collector
(************************)
(* truthyness *)
Expand Down Expand Up @@ -1172,7 +1172,7 @@ and eq_test cx _trace result_collector (sense, left, right) =
| DefT (_, SingletonStrT value) ->
let filtered =
if sense then
Type_filter.string_literal expected_loc sense value left
Type_filter.string_literal cx expected_loc sense value left
else
Type_filter.not_string_literal value left
in
Expand All @@ -1181,7 +1181,7 @@ and eq_test cx _trace result_collector (sense, left, right) =
| DefT (_, SingletonNumT value) ->
let filtered =
if sense then
Type_filter.number_literal expected_loc sense value left
Type_filter.number_literal cx expected_loc sense value left
else
Type_filter.not_number_literal value left
in
Expand All @@ -1190,25 +1190,25 @@ and eq_test cx _trace result_collector (sense, left, right) =
| DefT (_, SingletonBoolT true) ->
let filtered =
if sense then
Type_filter.true_ left
Type_filter.true_ cx left
else
Type_filter.not_true left
Type_filter.not_true cx left
in
report_filtering_result_to_predicate_result filtered result_collector
| DefT (_, BoolT_UNSOUND false)
| DefT (_, SingletonBoolT false) ->
let filtered =
if sense then
Type_filter.false_ left
Type_filter.false_ cx left
else
Type_filter.not_false left
Type_filter.not_false cx left
in
report_filtering_result_to_predicate_result filtered result_collector
| DefT (_, BigIntT_UNSOUND (_, value))
| DefT (_, SingletonBigIntT value) ->
let filtered =
if sense then
Type_filter.bigint_literal expected_loc sense value left
Type_filter.bigint_literal cx expected_loc sense value left
else
Type_filter.not_bigint_literal value left
in
Expand Down
8 changes: 4 additions & 4 deletions src/typing/subtyping_kit.ml
Original file line number Diff line number Diff line change
Expand Up @@ -1286,13 +1286,13 @@ module Make (Flow : INPUT) : OUTPUT = struct
(************)
(* literals *)
(************)
| (DefT (reason, SingletonStrT key), _) ->
| (DefT (reason, SingletonStrT key), _) when Context.allow_unsound_literal_coercsion cx ->
rec_flow_t cx trace ~use_op (DefT (reason, StrT_UNSOUND (None, key)), u)
| (DefT (reason, SingletonNumT lit), _) ->
| (DefT (reason, SingletonNumT lit), _) when Context.allow_unsound_literal_coercsion cx ->
rec_flow_t cx trace ~use_op (DefT (reason, NumT_UNSOUND (None, lit)), u)
| (DefT (reason, SingletonBoolT b), _) ->
| (DefT (reason, SingletonBoolT b), _) when Context.allow_unsound_literal_coercsion cx ->
rec_flow_t cx trace ~use_op (DefT (reason, BoolT_UNSOUND b), u)
| (DefT (reason, SingletonBigIntT lit), _) ->
| (DefT (reason, SingletonBigIntT lit), _) when Context.allow_unsound_literal_coercsion cx ->
rec_flow_t cx trace ~use_op (DefT (reason, BigIntT_UNSOUND (None, lit)), u)
| (NullProtoT reason, _) -> rec_flow_t cx trace ~use_op (DefT (reason, NullT), u)
(************)
Expand Down
72 changes: 48 additions & 24 deletions src/typing/type_filter.ml
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,13 @@ let rec not_undefined cx = function
| DefT (r, MixedT Mixed_non_null) -> DefT (r, MixedT Mixed_non_maybe) |> changed_result
| t -> unchanged_result t

let string_literal expected_loc sense expected t =
let mk_str_literal cx expected =
if Context.allow_unsound_literal_coercsion cx then
StrT_UNSOUND (None, expected)
else
SingletonStrT expected

let string_literal cx expected_loc sense expected t =
let expected_desc = RStringLit expected in
let lit_reason = replace_desc_new_reason expected_desc in
match t with
Expand All @@ -284,10 +290,10 @@ let string_literal expected_loc sense expected t =
DefT (mk_reason expected_desc expected_loc, StrT_UNSOUND (Some sense, expected))
|> changed_result
| DefT (r, StrGeneralT Truthy) when expected <> OrdinaryName "" ->
DefT (lit_reason r, StrT_UNSOUND (None, expected)) |> changed_result
DefT (lit_reason r, mk_str_literal cx expected) |> changed_result
| DefT (r, StrGeneralT AnyLiteral) ->
DefT (lit_reason r, StrT_UNSOUND (None, expected)) |> changed_result
| DefT (r, MixedT _) -> DefT (lit_reason r, StrT_UNSOUND (None, expected)) |> changed_result
DefT (lit_reason r, mk_str_literal cx expected) |> changed_result
| DefT (r, MixedT _) -> DefT (lit_reason r, mk_str_literal cx expected) |> changed_result
| AnyT _ as t -> unchanged_result t
| DefT (r, _) -> DefT (r, EmptyT) |> changed_result
| _ -> DefT (reason_of_t t, EmptyT) |> changed_result
Expand All @@ -299,7 +305,13 @@ let not_string_literal expected = function
DefT (r, EmptyT) |> changed_result
| t -> unchanged_result t

let number_literal expected_loc sense expected t =
let mk_num_literal cx expected =
if Context.allow_unsound_literal_coercsion cx then
NumT_UNSOUND (None, expected)
else
SingletonNumT expected

let number_literal cx expected_loc sense expected t =
let (_, expected_raw) = expected in
let expected_desc = RNumberLit expected_raw in
let lit_reason = replace_desc_new_reason expected_desc in
Expand All @@ -316,10 +328,10 @@ let number_literal expected_loc sense expected t =
DefT (mk_reason expected_desc expected_loc, NumT_UNSOUND (Some sense, expected))
|> changed_result
| DefT (r, NumGeneralT Truthy) when snd expected <> "0" ->
DefT (lit_reason r, NumT_UNSOUND (None, expected)) |> changed_result
DefT (lit_reason r, mk_num_literal cx expected) |> changed_result
| DefT (r, NumGeneralT AnyLiteral) ->
DefT (lit_reason r, NumT_UNSOUND (None, expected)) |> changed_result
| DefT (r, MixedT _) -> DefT (lit_reason r, NumT_UNSOUND (None, expected)) |> changed_result
DefT (lit_reason r, mk_num_literal cx expected) |> changed_result
| DefT (r, MixedT _) -> DefT (lit_reason r, mk_num_literal cx expected) |> changed_result
| AnyT _ as t -> unchanged_result t
| _ -> DefT (reason_of_t t, EmptyT) |> changed_result

Expand All @@ -330,7 +342,13 @@ let not_number_literal expected = function
DefT (r, EmptyT) |> changed_result
| t -> unchanged_result t

let bigint_literal expected_loc sense expected t =
let mk_bigint_literal cx expected =
if Context.allow_unsound_literal_coercsion cx then
BigIntT_UNSOUND (None, expected)
else
SingletonBigIntT expected

let bigint_literal cx expected_loc sense expected t =
let (_, expected_raw) = expected in
let expected_desc = RBigIntLit expected_raw in
let lit_reason = replace_desc_new_reason expected_desc in
Expand All @@ -347,10 +365,10 @@ let bigint_literal expected_loc sense expected t =
DefT (mk_reason expected_desc expected_loc, BigIntT_UNSOUND (Some sense, expected))
|> changed_result
| DefT (r, BigIntGeneralT Truthy) when snd expected <> "0n" ->
DefT (lit_reason r, BigIntT_UNSOUND (None, expected)) |> changed_result
DefT (lit_reason r, mk_bigint_literal cx expected) |> changed_result
| DefT (r, BigIntGeneralT AnyLiteral) ->
DefT (lit_reason r, BigIntT_UNSOUND (None, expected)) |> changed_result
| DefT (r, MixedT _) -> DefT (lit_reason r, BigIntT_UNSOUND (None, expected)) |> changed_result
DefT (lit_reason r, mk_bigint_literal cx expected) |> changed_result
| DefT (r, MixedT _) -> DefT (lit_reason r, mk_bigint_literal cx expected) |> changed_result
| AnyT _ as t -> unchanged_result t
| _ -> DefT (reason_of_t t, EmptyT) |> changed_result

Expand All @@ -361,48 +379,54 @@ let not_bigint_literal expected = function
DefT (r, EmptyT) |> changed_result
| t -> unchanged_result t

let true_ t =
let mk_bool_literal cx expected =
if Context.allow_unsound_literal_coercsion cx then
BoolT_UNSOUND expected
else
SingletonBoolT expected

let true_ cx t =
let lit_reason = replace_desc_new_reason (RBooleanLit true) in
match t with
| DefT (r, SingletonBoolT true) -> DefT (lit_reason r, SingletonBoolT true) |> unchanged_result
| DefT (r, BoolT_UNSOUND true) -> DefT (lit_reason r, BoolT_UNSOUND true) |> unchanged_result
| DefT (r, BoolGeneralT) -> DefT (lit_reason r, BoolT_UNSOUND true) |> changed_result
| DefT (r, MixedT _) -> DefT (lit_reason r, BoolT_UNSOUND true) |> changed_result
| DefT (r, BoolGeneralT) -> DefT (lit_reason r, mk_bool_literal cx true) |> changed_result
| DefT (r, MixedT _) -> DefT (lit_reason r, mk_bool_literal cx true) |> changed_result
| AnyT _ as t -> unchanged_result t
| t -> DefT (reason_of_t t, EmptyT) |> changed_result

let not_true t =
let not_true cx t =
let lit_reason = replace_desc_new_reason (RBooleanLit false) in
match t with
| DefT (r, SingletonBoolT true)
| DefT (r, BoolT_UNSOUND true) ->
DefT (r, EmptyT) |> changed_result
| DefT (r, BoolGeneralT) -> DefT (lit_reason r, BoolT_UNSOUND false) |> changed_result
| DefT (r, BoolGeneralT) -> DefT (lit_reason r, mk_bool_literal cx false) |> changed_result
| t -> unchanged_result t

let false_ t =
let false_ cx t =
let lit_reason = replace_desc_new_reason (RBooleanLit false) in
match t with
| DefT (r, SingletonBoolT false) -> DefT (lit_reason r, SingletonBoolT false) |> unchanged_result
| DefT (r, BoolT_UNSOUND false) -> DefT (lit_reason r, BoolT_UNSOUND false) |> unchanged_result
| DefT (r, BoolGeneralT) -> DefT (lit_reason r, BoolT_UNSOUND false) |> changed_result
| DefT (r, MixedT _) -> DefT (lit_reason r, BoolT_UNSOUND false) |> changed_result
| DefT (r, BoolGeneralT) -> DefT (lit_reason r, mk_bool_literal cx false) |> changed_result
| DefT (r, MixedT _) -> DefT (lit_reason r, mk_bool_literal cx false) |> changed_result
| AnyT _ as t -> unchanged_result t
| t -> DefT (reason_of_t t, EmptyT) |> changed_result

let not_false t =
let not_false cx t =
let lit_reason = replace_desc_new_reason (RBooleanLit true) in
match t with
| DefT (r, SingletonBoolT false)
| DefT (r, BoolT_UNSOUND false) ->
DefT (r, EmptyT) |> changed_result
| DefT (r, BoolGeneralT) -> DefT (lit_reason r, BoolT_UNSOUND true) |> changed_result
| DefT (r, BoolGeneralT) -> DefT (lit_reason r, mk_bool_literal cx true) |> changed_result
| t -> unchanged_result t

let boolean loc t =
let boolean cx loc t =
match t with
| DefT (r, MixedT Mixed_truthy) ->
DefT (replace_desc_new_reason BoolModuleT.desc r, BoolT_UNSOUND true) |> changed_result
DefT (replace_desc_new_reason BoolModuleT.desc r, mk_bool_literal cx true) |> changed_result
| AnyT _
| DefT (_, MixedT _) ->
DefT (mk_reason RBoolean loc, BoolGeneralT) |> changed_result
Expand Down
16 changes: 8 additions & 8 deletions src/typing/type_filter.mli
Original file line number Diff line number Diff line change
Expand Up @@ -31,27 +31,27 @@ val undefined : Type.t -> filter_result

val not_undefined : Context.t -> Type.t -> filter_result

val string_literal : ALoc.t -> bool -> Reason.name -> Type.t -> filter_result
val string_literal : Context.t -> ALoc.t -> bool -> Reason.name -> Type.t -> filter_result

val not_string_literal : Reason.name -> Type.t -> filter_result

val number_literal : ALoc.t -> bool -> Type.number_literal -> Type.t -> filter_result
val number_literal : Context.t -> ALoc.t -> bool -> Type.number_literal -> Type.t -> filter_result

val not_number_literal : Type.number_literal -> Type.t -> filter_result

val bigint_literal : ALoc.t -> bool -> Type.bigint_literal -> Type.t -> filter_result
val bigint_literal : Context.t -> ALoc.t -> bool -> Type.bigint_literal -> Type.t -> filter_result

val not_bigint_literal : Type.bigint_literal -> Type.t -> filter_result

val true_ : Type.t -> filter_result
val true_ : Context.t -> Type.t -> filter_result

val not_true : Type.t -> filter_result
val not_true : Context.t -> Type.t -> filter_result

val false_ : Type.t -> filter_result
val false_ : Context.t -> Type.t -> filter_result

val not_false : Type.t -> filter_result
val not_false : Context.t -> Type.t -> filter_result

val boolean : ALoc.t -> Type.t -> filter_result
val boolean : Context.t -> ALoc.t -> Type.t -> filter_result

val not_boolean : Type.t -> filter_result

Expand Down

0 comments on commit 9eccbcd

Please sign in to comment.