Skip to content

Commit

Permalink
Typed JSX intrinsics
Browse files Browse the repository at this point in the history
Summary:
This adds support to Flow for [userland-pluggable] typed JSX intrinsics.

Out of the box, any intrinsic is typed as `any` (just as all intrinsics are currently typed today). This is important because different apps have different intrinsics (i.e. ReactDOM has things like div/span/table/etc, but ReactNative should have no such intrinsics -- and perhaps may one day have its own set).

The design is fairly straightforward: A user includes a libdef that defines a global `$JSXIntrinsics` object type whose keys represent the names of built-ins and whose value-types represent the type of the ReactComponent for the intrinsic. Hopefully the tests do a good job of showing this in action.

TypeScript did a pretty good job with their design, so this is inspired heavily by that.

Reviewed By: bhosmer

Differential Revision: D2714159

fb-gh-sync-id: c7823fb4e4497e3799f633b831b83cd9b3cfd7d0
  • Loading branch information
jeffmo authored and facebook-github-bot-3 committed Jan 7, 2016
1 parent efc27ec commit e0e44d3
Show file tree
Hide file tree
Showing 27 changed files with 314 additions and 49 deletions.
3 changes: 3 additions & 0 deletions lib/core.js
Original file line number Diff line number Diff line change
Expand Up @@ -608,3 +608,6 @@ declare var exports: any;

/* Commonly available, shared between node and dom */
declare var console: any;

/* JSX Intrinsics */
type $JSXIntrinsics = Object;
85 changes: 48 additions & 37 deletions src/common/flowConfig.ml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ module Opts = struct
esproposal_class_instance_fields: esproposal_feature_mode;
esproposal_class_static_fields: esproposal_feature_mode;
esproposal_decorators: esproposal_feature_mode;
facebook_ignore_fbt: bool;
ignore_non_literal_requires: bool;
moduleSystem: moduleSystem;
module_name_mappers: (Str.regexp * string) list;
Expand Down Expand Up @@ -111,6 +112,7 @@ module Opts = struct
esproposal_class_instance_fields = ESPROPOSAL_WARN;
esproposal_class_static_fields = ESPROPOSAL_WARN;
esproposal_decorators = ESPROPOSAL_WARN;
facebook_ignore_fbt = false;
ignore_non_literal_requires = false;
moduleSystem = Node;
module_name_mappers = [];
Expand Down Expand Up @@ -490,54 +492,64 @@ let parse_excludes config lines =

let file_extension = Str.regexp "^\\(\\.[^ \t]+\\)+$"

let parse_options config lines = Opts.(
let options = Opts.parse config.options lines
|> Opts.define_opt "esproposal.class_instance_fields" Opts.({
let parse_options config lines =
let open Opts in
let options = parse config.options lines
|> define_opt "esproposal.class_instance_fields" {
_initializer = USE_DEFAULT;
flags = [];
optparser = optparse_esproposal_feature_flag ~allow_enable:true;
setter = (fun opts v -> {
opts with esproposal_class_instance_fields = v;
});
})
}

|> Opts.define_opt "esproposal.class_static_fields" Opts.({
|> define_opt "esproposal.class_static_fields" {
_initializer = USE_DEFAULT;
flags = [];
optparser = optparse_esproposal_feature_flag ~allow_enable:true;
setter = (fun opts v -> {
opts with esproposal_class_static_fields = v;
});
})
}

|> Opts.define_opt "esproposal.decorators" Opts.({
|> define_opt "esproposal.decorators" {
_initializer = USE_DEFAULT;
flags = [];
optparser = optparse_esproposal_feature_flag;
setter = (fun opts v -> {
opts with esproposal_decorators = v;
});
})
}

|> Opts.define_opt "log.file" Opts.({
|> define_opt "facebook.ignore_fbt" {
_initializer = USE_DEFAULT;
flags = [];
optparser = optparse_boolean;
setter = (fun opts v -> {
opts with facebook_ignore_fbt = v;
});
}

|> define_opt "log.file" {
_initializer = USE_DEFAULT;
flags = [];
optparser = optparse_filepath;
setter = (fun opts v -> {
opts with log_file = Some v;
});
})
}

|> Opts.define_opt "module.ignore_non_literal_requires" Opts.({
|> define_opt "module.ignore_non_literal_requires" {
_initializer = USE_DEFAULT;
flags = [];
optparser = optparse_boolean;
setter = (fun opts v ->
{opts with ignore_non_literal_requires = v;}
);
})
}

|> Opts.define_opt "module.file_ext" Opts.({
|> define_opt "module.file_ext" {
_initializer = INIT_FN (fun opts -> {
opts with module_file_exts = SSet.empty;
});
Expand All @@ -555,9 +567,9 @@ let parse_options config lines = Opts.(
let module_file_exts = SSet.add v opts.module_file_exts in
{opts with module_file_exts;}
);
})
}

|> Opts.define_opt "module.name_mapper" Opts.({
|> define_opt "module.name_mapper" {
_initializer = USE_DEFAULT;
flags = [ALLOW_DUPLICATE];
optparser = (fun str ->
Expand All @@ -580,9 +592,9 @@ let parse_options config lines = Opts.(
let module_name_mappers = v :: opts.module_name_mappers in
{opts with module_name_mappers;}
);
})
}

|> Opts.define_opt "module.system" Opts.({
|> define_opt "module.system" {
_initializer = USE_DEFAULT;
flags = [];
optparser = optparse_enum [
Expand All @@ -592,9 +604,9 @@ let parse_options config lines = Opts.(
setter = (fun opts v -> {
opts with moduleSystem = v;
});
})
}

|> Opts.define_opt "module.system.node.resolve_dirname" Opts.({
|> define_opt "module.system.node.resolve_dirname" {
_initializer = INIT_FN (fun opts -> {
opts with node_resolver_dirnames = [];
});
Expand All @@ -604,84 +616,83 @@ let parse_options config lines = Opts.(
let node_resolver_dirnames = v :: opts.node_resolver_dirnames in
{opts with node_resolver_dirnames;}
);
})
}

|> Opts.define_opt "munge_underscores" Opts.({
|> define_opt "munge_underscores" {
_initializer = USE_DEFAULT;
flags = [];
optparser = optparse_boolean;
setter = (fun opts v ->
{opts with munge_underscores = v;}
);
})
}

|> Opts.define_opt "server.max_workers" Opts.({
|> define_opt "server.max_workers" {
_initializer = USE_DEFAULT;
flags = [];
optparser = optparse_uint;
setter = (fun opts v ->
{opts with max_workers = v;}
);
})
}

|> Opts.define_opt "strip_root" Opts.({
|> define_opt "strip_root" {
_initializer = USE_DEFAULT;
flags = [];
optparser = optparse_boolean;
setter = (fun opts v ->
{opts with strip_root = v;}
);
})
}

|> Opts.define_opt "suppress_comment" Opts.({
|> define_opt "suppress_comment" {
_initializer = USE_DEFAULT;
flags = [ALLOW_DUPLICATE];
optparser = optparse_regexp;
setter = (fun opts v -> {
opts with suppress_comments = v::(opts.suppress_comments);
});
})
}

|> Opts.define_opt "suppress_type" Opts.({
|> define_opt "suppress_type" {
_initializer = USE_DEFAULT;
flags = [ALLOW_DUPLICATE];
optparser = optparse_string;
setter = (fun opts v -> {
opts with suppress_types = SSet.add v opts.suppress_types;
});
})
}

|> Opts.define_opt "temp_dir" Opts.({
|> define_opt "temp_dir" {
_initializer = USE_DEFAULT;
flags = [];
optparser = optparse_filepath;
setter = (fun opts v -> {
opts with temp_dir = v;
});
})
}

|> Opts.define_opt "traces" Opts.({
|> define_opt "traces" {
_initializer = USE_DEFAULT;
flags = [];
optparser = optparse_uint;
setter = (fun opts v ->
{opts with traces = v;}
);
})
}

|> Opts.define_opt "unsafe.enable_getters_and_setters" Opts.({
|> define_opt "unsafe.enable_getters_and_setters" {
_initializer = USE_DEFAULT;
flags = [];
optparser = optparse_boolean;
setter = (fun opts v ->
{opts with enable_unsafe_getters_and_setters = v;}
);
})
}

|> get_defined_opts
in
{config with options}
)

let assert_version (ln, line) =
try
Expand Down
1 change: 1 addition & 0 deletions src/common/flowConfig.mli
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ module Opts : sig
esproposal_class_instance_fields: esproposal_feature_mode;
esproposal_class_static_fields: esproposal_feature_mode;
esproposal_decorators: esproposal_feature_mode;
facebook_ignore_fbt: bool;
ignore_non_literal_requires: bool;
moduleSystem: moduleSystem;
module_name_mappers: (Str.regexp * string) list;
Expand Down
12 changes: 12 additions & 0 deletions src/typing/debug_js.ml
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,10 @@ and _json_of_use_t_impl json_cx t = Hh_json.(
"type", _json_of_t json_cx t
]

| ReifyTypeT (_, t_out) -> [
"t_out", _json_of_t json_cx t_out
]

| SpecializeT (_, cache, targs, tvar) -> [
"cache", JSON_Bool cache;
"types", JSON_Array (List.map (_json_of_t json_cx) targs);
Expand Down Expand Up @@ -454,6 +458,7 @@ and _json_of_use_t_impl json_cx t = Hh_json.(
"target_module_t", _json_of_t json_cx target_module_t;
"t_out", _json_of_t json_cx t_out;
]
| DebugPrintT _reason -> []
)
)

Expand Down Expand Up @@ -743,9 +748,16 @@ let json_of_t ?(depth=1000) cx t =
let json_cx = { cx; depth; stack = ISet.empty; } in
_json_of_t json_cx t

let json_of_use_t ?(depth=1000) cx use_t =
let json_cx = { cx; depth; stack = ISet.empty; } in
_json_of_use_t json_cx use_t

let jstr_of_t ?(depth=1000) cx t =
Hh_json.json_to_multiline (json_of_t ~depth cx t)

let jstr_of_use_t ?(depth=1000) cx use_t =
Hh_json.json_to_multiline (json_of_use_t ~depth cx use_t)

let json_of_graph ?(depth=1000) cx = Hh_json.(
let entries = IMap.fold (fun id _ entries ->
let json_cx = { cx; depth; stack = ISet.empty; } in
Expand Down
2 changes: 2 additions & 0 deletions src/typing/debug_js.mli
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@

val json_of_t: ?depth:int -> Context.t -> Type.t -> Hh_json.json
val jstr_of_t: ?depth:int -> Context.t -> Type.t -> string
val json_of_use_t: ?depth:int -> Context.t -> Type.use_t -> Hh_json.json
val jstr_of_use_t: ?depth:int -> Context.t -> Type.use_t -> string
val json_of_graph: ?depth:int -> Context.t -> Hh_json.json
val jstr_of_graph: ?depth:int -> Context.t -> string
val json_of_scope: ?depth:int -> Context.t -> Scope.t -> Hh_json.json
Expand Down
23 changes: 23 additions & 0 deletions src/typing/flow_js.ml
Original file line number Diff line number Diff line change
Expand Up @@ -1114,6 +1114,14 @@ let rec __flow cx ((l: Type.t), (u: Type.use_t)) trace =
| l, UseT DestructuringT (reason, t, s) ->
rec_flow_t cx trace (l, eval_selector cx reason t s)

(*************)
(* Debugging *)
(*************)

| (_, DebugPrintT (reason)) ->
let msg = (spf "!!! DebugPrintT: %s" (Debug_js.jstr_of_t cx l)) in
add_error cx [reason, msg];

(************)
(* tainting *)
(************)
Expand Down Expand Up @@ -2393,6 +2401,20 @@ let rec __flow cx ((l: Type.t), (u: Type.use_t)) trace =
| (TypeT(_,l), UseT TypeT(_,u)) ->
rec_unify cx trace l u

| (TypeT(type_reason, l), GetPropT(get_reason, (prop_reason, name), t_out)) ->
(**
* Reify the type, extract the prop from that, then wrap that result in a
* TypeT again.
*)
let prop_t = mk_tvar_where cx get_reason (fun t ->
rec_flow cx trace (l, GetPropT(get_reason, (prop_reason, name), t))
) in
rec_flow cx trace (TypeT (type_reason, mk_typeof_annotation cx ~trace prop_t), T t_out)

| (TypeT(_, l), ReifyTypeT(_, t_out)) ->
(* Extract the type denoted by the type expression *)
rec_flow cx trace (l, T t_out)

(* non-class/function values used in annotations are errors *)
| _, UseT TypeT _ ->
prerr_flow cx trace
Expand Down Expand Up @@ -3563,6 +3585,7 @@ and err_operation = function
| HasOwnPropT _ -> "Property not found in"
| HasPropT _ -> "Property not found in"
| UnaryMinusT _ -> "Expected number instead of"
| ReifyTypeT _ -> "Internal Error: Invalid type applied to ReifyTypeT!"
(* unreachable or unclassified use-types. until we have a mechanical way
to verify that all legit use types are listed above, we can't afford
to throw on a use type, so mark the error instead *)
Expand Down
5 changes: 5 additions & 0 deletions src/typing/gc_js.ml
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,9 @@ and gc_use cx state = function
| NotT (_, t) ->
gc cx state t

| ReifyTypeT (_, t_out) ->
gc cx state t_out

| SpecializeT (_, _, ts, t) ->
ts |> List.iter (gc cx state);
gc cx state t
Expand Down Expand Up @@ -373,6 +376,8 @@ and gc_use cx state = function
gc cx state target_module;
gc cx state t_out;

| DebugPrintT _ -> ()

and gc_id cx state id =
let root_id, constraints = Flow_js.find_constraints cx id in (
if state#mark id then (
Expand Down
Loading

0 comments on commit e0e44d3

Please sign in to comment.