Skip to content

Commit

Permalink
[flow] Singleton types get a with "generalizable" field
Browse files Browse the repository at this point in the history
Summary:
In the following code under natural inference
```
const x1 = "a";
const o1 = {f: x1};
```
we would like to infer
```
x1: "a" // SingletonStrT("a")
o1: {f:string} // ObjT {f: StrGeneralT}
```

At the same time, an annotated version of this code:
```
const x2: "a" = "a";
const o2 = {f: x2};
```
should yield
```
x2: "a" // SingletonStrT("a")
o2: {f:"a"} // ObjT {f: SingletonStrT("a")}
```
Note the difference between `o1` and `o2`. To enable this behavior we need to be able to distinguish between the two types of `SingletonStrT` that we infer for `x1` and `x2`.

The key difference is whether we should allow a `SingletonStrT` to "generalize" to `StrGeneralT`.

Roughly, a string literal type is generalizable iff it does not originate from an annotation, or a value under `as const`, or `Object.freeze()`.

This diff adds a field `from_annot` to `SingletonStrT`, `SingletonNumT`, etc, and populates it accordingly. This field has no impact yet in typechecking behavior. It will only be used later to inform the generalization process.

Changelog: [internal]

Reviewed By: SamChou19815

Differential Revision: D66659579

fbshipit-source-id: 148f2e35de53debc600d17f735dc51cbd771d190
  • Loading branch information
panagosg7 authored and facebook-github-bot committed Mar 6, 2025
1 parent ae4c8c1 commit a1f78dd
Show file tree
Hide file tree
Showing 20 changed files with 235 additions and 167 deletions.
13 changes: 7 additions & 6 deletions src/typing/annotation_inference.ml
Original file line number Diff line number Diff line change
Expand Up @@ -771,11 +771,11 @@ module rec ConsGen : S = struct
BoolModuleT.at (loc_of_reason reason)
(* !x when x is falsy *)
| (DefT (_, BoolT_UNSOUND false), Annot_NotT reason)
| (DefT (_, SingletonBoolT false), Annot_NotT reason)
| (DefT (_, SingletonBoolT { value = false; _ }), Annot_NotT reason)
| (DefT (_, StrT_UNSOUND (_, OrdinaryName "")), Annot_NotT reason)
| (DefT (_, SingletonStrT (OrdinaryName "")), Annot_NotT reason)
| (DefT (_, SingletonStrT { value = OrdinaryName ""; _ }), Annot_NotT reason)
| (DefT (_, NumT_UNSOUND (_, (0., _))), Annot_NotT reason)
| (DefT (_, SingletonNumT (0., _)), Annot_NotT reason)
| (DefT (_, SingletonNumT { value = (0., _); _ }), Annot_NotT reason)
| (DefT (_, NullT), Annot_NotT reason)
| (DefT (_, VoidT), Annot_NotT reason) ->
let reason = replace_desc_reason (RBooleanLit true) reason in
Expand Down Expand Up @@ -1152,11 +1152,12 @@ module rec ConsGen : S = struct
(*****************************)
| (DefT (reason, NumericStrKeyT (_, s)), _) ->
elab_t cx (DefT (reason, StrT_UNSOUND (None, OrdinaryName s))) op
| (DefT (reason, SingletonStrT key), _) ->
| (DefT (reason, SingletonStrT { value = key; _ }), _) ->
elab_t cx (DefT (reason, StrT_UNSOUND (None, key))) op
| (DefT (reason, SingletonNumT lit), _) ->
| (DefT (reason, SingletonNumT { value = lit; _ }), _) ->
elab_t cx (DefT (reason, NumT_UNSOUND (None, lit))) op
| (DefT (reason, SingletonBoolT b), _) -> elab_t cx (DefT (reason, BoolT_UNSOUND b)) op
| (DefT (reason, SingletonBoolT { value = b; _ }), _) ->
elab_t cx (DefT (reason, BoolT_UNSOUND b)) op
| (NullProtoT reason, _) -> elab_t cx (DefT (reason, NullT)) op
(********************)
(* Function Statics *)
Expand Down
10 changes: 6 additions & 4 deletions src/typing/debug_js.ml
Original file line number Diff line number Diff line change
Expand Up @@ -345,10 +345,12 @@ let rec dump_t_ (depth, tvars) cx t =
)
t
| DefT (_, NumericStrKeyT (_, s)) -> p ~extra:s t
| DefT (_, SingletonStrT s) -> p ~extra:(spf "%S" (display_string_of_name s)) t
| DefT (_, SingletonNumT (_, s)) -> p ~extra:s t
| DefT (_, SingletonBoolT b) -> p ~extra:(spf "%B" b) t
| DefT (_, SingletonBigIntT (_, s)) -> p ~extra:s t
| DefT (_, SingletonStrT { from_annot; value = s }) ->
p ~extra:(spf "%S (from_annot=%b)" (display_string_of_name s) from_annot) t
| DefT (_, SingletonNumT { from_annot; value = (_, s) }) ->
p ~extra:(s ^ spf " (from_annot=%b)" from_annot) t
| DefT (_, SingletonBoolT { value = b; _ }) -> p ~extra:(spf "%B" b) t
| DefT (_, SingletonBigIntT { value = (_, s); _ }) -> p ~extra:s t
| NamespaceT { namespace_symbol; values_type; types_tmap } ->
p
t
Expand Down
5 changes: 4 additions & 1 deletion src/typing/env_resolution.ml
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,10 @@ let resolve_hint cx loc hint =
UnionT (mk_reason (RCustom "providers") loc, UnionRep.make t1 t2 ts)
| WriteLocHint (kind, loc) -> Type_env.checked_find_loc_env_write cx kind loc
| StringLiteralType name ->
DefT (mk_reason (RIdentifier (OrdinaryName name)) loc, SingletonStrT (OrdinaryName name))
DefT
( mk_reason (RIdentifier (OrdinaryName name)) loc,
SingletonStrT { from_annot = true; value = OrdinaryName name }
)
| ReactFragmentType ->
Flow_js_utils.ImportExportUtils.get_implicitly_imported_react_type
cx
Expand Down
21 changes: 13 additions & 8 deletions src/typing/flow_js.ml
Original file line number Diff line number Diff line change
Expand Up @@ -1433,16 +1433,16 @@ struct
) ->
(match (drop_generic key, flags.obj_kind) with
(* If we have a literal string and that property exists *)
| (DefT (_, (StrT_UNSOUND (_, x) | SingletonStrT x)), _) when Context.has_prop cx mapr x
->
| (DefT (_, (StrT_UNSOUND (_, x) | SingletonStrT { value = x; _ })), _)
when Context.has_prop cx mapr x ->
()
(* If we have a dictionary, try that next *)
| (_, Indexed { key = expected_key; _ }) ->
rec_flow_t ~use_op cx trace (mod_reason_of_t (Fun.const reason_op) key, expected_key)
| _ ->
let (prop, suggestion) =
match drop_generic key with
| DefT (_, (StrT_UNSOUND (_, prop) | SingletonStrT prop)) ->
| DefT (_, (StrT_UNSOUND (_, prop) | SingletonStrT { value = prop; _ })) ->
(Some prop, prop_typo_suggestion cx [mapr] (display_string_of_name prop))
| _ -> (None, None)
in
Expand All @@ -1461,9 +1461,12 @@ struct
HasOwnPropT
( use_op,
reason_op,
( ( DefT (_, (StrT_UNSOUND (_, x) | SingletonStrT x))
| GenericT { bound = DefT (_, (StrT_UNSOUND (_, x) | SingletonStrT x)); _ } ) as
key
( ( DefT (_, (StrT_UNSOUND (_, x) | SingletonStrT { value = x; _ }))
| GenericT
{
bound = DefT (_, (StrT_UNSOUND (_, x) | SingletonStrT { value = x; _ }));
_;
} ) as key
)
)
) ->
Expand Down Expand Up @@ -3965,7 +3968,9 @@ struct
let r = reason_of_t value_t in
match index with
| None -> NumModuleT.why r
| Some i -> DefT (r, SingletonNumT (float_of_int i, string_of_int i))
| Some i ->
DefT
(r, SingletonNumT { from_annot = true; value = (float_of_int i, string_of_int i) })
in
Slice_utils.mk_mapped_prop_type
~use_op
Expand Down Expand Up @@ -5268,7 +5273,7 @@ struct
suggestion = None;
}
)
| ( DefT (reason, (NumT_UNSOUND (_, (value, _)) | SingletonNumT (value, _))),
| ( DefT (reason, (NumT_UNSOUND (_, (value, _)) | SingletonNumT { value = (value, _); _ })),
WriteComputedObjPropCheckT { reason_key; _ }
) ->
let kind = Flow_intermediate_error_types.InvalidObjKey.kind_of_num_value value in
Expand Down
44 changes: 26 additions & 18 deletions src/typing/flow_js_utils.ml
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,7 @@ let ground_subtype = function
)
when String.starts_with ~prefix:prefix2 prefix1 ->
true
| ( DefT (_, (StrT_UNSOUND (None, OrdinaryName s) | SingletonStrT (OrdinaryName s))),
| ( DefT (_, (StrT_UNSOUND (None, OrdinaryName s) | SingletonStrT { value = OrdinaryName s; _ })),
UseT (_, StrUtilT { reason = _; op = StrPrefix prefix; remainder = None })
)
when String.starts_with ~prefix s ->
Expand All @@ -316,7 +316,7 @@ let ground_subtype = function
)
when String.ends_with ~suffix:suffix2 suffix1 ->
true
| ( DefT (_, (StrT_UNSOUND (None, OrdinaryName s) | SingletonStrT (OrdinaryName s))),
| ( DefT (_, (StrT_UNSOUND (None, OrdinaryName s) | SingletonStrT { value = OrdinaryName s; _ })),
UseT (_, StrUtilT { reason = _; op = StrSuffix suffix; remainder = None })
)
when String.ends_with ~suffix s ->
Expand Down Expand Up @@ -1015,7 +1015,7 @@ let obj_key_mirror cx o reason_op =
in
let map_field key t =
let reason = replace_desc_reason (RStringLit key) reason_op in
map_t (DefT (reason, SingletonStrT key)) t
map_t (DefT (reason, SingletonStrT { from_annot = true; value = key })) t
in
let props_tmap =
Context.find_props cx o.props_tmap
Expand Down Expand Up @@ -2699,8 +2699,12 @@ module GetPropT_kit (F : Get_prop_helper_sig) = struct
add_output cx Error_message.(EInternal (loc, PropRefComputedLiteral));
F.error_type cx trace reason_op
| GenericT
{ bound = DefT (_, (NumT_UNSOUND (_, (value, _)) | SingletonNumT (value, _))); _ }
| DefT (_, (NumT_UNSOUND (_, (value, _)) | SingletonNumT (value, _))) ->
{
bound =
DefT (_, (NumT_UNSOUND (_, (value, _)) | SingletonNumT { value = (value, _); _ }));
_;
}
| DefT (_, (NumT_UNSOUND (_, (value, _)) | SingletonNumT { value = (value, _); _ })) ->
let reason_prop = reason_of_t elem_t in
let kind = Flow_intermediate_error_types.InvalidObjKey.kind_of_num_value value in
add_output cx (Error_message.EObjectComputedPropertyAccess (reason_op, reason_prop, kind));
Expand Down Expand Up @@ -2749,8 +2753,10 @@ let array_elem_check
in
let (can_write_tuple, value, use_op) =
match l with
| DefT (index_reason, (NumT_UNSOUND (_, (float_value, _)) | SingletonNumT (float_value, _))) ->
begin
| DefT
( index_reason,
(NumT_UNSOUND (_, (float_value, _)) | SingletonNumT { value = (float_value, _); _ })
) -> begin
match elements with
| None -> (false, elem_t, use_op)
| Some elements ->
Expand Down Expand Up @@ -2843,19 +2849,21 @@ let array_elem_check
(value, is_tuple, use_op, react_dro)

let propref_for_elem_t = function
| OpaqueT (reason, { super_t = Some (DefT (_, SingletonStrT name)); _ })
| GenericT { bound = DefT (_, (SingletonStrT name | StrT_UNSOUND (_, name))); reason; _ }
| DefT (reason, (SingletonStrT name | StrT_UNSOUND (_, name))) ->
| OpaqueT (reason, { super_t = Some (DefT (_, SingletonStrT { value = name; _ })); _ })
| GenericT
{ bound = DefT (_, (SingletonStrT { value = name; _ } | StrT_UNSOUND (_, name))); reason; _ }
| DefT (reason, (SingletonStrT { value = name; _ } | StrT_UNSOUND (_, name))) ->
let reason = replace_desc_reason (RProperty (Some name)) reason in
mk_named_prop ~reason ~from_indexed_access:true name
| OpaqueT (reason_num, { super_t = Some (DefT (_, SingletonNumT (value, raw))); _ })
| OpaqueT (reason_num, { super_t = Some (DefT (_, SingletonNumT { value = (value, raw); _ })); _ })
| GenericT
{
bound = DefT (_, (NumT_UNSOUND (_, (value, raw)) | SingletonNumT (value, raw)));
bound =
DefT (_, (NumT_UNSOUND (_, (value, raw)) | SingletonNumT { value = (value, raw); _ }));
reason = reason_num;
_;
}
| DefT (reason_num, (NumT_UNSOUND (_, (value, raw)) | SingletonNumT (value, raw)))
| DefT (reason_num, (NumT_UNSOUND (_, (value, raw)) | SingletonNumT { value = (value, raw); _ }))
when Js_number.is_float_safe_integer value ->
let reason = replace_desc_reason (RProperty (Some (OrdinaryName raw))) reason_num in
let name = OrdinaryName (Dtoa.ecma_string_of_float value) in
Expand All @@ -2868,7 +2876,7 @@ let keylist_of_props props reason_op =
match name with
| OrdinaryName _ ->
let reason = replace_desc_new_reason (RStringLit name) reason_op in
DefT (reason, SingletonStrT name) :: acc
DefT (reason, SingletonStrT { from_annot = true; value = name }) :: acc
| InternalName _ -> acc)
props
[]
Expand Down Expand Up @@ -2984,9 +2992,9 @@ let flow_unary_arith cx l reason kind =
| (Minus, DefT (lreason, NumT_UNSOUND (_, lit))) ->
let (reason, lit) = unary_negate_lit ~annot_loc:(loc_of_reason reason) lreason lit in
DefT (reason, NumT_UNSOUND (None, lit))
| (Minus, DefT (lreason, SingletonNumT lit)) ->
| (Minus, DefT (lreason, SingletonNumT { from_annot; value = lit })) ->
let (reason, lit) = unary_negate_lit ~annot_loc:(loc_of_reason reason) lreason lit in
DefT (reason, SingletonNumT lit)
DefT (reason, SingletonNumT { from_annot; value = lit })
| (Minus, DefT (_, NumGeneralT _)) -> l
| (Minus, DefT (_, BigIntT_UNSOUND (_, (value, raw)))) ->
let (value, raw) = Flow_ast_utils.negate_bigint_literal (value, raw) in
Expand Down Expand Up @@ -3330,7 +3338,7 @@ end = struct

let on_concretized_bad_non_element_normalization normalization_cx = function
| DefT (invalid_type_reason, BoolT_UNSOUND false)
| DefT (invalid_type_reason, SingletonBoolT false)
| DefT (invalid_type_reason, SingletonBoolT { value = false; _ })
| DefT (invalid_type_reason, NullT)
| DefT (invalid_type_reason, VoidT) ->
TypeCollector.add normalization_cx.type_collector (AnyT.error normalization_cx.result_reason);
Expand Down Expand Up @@ -3373,7 +3381,7 @@ end = struct
invalid_type_reasons = Nel.one generic_reason;
}
)
| DefT (reason, SingletonStrT (OrdinaryName "svg")) ->
| DefT (reason, SingletonStrT { value = OrdinaryName "svg"; _ }) ->
TypeCollector.add
normalization_cx.type_collector
(DefT (reason, RendersT (InstrinsicRenders "svg")))
Expand Down
2 changes: 1 addition & 1 deletion src/typing/object_kit.ml
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ module Kit (Flow : Flow_common.S) : OBJECT = struct
(fun (keys, indexers) t ->
match t with
| DefT (r, StrT_UNSOUND (_, name))
| DefT (r, SingletonStrT name) ->
| DefT (r, SingletonStrT { value = name; _ }) ->
((name, r) :: keys, indexers)
| DefT (_, EmptyT) -> (keys, indexers)
| _ -> (keys, t :: indexers))
Expand Down
46 changes: 23 additions & 23 deletions src/typing/predicate_kit.ml
Original file line number Diff line number Diff line change
Expand Up @@ -545,16 +545,16 @@ and intersect =
* handling a few cases here explicitly. *)
let ground_types_differ t1 t2 =
match (C.unwrap t1, C.unwrap t2) with
| ( DefT (_, (SingletonStrT v1 | StrT_UNSOUND (_, v1))),
DefT (_, (SingletonStrT v2 | StrT_UNSOUND (_, v2)))
| ( DefT (_, (SingletonStrT { value = v1; _ } | StrT_UNSOUND (_, v1))),
DefT (_, (SingletonStrT { value = v2; _ } | StrT_UNSOUND (_, v2)))
) ->
v1 <> v2
| ( DefT (_, (SingletonNumT v1 | NumT_UNSOUND (_, v1))),
DefT (_, (SingletonNumT v2 | NumT_UNSOUND (_, v2)))
| ( DefT (_, (SingletonNumT { value = v1; _ } | NumT_UNSOUND (_, v1))),
DefT (_, (SingletonNumT { value = v2; _ } | NumT_UNSOUND (_, v2)))
) ->
v1 <> v2
| ( DefT (_, (SingletonBoolT v1 | BoolT_UNSOUND v1)),
DefT (_, (SingletonBoolT v2 | BoolT_UNSOUND v2))
| ( DefT (_, (SingletonBoolT { value = v1; _ } | BoolT_UNSOUND v1)),
DefT (_, (SingletonBoolT { value = v2; _ } | BoolT_UNSOUND v2))
) ->
v1 <> v2
| (_, _) -> false
Expand Down Expand Up @@ -1146,16 +1146,16 @@ and sentinel_prop_test_generic key cx trace result_collector orig_obj =
in
let sentinel_of_literal = function
| DefT (_, StrT_UNSOUND (_, value))
| DefT (_, SingletonStrT value) ->
| DefT (_, SingletonStrT { value; _ }) ->
Some UnionEnum.(One (Str value))
| DefT (_, NumT_UNSOUND (_, value))
| DefT (_, SingletonNumT value) ->
| DefT (_, SingletonNumT { value; _ }) ->
Some UnionEnum.(One (Num value))
| DefT (_, BoolT_UNSOUND value)
| DefT (_, SingletonBoolT value) ->
| DefT (_, SingletonBoolT { value; _ }) ->
Some UnionEnum.(One (Bool value))
| DefT (_, BigIntT_UNSOUND (_, value))
| DefT (_, SingletonBigIntT value) ->
| DefT (_, SingletonBigIntT { value; _ }) ->
Some UnionEnum.(One (BigInt value))
| DefT (_, VoidT) -> Some UnionEnum.(One Void)
| DefT (_, NullT) -> Some UnionEnum.(One Null)
Expand Down Expand Up @@ -1223,10 +1223,10 @@ and concretize_and_run_sentinel_prop_test
| UnionEnum.One enum ->
let def =
match enum with
| UnionEnum.Str v -> SingletonStrT v
| UnionEnum.Num v -> SingletonNumT v
| UnionEnum.Bool v -> SingletonBoolT v
| UnionEnum.BigInt v -> SingletonBigIntT v
| UnionEnum.Str v -> SingletonStrT { from_annot = true; value = v }
| UnionEnum.Num v -> SingletonNumT { from_annot = true; value = v }
| UnionEnum.Bool v -> SingletonBoolT { from_annot = true; value = v }
| UnionEnum.BigInt v -> SingletonBigIntT { from_annot = true; value = v }
| UnionEnum.Void -> VoidT
| UnionEnum.Null -> NullT
in
Expand Down Expand Up @@ -1258,10 +1258,10 @@ and concretize_and_run_sentinel_prop_test
(fun enum acc ->
let def =
match enum with
| UnionEnum.Str v -> SingletonStrT v
| UnionEnum.Num v -> SingletonNumT v
| UnionEnum.Bool v -> SingletonBoolT v
| UnionEnum.BigInt v -> SingletonBigIntT v
| UnionEnum.Str v -> SingletonStrT { from_annot = true; value = v }
| UnionEnum.Num v -> SingletonNumT { from_annot = true; value = v }
| UnionEnum.Bool v -> SingletonBoolT { from_annot = true; value = v }
| UnionEnum.BigInt v -> SingletonBigIntT { from_annot = true; value = v }
| UnionEnum.Void -> VoidT
| UnionEnum.Null -> NullT
in
Expand Down Expand Up @@ -1311,7 +1311,7 @@ and eq_test cx _trace result_collector (sense, left, right) =
let expected_loc = loc_of_t right in
match right with
| DefT (_, StrT_UNSOUND (_, value))
| DefT (_, SingletonStrT value) ->
| DefT (_, SingletonStrT { value; _ }) ->
let filtered =
if sense then
Type_filter.string_literal cx expected_loc sense value left
Expand All @@ -1320,7 +1320,7 @@ and eq_test cx _trace result_collector (sense, left, right) =
in
report_filtering_result_to_predicate_result filtered result_collector
| DefT (_, NumT_UNSOUND (_, value))
| DefT (_, SingletonNumT value) ->
| DefT (_, SingletonNumT { value; _ }) ->
let filtered =
if sense then
Type_filter.number_literal cx expected_loc sense value left
Expand All @@ -1329,7 +1329,7 @@ and eq_test cx _trace result_collector (sense, left, right) =
in
report_filtering_result_to_predicate_result filtered result_collector
| DefT (_, BoolT_UNSOUND true)
| DefT (_, SingletonBoolT true) ->
| DefT (_, SingletonBoolT { value = true; _ }) ->
let filtered =
if sense then
Type_filter.true_ cx left
Expand All @@ -1338,7 +1338,7 @@ and eq_test cx _trace result_collector (sense, left, right) =
in
report_filtering_result_to_predicate_result filtered result_collector
| DefT (_, BoolT_UNSOUND false)
| DefT (_, SingletonBoolT false) ->
| DefT (_, SingletonBoolT { value = false; _ }) ->
let filtered =
if sense then
Type_filter.false_ cx left
Expand All @@ -1347,7 +1347,7 @@ and eq_test cx _trace result_collector (sense, left, right) =
in
report_filtering_result_to_predicate_result filtered result_collector
| DefT (_, BigIntT_UNSOUND (_, value))
| DefT (_, SingletonBigIntT value) ->
| DefT (_, SingletonBigIntT { value; _ }) ->
let filtered =
if sense then
Type_filter.bigint_literal cx expected_loc sense value left
Expand Down
Loading

0 comments on commit a1f78dd

Please sign in to comment.