Skip to content

Commit 82774c5

Browse files
panagosg7meta-codesync[bot]
authored andcommitted
[flow] Fast path for large union element access on object types
Summary: When resolving `T[K]` where `K = k₁ | k₂ | ... | kₙ` (keyof T) and T is an object type, Flow distributes the union and creates n individual GetPropT constraint flows. Add a fast path in flow_js.ml for `UnionT + ElemT { ObjT, ReadElem }` when the object has no dict/indexer and the union has >100 members. Instead of distributing through GetPropT, directly look up each key in the property map (O(1) hash lookup per key) and collect value types into a union. This bypasses GetPropT semantics (skip_optional, no_unchecked_indexed_access) so we only apply it for large unions where the perf cost of distribution is significant. Falls back to the standard distribution path if any key can't be resolved via direct lookup. Changelog: [internal] Reviewed By: SamChou19815 Differential Revision: D94949645 fbshipit-source-id: 47ead9cf27091d95f304d5c5b3bef0427e5f0178
1 parent 86da4b2 commit 82774c5

3 files changed

Lines changed: 74 additions & 11 deletions

File tree

src/typing/flow_js.ml

Lines changed: 65 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,26 @@ struct
310310

311311
module GetPropTKit = GetPropT_kit (Get_prop_helper)
312312

313+
(* Look up a key's value type directly in a property map. Returns Some type_ if the
314+
* key is a string literal, the property exists, and is readable; None otherwise.
315+
* Applies react_dro wrapping if needed. *)
316+
let lookup_prop_type_direct cx ~use_op ~react_dro props key_type =
317+
match TypeUtil.name_of_singleton_string_type key_type with
318+
| Some name ->
319+
(match NameUtils.Map.find_opt name props with
320+
| Some prop ->
321+
(match Property.type_ prop with
322+
| OrdinaryField { type_; polarity } when Polarity.compat (polarity, Polarity.Positive) ->
323+
let type_ =
324+
match react_dro with
325+
| Some dro -> mk_react_dro cx (Frame (ReactDeepReadOnly dro, use_op)) dro type_
326+
| None -> type_
327+
in
328+
Some type_
329+
| _ -> None)
330+
| None -> None)
331+
| None -> None
332+
313333
(** NOTE: Do not call this function directly. Instead, call the wrapper
314334
functions `rec_flow`, `join_flow`, or `flow_opt` (described below) inside
315335
this module, and the function `flow` outside this module. **)
@@ -1698,18 +1718,52 @@ struct
16981718
action = ReadElem { id; from_annot = true; skip_optional; access_iterables; tout };
16991719
}
17001720
) ->
1701-
let reason = update_desc_reason invalidate_rtype_alias reason in
1702-
let (t0, (t1, ts)) = UnionRep.members_nel rep in
1703-
let f t =
1704-
Tvar.mk_no_wrap_where cx reason (fun tvar ->
1705-
let action =
1706-
ReadElem { id; from_annot = true; skip_optional; access_iterables; tout = tvar }
1707-
in
1708-
rec_flow cx trace (t, ElemT { use_op; reason; obj; action })
1709-
)
1721+
let distribute () =
1722+
let reason = update_desc_reason invalidate_rtype_alias reason in
1723+
let (t0, (t1, ts)) = UnionRep.members_nel rep in
1724+
let f t =
1725+
Tvar.mk_no_wrap_where cx reason (fun tvar ->
1726+
let action =
1727+
ReadElem { id; from_annot = true; skip_optional; access_iterables; tout = tvar }
1728+
in
1729+
rec_flow cx trace (t, ElemT { use_op; reason; obj; action })
1730+
)
1731+
in
1732+
let rep = UnionRep.make (f t0) (f t1) (Base.List.map ts ~f) in
1733+
rec_flow_t cx trace ~use_op:unknown_use (UnionT (reason, rep), OpenT tout)
17101734
in
1711-
let rep = UnionRep.make (f t0) (f t1) (Base.List.map ts ~f) in
1712-
rec_flow_t cx trace ~use_op:unknown_use (UnionT (reason, rep), OpenT tout)
1735+
(* Fast path for large Union-of-keys ~> ElemT on an ObjT without a dict.
1736+
* Instead of distributing the union and flowing each key through
1737+
* GetPropT individually (which is O(n) flows for n keys), directly
1738+
* look up each key in the property map (O(1) hash lookup per key)
1739+
* and collect the value types into a union.
1740+
*
1741+
* We only apply this for large unions (>100 members) to avoid adding
1742+
* overhead to the common small-union case. The semantics are equivalent:
1743+
* skip_optional and no_unchecked_indexed_access are irrelevant here since
1744+
* we require no dict and only match properties that exist in the prop map. *)
1745+
begin
1746+
match obj with
1747+
| DefT (_, ObjT { props_tmap; flags = { obj_kind; react_dro; _ }; _ })
1748+
when Obj_type.get_dict_opt obj_kind = None && List.length (UnionRep.members rep) > 100
1749+
->
1750+
let props = Context.find_props cx props_tmap in
1751+
let members = UnionRep.members rep in
1752+
let value_types =
1753+
Base.List.filter_map members ~f:(lookup_prop_type_direct cx ~use_op ~react_dro props)
1754+
in
1755+
if List.length value_types = List.length members then
1756+
let reason = update_desc_reason invalidate_rtype_alias reason in
1757+
match value_types with
1758+
| [] -> ()
1759+
| [t] -> rec_flow_t cx trace ~use_op:unknown_use (t, OpenT tout)
1760+
| t0 :: t1 :: ts ->
1761+
let rep = UnionRep.make t0 t1 ts in
1762+
rec_flow_t cx trace ~use_op:unknown_use (UnionT (reason, rep), OpenT tout)
1763+
else
1764+
distribute ()
1765+
| _ -> distribute ()
1766+
end
17131767
| (UnionT (_, rep), _)
17141768
when match u with
17151769
| WriteComputedObjPropCheckT _ -> false

src/typing/typeUtil.ml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -887,6 +887,13 @@ let reason_of_resolved_param = function
887887
| ResolvedArg (TupleElement { reason; _ }, _) ->
888888
reason
889889

890+
(* Extract the property name from a key type that is a SingletonStrT,
891+
* possibly wrapped in a GenericT. Returns None for non-string-literal keys. *)
892+
let name_of_singleton_string_type = function
893+
| DefT (_, SingletonStrT { value = name; _ }) -> Some name
894+
| GenericT { bound = DefT (_, SingletonStrT { value = name; _ }); _ } -> Some name
895+
| _ -> None
896+
890897
let dro_of_type t =
891898
match t with
892899
| DefT

src/typing/typeUtil.mli

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,8 @@ val reason_of_resolved_param : Type.resolved_param -> reason
127127

128128
val normalize_jsx_children_prop : ALoc.t -> Type.t list -> Type.t option
129129

130+
val name_of_singleton_string_type : Type.t -> Reason.name option
131+
130132
val dro_of_type : Type.t -> Type.react_dro option
131133

132134
val map_property : f:(Type.t -> Type.t) -> Type.property_type -> Type.property_type

0 commit comments

Comments
 (0)