Skip to content

Commit

Permalink
[flow][refactor] Extract current non RenderT ~> RenderT handling code…
Browse files Browse the repository at this point in the history
… into renders_kit

Summary:
I want to use this code in some form to power the code action to insert render type declaration automatically. Having it in subtyping_kit complicates things and it's hard to reason about how it interact with other rules. Fortunately, the entire continuous block of the subtyping logic can be moved here in a straightforward way.

It also makes me happy that most of the core render type subtyping logic is in one place now.

Changelog: [internal]

Reviewed By: panagosg7

Differential Revision: D62273377

fbshipit-source-id: bfac305f7dd0806aeeca63a8032a7d98c022a923
  • Loading branch information
SamChou19815 authored and facebook-github-bot committed Sep 6, 2024
1 parent 53eb623 commit d169289
Show file tree
Hide file tree
Showing 3 changed files with 139 additions and 136 deletions.
129 changes: 129 additions & 0 deletions src/typing/renders_kit.ml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,14 @@ module type S = sig
use_op:Type.use_op ->
(Reason.reason * Type.canonical_renders_form) * (Reason.reason * Type.canonical_renders_form) ->
unit

val non_renders_to_renders :
Context.t ->
Type.DepthTrace.t ->
use_op:Type.use_op ->
Type.t ->
Reason.reason * Type.canonical_renders_form ->
unit
end

module Make (Flow : INPUT) : S = struct
Expand Down Expand Up @@ -176,4 +184,125 @@ module Make (Flow : INPUT) : S = struct
explanation = None;
}
)

let try_promote_render_type_from_react_element_type
cx trace ~use_op (elem_reason, opq) (renders_r, upper_renders) =
let possibly_promoted =
match opq with
| {
super_t = Some (DefT (_, ObjT { props_tmap; _ }));
opaque_type_args = (_, _, component_t, _) :: (_ as _targs);
_;
} ->
(match Context.find_monomorphized_component cx props_tmap with
| Some mono_component ->
get_builtin_typeapp cx elem_reason "React$ComponentRenders" [mono_component]
| None ->
(* We only want to promote if this is actually a React of a component, otherwise we want
* to flow the original object to the tout.
*
* We perform a speculative subtyping check and then use ComponentRenders to
* extract the render type of the component. This type gets concretized, and we continue
* with renders subtyping if we get a RendersT from ComponentRenders, otherwise we error,
* as we've already checked for structural compatibility in subtyping kit. *)
let top_abstract_component =
let config = EmptyT.why renders_r in
let instance = MixedT.why renders_r in
let renders = get_builtin_type cx renders_r "React$Node" in
DefT
( renders_r,
ReactAbstractComponentT { config; instance; renders; component_kind = Structural }
)
in
if speculative_subtyping_succeeds cx component_t top_abstract_component then
get_builtin_typeapp cx elem_reason "React$ComponentRenders" [component_t]
else if
speculative_subtyping_succeeds
cx
component_t
(DefT (elem_reason, SingletonStrT (Reason.OrdinaryName "svg")))
then
DefT (elem_reason, RendersT (InstrinsicRenders "svg"))
else
OpaqueT (elem_reason, opq))
| _ -> OpaqueT (elem_reason, opq)
in
let use_op = Frame (RendersCompatibility, use_op) in
possible_concrete_types_for_inspection cx elem_reason possibly_promoted
|> Base.List.iter ~f:(function
| AnyT _ as l -> rec_flow_t cx trace ~use_op (l, DefT (renders_r, RendersT upper_renders))
| DefT (_, RendersT _) as l ->
let l = reposition_reason cx ~trace elem_reason ~use_desc:true l in
let renders_t = DefT (renders_r, RendersT upper_renders) in
rec_flow_t cx trace ~use_op (l, renders_t)
(* We did not successfully promote the React$Element and we have a RendersT on the RHS.
* so this is an error *)
| _ ->
Flow_js_utils.add_output
cx
(Error_message.EIncompatibleWithUseOp
{
reason_lower = elem_reason;
reason_upper = renders_r;
use_op;
explanation = None;
}
)
)

let non_renders_to_renders cx trace ~use_op l (renders_r, upper_renders) =
match (l, upper_renders) with
| ( DefT (_, (NullT | VoidT | BoolT (Some false))),
( StructuralRenders
{ renders_variant = RendersMaybe | RendersStar; renders_structural_type = _ }
| DefaultRenders )
) ->
()
| ( DefT (_, ArrT (ArrayAT { elem_t = t; _ } | TupleAT { elem_t = t; _ } | ROArrayAT (t, _))),
( StructuralRenders { renders_variant = RendersStar; renders_structural_type = _ }
| DefaultRenders )
) ->
rec_flow_t cx trace ~use_op (t, reconstruct_render_type renders_r upper_renders)
(* Try to do structural subtyping. If that fails promote to a render type *)
| (OpaqueT (reason_opaque, ({ opaque_id; _ } as opq)), (InstrinsicRenders _ | NominalRenders _))
when Some opaque_id = Flow_js_utils.builtin_react_element_opaque_id cx ->
try_promote_render_type_from_react_element_type
cx
trace
~use_op
(reason_opaque, opq)
(renders_r, upper_renders)
| ( OpaqueT (reason_opaque, ({ opaque_id; _ } as opq)),
StructuralRenders { renders_variant = _; renders_structural_type = t }
)
when Some opaque_id = Flow_js_utils.builtin_react_element_opaque_id cx ->
if not (speculative_subtyping_succeeds cx l t) then
try_promote_render_type_from_react_element_type
cx
trace
~use_op
(reason_opaque, opq)
(renders_r, upper_renders)
(* given x <: y, x <: renders y. The only case in which this is not true is when `x` is a component reference,
* Foo <: renders Foo fails in that case. Since the RHS is in its canonical form we know that we're safe
* to Flow the LHS to the structural type on the RHS *)
| (l, StructuralRenders { renders_variant = _; renders_structural_type = t }) ->
rec_flow_t cx trace ~use_op:(Frame (RendersCompatibility, use_op)) (l, t)
| (l, DefaultRenders) ->
rec_flow_t
cx
trace
~use_op:(Frame (RendersCompatibility, use_op))
(l, get_builtin_type cx renders_r ~use_desc:true "React$Node")
| (l, _) ->
Flow_js_utils.add_output
cx
(Error_message.EIncompatibleWithUseOp
{
reason_lower = TypeUtil.reason_of_t l;
reason_upper = renders_r;
use_op = Frame (RendersCompatibility, use_op);
explanation = None;
}
)
end
8 changes: 8 additions & 0 deletions src/typing/renders_kit.mli
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,14 @@ module type S = sig
use_op:Type.use_op ->
(Reason.reason * Type.canonical_renders_form) * (Reason.reason * Type.canonical_renders_form) ->
unit

val non_renders_to_renders :
Context.t ->
Type.DepthTrace.t ->
use_op:Type.use_op ->
Type.t ->
Reason.reason * Type.canonical_renders_form ->
unit
end

module Make (_ : INPUT) : S
138 changes: 2 additions & 136 deletions src/typing/subtyping_kit.ml
Original file line number Diff line number Diff line change
Expand Up @@ -602,71 +602,6 @@ module Make (Flow : INPUT) : OUTPUT = struct
flow_to_mutable_child cx trace use_op lit1 t1 t2;
array_flow cx trace use_op lit1 r1 ~index:(index + 1) (ts1, e1, ts2, e2)

let try_promote_render_type_from_react_element_type
cx trace ~use_op (elem_reason, opq) (renders_r, upper_renders) =
let possibly_promoted =
match opq with
| {
super_t = Some (DefT (_, ObjT { props_tmap; _ }));
opaque_type_args = (_, _, component_t, _) :: (_ as _targs);
_;
} ->
(match Context.find_monomorphized_component cx props_tmap with
| Some mono_component ->
get_builtin_typeapp cx elem_reason "React$ComponentRenders" [mono_component]
| None ->
(* We only want to promote if this is actually a React of a component, otherwise we want
* to flow the original object to the tout.
*
* We perform a speculative subtyping check and then use ComponentRenders to
* extract the render type of the component. This type gets concretized, and we continue
* with renders subtyping if we get a RendersT from ComponentRenders, otherwise we error,
* as we've already checked for structural compatibility in subtyping kit. *)
let top_abstract_component =
let config = EmptyT.why renders_r in
let instance = MixedT.why renders_r in
let renders = get_builtin_type cx renders_r "React$Node" in
DefT
( renders_r,
ReactAbstractComponentT { config; instance; renders; component_kind = Structural }
)
in
if speculative_subtyping_succeeds cx component_t top_abstract_component then
get_builtin_typeapp cx elem_reason "React$ComponentRenders" [component_t]
else if
speculative_subtyping_succeeds
cx
component_t
(DefT (elem_reason, SingletonStrT (OrdinaryName "svg")))
then
DefT (elem_reason, RendersT (InstrinsicRenders "svg"))
else
OpaqueT (elem_reason, opq))
| _ -> OpaqueT (elem_reason, opq)
in
let use_op = Frame (RendersCompatibility, use_op) in
possible_concrete_types_for_inspection cx elem_reason possibly_promoted
|> Base.List.iter ~f:(function
| AnyT _ as l -> rec_flow_t cx trace ~use_op (l, DefT (renders_r, RendersT upper_renders))
| DefT (_, RendersT _) as l ->
let l = reposition_reason cx ~trace elem_reason ~use_desc:true l in
let renders_t = DefT (renders_r, RendersT upper_renders) in
rec_flow_t cx trace ~use_op (l, renders_t)
(* We did not successfully promote the React$Element and we have a RendersT on the RHS.
* so this is an error *)
| _ ->
add_output
cx
(Error_message.EIncompatibleWithUseOp
{
reason_lower = elem_reason;
reason_upper = renders_r;
use_op;
explanation = None;
}
)
)

let take_n_from_set n set =
let exception Done in
let result = ref [] in
Expand Down Expand Up @@ -1579,77 +1514,8 @@ module Make (Flow : INPUT) : OUTPUT = struct
rec_flow_t cx trace ~use_op:(Frame (RendersCompatibility, use_op)) (rendersl, rendersu)
| (DefT (reasonl, RendersT r1), DefT (reasonu, RendersT r2)) ->
RendersKit.rec_renders_to_renders cx trace ~use_op ((reasonl, r1), (reasonu, r2))
| ( DefT (_, (NullT | VoidT | BoolT (Some false))),
DefT
( _,
RendersT
( StructuralRenders
{ renders_variant = RendersMaybe | RendersStar; renders_structural_type = _ }
| DefaultRenders )
)
) ->
()
| ( DefT (_, ArrT (ArrayAT { elem_t = t; _ } | TupleAT { elem_t = t; _ } | ROArrayAT (t, _))),
DefT
( _,
RendersT
( StructuralRenders { renders_variant = RendersStar; renders_structural_type = _ }
| DefaultRenders )
)
) ->
rec_flow_t cx trace ~use_op (t, u)
(* Try to do structural subtyping. If that fails promote to a render type *)
| ( OpaqueT (reason_opaque, ({ opaque_id; _ } as opq)),
DefT (renders_r, RendersT ((InstrinsicRenders _ | NominalRenders _) as form))
)
when Some opaque_id = Flow_js_utils.builtin_react_element_opaque_id cx ->
try_promote_render_type_from_react_element_type
cx
trace
~use_op
(reason_opaque, opq)
(renders_r, form)
| ( OpaqueT (reason_opaque, ({ opaque_id; _ } as opq)),
DefT
( renders_r,
RendersT (StructuralRenders { renders_variant = _; renders_structural_type = t } as form)
)
)
when Some opaque_id = Flow_js_utils.builtin_react_element_opaque_id cx ->
if not (speculative_subtyping_succeeds cx l t) then
try_promote_render_type_from_react_element_type
cx
trace
~use_op
(reason_opaque, opq)
(renders_r, form)
(* given x <: y, x <: renders y. The only case in which this is not true is when `x` is a component reference,
* Foo <: renders Foo fails in that case. Since the RHS is in its canonical form we know that we're safe
* to Flow the LHS to the structural type on the RHS *)
| ( l,
DefT
( _renders_reason,
RendersT (StructuralRenders { renders_variant = _; renders_structural_type = t })
)
) ->
rec_flow_t cx trace ~use_op:(Frame (RendersCompatibility, use_op)) (l, t)
| (l, DefT (renders_r, RendersT DefaultRenders)) ->
rec_flow_t
cx
trace
~use_op:(Frame (RendersCompatibility, use_op))
(l, get_builtin_type cx renders_r ~use_desc:true "React$Node")
| (l, DefT (_, RendersT _)) ->
add_output
cx
(Error_message.EIncompatibleWithUseOp
{
reason_lower = reason_of_t l;
reason_upper = reason_of_t u;
use_op = Frame (RendersCompatibility, use_op);
explanation = None;
}
)
| (l, DefT (renders_r, RendersT upper_renders)) ->
RendersKit.non_renders_to_renders cx trace ~use_op l (renders_r, upper_renders)
(* Exiting the renders world *)
| (DefT (r, RendersT (InstrinsicRenders _ | NominalRenders _)), u) ->
let mixed_element = get_builtin_type cx r "React$MixedElement" in
Expand Down

0 comments on commit d169289

Please sign in to comment.