Skip to content

Commit

Permalink
[flow] Normalize React.AbstractComponent to the new component type,…
Browse files Browse the repository at this point in the history
… with best effort flattening

Summary:
We want to one day kill `React.AbstractComponent`, since it's instance targ is not in-line with react 19's model of ref as a prop. People learn what types to write from hover, so it's important that we stop printing out these types.

In this diff, we star to normalize React.AbstractComponent to the new component type, which is enabled regardless of whether component syntax is enabled. We have the following baseline strategy:

1. Props go into `component(...Props)`
2. Instance go into `component(ref: React.RefSetter<Instance>)`
3. Renders go into `component() renders Renders`

For props, we also do some best effort flattening if the props have an object type that we can see in normalizer. For exact objects, it's straightforward; for inexact objects, we add `...{...}` at the end. We bail out on things like getters, methods, etc.

For renders, we won't repeat the `render` keyword if the `Renders` already has a render type.

Changelog: [ide] On hover, values that have `React.AbstractComponent` type will be shown in the [component type](https://flow.org/en/docs/react/component-types/) syntax.

Reviewed By: jbrown215

Differential Revision: D62917950

fbshipit-source-id: 2464a9baf253219ed2dfb90e6ae9dc0bf6b79687
  • Loading branch information
SamChou19815 authored and facebook-github-bot committed Sep 19, 2024
1 parent c0c9e46 commit dd825da
Show file tree
Hide file tree
Showing 13 changed files with 319 additions and 21 deletions.
24 changes: 23 additions & 1 deletion src/common/ty/ty.ml
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,11 @@ type t =
false_type: t;
}
| Infer of symbol * t option
| Component of {
props: component_props;
instance: t option;
renders: t;
}
| Renders of t * renders_kind

(* Recursive variable *)
Expand Down Expand Up @@ -294,6 +299,21 @@ and utility =
| ReactCheckComponentRef of t
| ReactConfigType of t * t

and component_props =
| UnflattenedComponentProps of t
| FlattenedComponentProps of {
props: flattened_component_prop list;
inexact: bool;
}

and flattened_component_prop =
| FlattenedComponentProp of {
name: Reason.name;
optional: bool;
def_locs: aloc list;
t: t;
}

and renders_kind =
| RendersNormal
| RendersMaybe
Expand Down Expand Up @@ -580,7 +600,8 @@ class ['A] comparator_ty =
| InlineInterface _ -> 26
| Conditional _ -> 27
| Infer _ -> 28
| Renders _ -> 29
| Component _ -> 29
| Renders _ -> 30

method tag_of_decl _ =
function
Expand Down Expand Up @@ -806,6 +827,7 @@ let mk_exact ty =
| Tup _
| InlineInterface _
| Infer _
| Component _
| Renders _ ->
ty
(* Do not nest $Exact *)
Expand Down
66 changes: 66 additions & 0 deletions src/common/ty/ty_debug.ml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ let string_of_ctor_t = function
| IndexedAccess _ -> "IndexedAccess"
| Conditional _ -> "Conditional"
| Infer _ -> "Infer"
| Component _ -> "Component"
| Renders _ -> "Renders"

let string_of_ctor_decl = function
Expand Down Expand Up @@ -387,6 +388,37 @@ struct
"Infer (%s, %s)"
(dump_symbol s)
(Base.Option.value_map ~default:"None" ~f:(dump_t ~depth) b)
| Component { props; instance; renders } ->
let props =
match props with
| UnflattenedComponentProps t -> [spf "...%s" (dump_t ~depth t)]
| FlattenedComponentProps { props; inexact } ->
let props =
Base.List.map
props
~f:(fun (FlattenedComponentProp { name; optional; def_locs = _; t }) ->
spf
"%s%s: %s"
(Reason.display_string_of_name name)
( if optional then
"?"
else
""
)
(dump_t ~depth t)
)
in
if inexact then
props @ ["...{...}"]
else
props
in
let props =
match instance with
| None -> props
| Some t -> spf "ref: React.RefSetter<%s>" (dump_t ~depth t) :: props
in
spf "Conditional(%s): %s" (Base.String.concat ~sep:", " props) (dump_t ~depth renders)
| Renders (t, _) -> spf "Renders (%s)" (dump_t ~depth t)

and dump_class_decl ~depth (name, ps) =
Expand Down Expand Up @@ -561,6 +593,40 @@ struct
("name", json_of_symbol s);
("bound", Base.Option.value_map ~default:JSON_Null ~f:json_of_t b);
]
| Component { props; instance; renders } ->
let props =
match props with
| UnflattenedComponentProps t ->
JSON_Object [("kind", JSON_String "unflattened"); ("type", json_of_t t)]
| FlattenedComponentProps { props; inexact } ->
let props =
Base.List.map
props
~f:(fun (FlattenedComponentProp { name; optional; def_locs; t }) ->
JSON_Object
[
("name", JSON_String (Reason.display_string_of_name name));
("optional", JSON_Bool optional);
("type", json_of_t t);
( "def_locs",
JSON_Array
(Base.List.map def_locs ~f:(fun loc -> JSON_String (string_of_aloc loc)))
);
]
)
in
JSON_Object
[
("kind", JSON_String "flattened");
("types", JSON_Array props);
("inexact", JSON_Bool inexact);
]
in
[
("props", props);
("instance", Base.Option.value_map instance ~f:json_of_t ~default:JSON_Null);
("renders", json_of_t renders);
]
| Renders (t, variant) ->
[
("argument", json_of_t t);
Expand Down
60 changes: 60 additions & 0 deletions src/common/ty/ty_printer.ml
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,66 @@ let layout_of_elt ~prefer_single_quotes ?(size = 5000) ?(with_comments = true) ~
option ~f:(fun t -> fuse [space; Atom "extends"; space; type_ ~depth t]) b;
];
]
| Component { props; instance; renders } ->
let to_key x =
if property_key_quotes_needed x then
let quote = better_quote ~prefer_single_quotes x in
fuse [Atom quote; Atom (utf8_escape ~quote x); Atom quote]
else
identifier (Reason.OrdinaryName x)
in
let params =
match props with
| UnflattenedComponentProps t -> [fuse [Atom "..."; type_ ~depth t]]
| FlattenedComponentProps { props; inexact } ->
let params =
Base.List.map
props
~f:(fun (FlattenedComponentProp { name; optional; def_locs = _; t }) ->
fuse
[
to_key (Reason.display_string_of_name name);
( if optional then
Atom "?"
else
Empty
);
Atom ":";
pretty_space;
type_ ~depth t;
]
)
in
if inexact then
params @ [Atom "...{...}"]
else
params
in
let params =
match instance with
| None -> params
| Some t ->
fuse
[
Atom "ref:";
pretty_space;
Atom "React.RefSetter";
list ~wrap:(Atom "<", Atom ">") ~sep:(Atom ",") [type_ ~depth t];
]
:: params
in
let renders =
match renders with
| Renders _ -> type_ ~depth renders
| t -> fuse [Atom "renders"; space; type_with_parens ~depth t]
in
fuse
[
Atom "component";
list ~wrap:(Atom "(", Atom ")") ~sep:(Atom ",") ~trailing:false params;
space;
renders;
]
| Renders (t, variant) ->
let renders_str =
match variant with
Expand Down
99 changes: 91 additions & 8 deletions src/common/ty/ty_serializer.ml
Original file line number Diff line number Diff line change
Expand Up @@ -182,15 +182,98 @@ let type_ options =
| TypeOf (FunProto, _) -> just (qualified2 "Object" "prototype")
| TypeOf (ObjProto, _) -> just (qualified2 "Function" "prototype")
| TypeOf (FunProtoBind, _) -> just (qualified3 "Function" "prototype" "bind")
| Renders (t, kind) ->
let argument = type_ t in
let variant =
match kind with
| RendersNormal -> T.Renders.Normal
| RendersMaybe -> T.Renders.Maybe
| RendersStar -> T.Renders.Star
| Component { props; instance; renders = renders_ } ->
let all_params =
match props with
| UnflattenedComponentProps t ->
let rest_param =
{
T.Component.RestParam.argument = None;
annot = type_ t;
optional = false;
comments = None;
}
in
{ T.Component.Params.params = []; rest = Some (just rest_param); comments = None }
| FlattenedComponentProps { props; inexact } ->
let params =
Base.List.map
props
~f:(fun (FlattenedComponentProp { name; optional; def_locs = _; t }) ->
let name =
let x = Reason.show_name name in
if Ty_printer.property_key_quotes_needed x then
let quote = Ty_printer.better_quote ~prefer_single_quotes:false x in
let raw = quote ^ Ty_printer.utf8_escape ~quote x ^ quote in
Ast.Statement.ComponentDeclaration.Param.StringLiteral
(Loc.none, { Ast.StringLiteral.value = x; raw; comments = None })
else
Ast.Statement.ComponentDeclaration.Param.Identifier (id_from_string x)
in
let annot = annotation t in
just { T.Component.Param.name; optional; annot }
)
in
let rest =
if inexact then
Some
(just
{
T.Component.RestParam.argument = None;
annot =
just
(T.Object
{
T.Object.exact = false;
inexact = true;
properties = [];
comments = None;
}
);
optional = false;
comments = None;
}
)
else
None
in
{ T.Component.Params.params; rest; comments = None }
in
just (T.Renders { T.Renders.operator_loc = Loc.none; argument; comments = None; variant })
let all_params =
match instance with
| None -> all_params
| Some t ->
let ref_prop =
just
{
T.Component.Param.name =
Ast.Statement.ComponentDeclaration.Param.Identifier (id_from_string "ref");
optional = false;
annot =
just
(mk_generic_type (id_from_string "React.RefSetter") (Some (mk_targs [type_ t])));
}
in
let params = ref_prop :: all_params.T.Component.Params.params in
{ all_params with T.Component.Params.params }
in
let params = just all_params in
let renders =
match renders_ with
| Renders (t, kind) -> T.AvailableRenders (Loc.none, renders t kind)
| _ -> T.AvailableRenders (Loc.none, renders t RendersNormal)
in
just (T.Component { T.Component.tparams = None; params; renders; comments = None })
| Renders (t, kind) -> just (T.Renders (renders t kind))
and renders t kind =
let argument = type_ t in
let variant =
match kind with
| RendersNormal -> T.Renders.Normal
| RendersMaybe -> T.Renders.Maybe
| RendersStar -> T.Renders.Star
in
{ T.Renders.operator_loc = Loc.none; argument; comments = None; variant }
and generic x targs =
let id = id_from_symbol x in
let targs = Base.Option.map ~f:type_arguments targs in
Expand Down
1 change: 1 addition & 0 deletions src/services/autocomplete/autocompleteService_js.ml
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ let lsp_completion_of_type =
| Utility _
| IndexedAccess _
| Conditional _
| Component _
| Infer _
| Renders _ ->
Lsp.Completion.Variable
Expand Down
1 change: 1 addition & 0 deletions src/typing/ty_members.ml
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,7 @@ let rec members_of_ty : Ty.t -> Ty.t member_info NameUtils.Map.t * string list =
| Utility _
| IndexedAccess _
| Conditional _
| Component _
| Infer _
| Renders _ ->
(NameUtils.Map.empty, [])
Expand Down
34 changes: 27 additions & 7 deletions src/typing/ty_normalizer.ml
Original file line number Diff line number Diff line change
Expand Up @@ -783,15 +783,35 @@ module Make (I : INPUT) : S = struct
let%bind config = type__ ~env config in
let%bind instance =
match instance with
| ComponentInstanceOmitted _ -> return Ty.Void
| ComponentInstanceAvailable t -> type__ ~env t
| ComponentInstanceOmitted _ -> return None
| ComponentInstanceAvailable t -> type__ ~env t >>| Base.Option.some
in
let%bind renders = type__ ~env renders in
return
(generic_talias
(Ty_symbol.builtin_symbol (Reason.OrdinaryName "React$AbstractComponent"))
(Some [config; instance; renders])
)
let props =
let props_flattened =
match config with
| Ty.Obj
{
Ty.obj_def_loc = _;
obj_literal = Some false | None;
obj_props;
obj_kind = (Ty.ExactObj | Ty.InexactObj) as obj_kind;
} ->
Base.List.fold_result obj_props ~init:[] ~f:(fun acc -> function
| Ty.NamedProp { name; prop = Ty.Field { t; polarity = _; optional }; def_locs; _ }
->
let prop = Ty.FlattenedComponentProp { name; optional; def_locs; t } in
Ok (prop :: acc)
| _ -> Error ()
)
|> Base.Result.map ~f:(fun props -> (props, obj_kind = Ty.InexactObj))
| _ -> Error ()
in
match props_flattened with
| Ok (props, inexact) -> Ty.FlattenedComponentProps { props = List.rev props; inexact }
| Error () -> Ty.UnflattenedComponentProps config
in
return (Ty.Component { props; instance; renders })
| DefT (_, RendersT (InstrinsicRenders n)) -> return (Ty.StrLit (OrdinaryName n))
| DefT (r, RendersT (NominalRenders { renders_id = _; renders_name; _ })) ->
let symbol =
Expand Down
5 changes: 4 additions & 1 deletion tests/autocomplete/autocomplete.exp
Original file line number Diff line number Diff line change
Expand Up @@ -5723,7 +5723,10 @@ Flags: --pretty
{
"result":[
{"name":"ab","type":"number"},
{"name":"ac","type":"React$AbstractComponent<Props, any, React$Node>"}
{
"name":"ac",
"type":"component(ref: React.RefSetter<any>, ...Props) renders React$Node"
}
]
}

Expand Down
2 changes: 1 addition & 1 deletion tests/autocomplete_react/autocomplete_react.exp
Original file line number Diff line number Diff line change
Expand Up @@ -387,7 +387,7 @@ Flags: --pretty
"result":[
{
"name":"AbstractComponent",
"type":"type AbstractComponent<-Config, +Instance = mixed, +Renders: React$Node = React$Node> = React$AbstractComponent<Config, Instance, Renders>"
"type":"type AbstractComponent<-Config, +Instance = mixed, +Renders: React$Node = React$Node> = component(ref: React.RefSetter<Instance>, ...Config) renders Renders"
},
{
"name":"ChildrenArray",
Expand Down
Loading

0 comments on commit dd825da

Please sign in to comment.