Skip to content

Commit 8bad22f

Browse files
SamChou19815meta-codesync[bot]
authored andcommitted
[flow] Avoid crashing when we are doing any substitution for generic conditional types
Summary: In generic conditional type, we have a step to replace all GenericT with any, to see whether the check will still fail (so that we can always take the false branch). During the substitution, we might encounter `Subst_name.Synthetic` that are otherwise unreachable, since during conditional type evaluation, we can see through the `OpenT` for `check_t`. We should replace these synthetic `GenericT` that's supposed to represent generic `EvalT` results with stuck `EvalT` backed by opaque types. However, that's a more complicated fix. For now, I will just did a hole for the generic conditional type use case to avoid the crash. Changelog: [fix] Fixed a potential crash when evaluating a conditional type. [example](https://flow.org/try/#1N4Igxg9gdgZglgcxALlAIwIZoKYBsD6uEEAztvhgE6UYCe+JADpdhgCYowa5kA0I2KAFcAtiRQAXSkOz9sADwxgJ+NPTbYuQ3BMnTZA+Y2yU4IwRO4A6SFBIrGVDGM7c+h46fNRLuKxJIGWh8MeT0ZfhYlCStpHzNsFBAMIQkIEQwJODAQfiEyfBE4eWw2fDgofDBMsAALfAA3KjgsXGxxZC4eAw0G-GhcWn9aY3wWZldu-g1mbGqJUoBaCRHEzrcDEgBrbAk62kXhXFxJ923d-cPRHEpTgyEoMDaqZdW7vKgoOfaSKgOKpqmDA+d4gB5fMA-P6LCCMLLQbiLOoYCqgh6-GDYRYIXYLSgkRZkCR4jpddwPfJLZjpOBkUEKTwJEJ+DAkMiUFSwkyZCC3dbdAC+-EgGiSK2MAAIAHLQADycLgCNwAGlsLQSAAeAAqAD4JQBeCUAEgAatwZJrgAAdKASiUAbR2tAlFQlTogMAlWoAusgJQ0IHA2BKFAsoGwSF7HWrvRKAPwhkRw51+p28G0CnUAbht4uwEtlaAAVnMJAB1OASWoyqDy+FQbiq2javWG622h1Ol22mt1xUNlVqzW631Rp3e9NQAU5nyrCUAQSgECrJkLJeU2rXpdbxoASqw2LKoIMNe27VYL1vlBWq72FUqm6ebXa7dHna73Z6tVeJKPv8XSzfCdnwlTNJ0zLNchABoTBIfskgyewTBAAUgA) Reviewed By: panagosg7 Differential Revision: D84923742 fbshipit-source-id: f5493405c7a480792e2a6144ab75c494c7a6bc63
1 parent 2225660 commit 8bad22f

5 files changed

Lines changed: 96 additions & 27 deletions

File tree

src/typing/flow_js.mli

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,13 @@ val resolve_spread_list :
5959
(* polymorphism *)
6060

6161
val subst :
62-
Context.t -> ?use_op:Type.use_op -> ?force:bool -> Type.t Subst_name.Map.t -> Type.t -> Type.t
62+
Context.t ->
63+
?use_op:Type.use_op ->
64+
?force:bool ->
65+
?purpose:Type_subst.Purpose.t ->
66+
Type.t Subst_name.Map.t ->
67+
Type.t ->
68+
Type.t
6369

6470
(* destructors *)
6571

src/typing/implicit_instantiation.ml

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1763,10 +1763,20 @@ module Kit (FlowJs : Flow_common.S) (Instantiation_helper : Flow_js_utils.Instan
17631763
)
17641764
in
17651765
let any_substituted_check_t =
1766-
Type_subst.subst cx ~use_op:unknown_use any_subst_map check_t
1766+
Type_subst.subst
1767+
cx
1768+
~use_op:unknown_use
1769+
~purpose:Type_subst.Purpose.ConditionalTypeAnySubst
1770+
any_subst_map
1771+
check_t
17671772
in
17681773
let any_substituted_extends_t =
1769-
Type_subst.subst cx ~use_op:unknown_use any_subst_map extends_t
1774+
Type_subst.subst
1775+
cx
1776+
~use_op:unknown_use
1777+
~purpose:Type_subst.Purpose.ConditionalTypeAnySubst
1778+
any_subst_map
1779+
extends_t
17701780
in
17711781
(match
17721782
SpeculationKit.try_singleton_throw_on_failure

src/typing/type_subst.ml

Lines changed: 56 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -211,9 +211,20 @@ class virtual ['a] type_subst_base =
211211
Context.make_export_map cx exps'
212212
end
213213

214+
module Purpose = struct
215+
type t =
216+
| Normal
217+
(* In generic conditional type, we have a step to replace all GenericT with any, to see whether
218+
* the check will still fail (so that we can always take the false branch).
219+
* During the substitution, we might encounter `Subst_name.Synthetic`. We shouldn't crash
220+
* over there. Instead, we should keep them as is. *)
221+
| ConditionalTypeAnySubst
222+
end
223+
214224
let substituter =
215225
object (self)
216-
inherit [replacement Subst_name.Map.t * bool * bool * use_op option] type_subst_base as super
226+
inherit
227+
[replacement Subst_name.Map.t * bool * bool * Purpose.t * use_op option] type_subst_base as super
217228

218229
val mutable change_id = false
219230

@@ -223,14 +234,17 @@ let substituter =
223234
val mutable obj_reachable_targs = None
224235

225236
method! type_ cx map_cx t =
226-
let (map, force, placeholder_no_infer, use_op) = map_cx in
237+
let (map, force, placeholder_no_infer, purpose, use_op) = map_cx in
227238
if Subst_name.Map.is_empty map then
228239
t
229240
else
230241
let t_out =
231242
match t with
232243
| GenericT { name = Subst_name.Synthetic { name; op_kind; ts = ids }; reason; _ } ->
233-
if Base.List.exists ~f:(fun name -> Subst_name.Map.mem name map) ids then
244+
if
245+
purpose = Purpose.Normal
246+
&& Base.List.exists ~f:(fun name -> Subst_name.Map.mem name map) ids
247+
then
234248
failwith
235249
(Utils_js.spf
236250
"Cannot substitute through synthetic name %s(kind=%s) at %s."
@@ -272,14 +286,17 @@ let substituter =
272286
Nel.fold_left
273287
(fun (xs, map, changed) typeparam ->
274288
let bound =
275-
self#type_ cx (map, force, placeholder_no_infer, use_op) typeparam.Type.bound
289+
self#type_
290+
cx
291+
(map, force, placeholder_no_infer, purpose, use_op)
292+
typeparam.Type.bound
276293
in
277294
let default =
278295
match typeparam.default with
279296
| None -> None
280297
| Some default ->
281298
let default_ =
282-
self#type_ cx (map, force, placeholder_no_infer, use_op) default
299+
self#type_ cx (map, force, placeholder_no_infer, purpose, use_op) default
283300
in
284301
if default_ == default then
285302
typeparam.default
@@ -296,7 +313,7 @@ let substituter =
296313
in
297314
let xs = xs |> List.rev |> Nel.of_list in
298315
let xs = Base.Option.value_exn xs in
299-
let inner_ = self#type_ cx (map, false, placeholder_no_infer, None) inner in
316+
let inner_ = self#type_ cx (map, false, placeholder_no_infer, purpose, None) inner in
300317
let changed = changed || inner_ != inner in
301318
let id =
302319
if change_id then
@@ -311,7 +328,9 @@ let substituter =
311328
t
312329
| ThisInstanceT (r, this, i, this_name) ->
313330
let (name, map) = avoid_capture map this_name in
314-
let this_ = self#instance_type cx (map, force, placeholder_no_infer, use_op) this in
331+
let this_ =
332+
self#instance_type cx (map, force, placeholder_no_infer, purpose, use_op) this
333+
in
315334
if this_ == this && name == this_name then
316335
t
317336
else
@@ -352,10 +371,10 @@ let substituter =
352371
mod_reason_of_t (Reason.replace_desc_reason desc) t_out
353372
| _ -> t_out
354373

355-
method! predicate cx (map, force, placeholder_no_infer, use_op) p =
374+
method! predicate cx map_cx p =
356375
match p with
357-
| LatentP _ -> super#predicate cx (map, force, placeholder_no_infer, use_op) p
358-
| LatentThisP _ -> super#predicate cx (map, force, placeholder_no_infer, use_op) p
376+
| LatentP _ -> super#predicate cx map_cx p
377+
| LatentThisP _ -> super#predicate cx map_cx p
359378
| p -> p
360379

361380
method! obj_type cx map_cx t =
@@ -385,27 +404,29 @@ let substituter =
385404
(Some name, map)
386405

387406
method! destructor cx map_cx t =
388-
let (map, _, placeholder_no_infer, _) = map_cx in
407+
let (map, _, placeholder_no_infer, purpose, _) = map_cx in
389408
match t with
390409
| ConditionalType { distributive_tparam_name; infer_tparams; extends_t; true_t; false_t } ->
391410
let (distributive_tparam_name, map) =
392411
self#distributive_tparam_name distributive_tparam_name map
393412
in
394-
let false_t' = self#type_ cx (map, false, placeholder_no_infer, None) false_t in
413+
let false_t' = self#type_ cx (map, false, placeholder_no_infer, purpose, None) false_t in
395414
let (tparams_rev, map, changed) =
396415
Base.List.fold
397416
infer_tparams
398417
~init:([], map, false)
399418
~f:(fun (xs, map, changed) typeparam ->
400419
let bound =
401-
self#type_ cx (map, false, placeholder_no_infer, None) typeparam.Type.bound
420+
self#type_ cx (map, false, placeholder_no_infer, purpose, None) typeparam.Type.bound
402421
in
403422
let (name, map) = avoid_capture map typeparam.name in
404423
({ typeparam with bound; name } :: xs, map, changed || bound != typeparam.bound)
405424
)
406425
in
407-
let extends_t' = self#type_ cx (map, false, placeholder_no_infer, None) extends_t in
408-
let true_t' = self#type_ cx (map, false, placeholder_no_infer, None) true_t in
426+
let extends_t' =
427+
self#type_ cx (map, false, placeholder_no_infer, purpose, None) extends_t
428+
in
429+
let true_t' = self#type_ cx (map, false, placeholder_no_infer, purpose, None) true_t in
409430
if changed || extends_t != extends_t' || true_t != true_t' || false_t != false_t' then
410431
ConditionalType
411432
{
@@ -421,11 +442,13 @@ let substituter =
421442
let (distributive_tparam_name, map) =
422443
self#distributive_tparam_name distributive_tparam_name map
423444
in
424-
let property_type' = self#type_ cx (map, false, placeholder_no_infer, None) property_type in
445+
let property_type' =
446+
self#type_ cx (map, false, placeholder_no_infer, purpose, None) property_type
447+
in
425448
let homomorphic' =
426449
match homomorphic with
427450
| SemiHomomorphic t ->
428-
let t' = self#type_ cx (map, false, placeholder_no_infer, None) t in
451+
let t' = self#type_ cx (map, false, placeholder_no_infer, purpose, None) t in
429452
if t' == t then
430453
homomorphic
431454
else
@@ -451,14 +474,23 @@ let substituter =
451474
method eval_id _cx _map_cx _id = assert false
452475
end
453476

454-
let subst cx ?use_op ?(force = true) ?(placeholder_no_infer = false) map ty =
477+
let subst
478+
cx ?use_op ?(force = true) ?(placeholder_no_infer = false) ?(purpose = Purpose.Normal) map ty =
455479
let map = Subst_name.Map.map (fun t -> TypeSubst (t, free_var_finder cx t)) map in
456-
substituter#type_ cx (map, force, placeholder_no_infer, use_op) ty
480+
substituter#type_ cx (map, force, placeholder_no_infer, purpose, use_op) ty
457481

458-
let subst_destructor cx ?use_op ?(force = true) ?(placeholder_no_infer = false) map des =
482+
let subst_destructor
483+
cx ?use_op ?(force = true) ?(placeholder_no_infer = false) ?(purpose = Purpose.Normal) map des =
459484
let map = Subst_name.Map.map (fun t -> TypeSubst (t, free_var_finder cx t)) map in
460-
substituter#destructor cx (map, force, placeholder_no_infer, use_op) des
461-
462-
let subst_instance_type cx ?use_op ?(force = true) ?(placeholder_no_infer = false) map instance_t =
485+
substituter#destructor cx (map, force, placeholder_no_infer, purpose, use_op) des
486+
487+
let subst_instance_type
488+
cx
489+
?use_op
490+
?(force = true)
491+
?(placeholder_no_infer = false)
492+
?(purpose = Purpose.Normal)
493+
map
494+
instance_t =
463495
let map = Subst_name.Map.map (fun t -> TypeSubst (t, free_var_finder cx t)) map in
464-
substituter#instance_type cx (map, force, placeholder_no_infer, use_op) instance_t
496+
substituter#instance_type cx (map, force, placeholder_no_infer, purpose, use_op) instance_t

src/typing/type_subst.mli

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,11 +69,18 @@ val free_var_finder_in_destructor :
6969

7070
val new_name : Subst_name.t -> Subst_name.Set.t -> Subst_name.t
7171

72+
module Purpose : sig
73+
type t =
74+
| Normal
75+
| ConditionalTypeAnySubst
76+
end
77+
7278
val subst :
7379
Context.t ->
7480
?use_op:Type.use_op ->
7581
?force:bool ->
7682
?placeholder_no_infer:bool ->
83+
?purpose:Purpose.t ->
7784
Type.t Subst_name.Map.t ->
7885
Type.t ->
7986
Type.t
@@ -83,6 +90,7 @@ val subst_destructor :
8390
?use_op:Type.use_op ->
8491
?force:bool ->
8592
?placeholder_no_infer:bool ->
93+
?purpose:Purpose.t ->
8694
Type.t Subst_name.Map.t ->
8795
Type.destructor ->
8896
Type.destructor
@@ -92,6 +100,7 @@ val subst_instance_type :
92100
?use_op:Type.use_op ->
93101
?force:bool ->
94102
?placeholder_no_infer:bool ->
103+
?purpose:Purpose.t ->
95104
Type.t Subst_name.Map.t ->
96105
Type.instance_t ->
97106
Type.instance_t
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// Should not crash!
2+
type NonOptionalKeys<T> = $Values<{
3+
[key in keyof T]: void extends T[key] ? empty : key,
4+
}>;
5+
type ObjectWithNonOptionalKey<T> = {
6+
[key in NonOptionalKeys<T>]: T[key],
7+
};
8+
type AnotherObject<TObject> = $ReadOnly<{
9+
...ObjectWithNonOptionalKey<{
10+
[key in keyof TObject]: TObject[key],
11+
}>,
12+
}>;

0 commit comments

Comments
 (0)