Skip to content

Commit

Permalink
[optional-chaining] Create method_action type to prepare for option…
Browse files Browse the repository at this point in the history
…al chain method calls

Summary: To support optionally-chained method calls, we will need to handle the case where we look up a method on a receiver and find that it is optional, then "filter out" the nullish components of the type before passing the non-null function to a `CallT`. This work is built in later diffs, but this diff modifies `MethodT` (and relatedly, `CallElemT`) so that instead of containing a `funcalltype` representing the call that the looked-up method will flow to, it contains a new ocaml type called `method_action`. Method actions are currently just `CallM`, which represents the current, non-optional behavior of methods where the looked-up method is directly called. `CallM`s are converted to `CallT`s after methods are looked up. In later dfiffs we will add `ChainM`s, which are converted to `OptionalChainT`s and support optional chain method calls, but this diff just sets up for that.

Reviewed By: panagosg7

Differential Revision: D18302024

fbshipit-source-id: 12101d9e5adeff5773c2015210d23bf122af085e
  • Loading branch information
mvitousek authored and facebook-github-bot committed Nov 5, 2019
1 parent 114a341 commit 33cacfe
Show file tree
Hide file tree
Showing 7 changed files with 104 additions and 42 deletions.
15 changes: 10 additions & 5 deletions src/typing/debug_js.ml
Expand Up @@ -422,7 +422,7 @@ and _json_of_use_t_impl json_cx t =
| MethodT (_, _, _, propref, funtype, _) ->
[
("propRef", json_of_propref json_cx propref);
("funType", json_of_funcalltype json_cx funtype);
("funType", json_of_methodaction json_cx funtype);
]
| ReposLowerT (_, use_desc, use_t) ->
[("type", _json_of_use_t json_cx use_t); ("useDesc", JSON_Bool use_desc)]
Expand All @@ -443,10 +443,10 @@ and _json_of_use_t_impl json_cx t =
| SetElemT (_, _, indext, _, elemt, _)
| GetElemT (_, _, indext, elemt) ->
[("indexType", _json_of_t json_cx indext); ("elemType", _json_of_t json_cx elemt)]
| CallElemT (_, _, indext, funtype) ->
| CallElemT (_, _, indext, action) ->
[
("indexType", _json_of_t json_cx indext);
("funType", json_of_funcalltype json_cx funtype);
("funType", json_of_methodaction json_cx action);
]
| GetStaticsT (_, t) -> [("type", _json_of_t json_cx t)]
| GetProtoT (_, t)
Expand Down Expand Up @@ -584,7 +584,7 @@ and _json_of_use_t_impl json_cx t =
(match action with
| ReadElem t -> ("readElem", _json_of_t json_cx t)
| WriteElem (t, _, _) -> ("writeElem", _json_of_t json_cx t)
| CallElem (_, funtype) -> ("callElem", json_of_funcalltype json_cx funtype));
| CallElem (_, action) -> ("callElem", json_of_methodaction json_cx action));
]
| MakeExactT (_, cont) -> _json_of_cont json_cx cont
| CJSRequireT (_, export, _) -> [("export", _json_of_t json_cx export)]
Expand Down Expand Up @@ -1008,6 +1008,11 @@ and json_of_funcalltype_impl
("strictArity", JSON_Bool call_strict_arity);
])

and json_of_methodaction json_cx = check_depth json_of_methodaction_impl json_cx

and json_of_methodaction_impl json_cx = function
| CallM funtype -> json_of_funcalltype json_cx funtype

and json_of_funcallarg json_cx = check_depth json_of_funcallarg_impl json_cx

and json_of_funcallarg_impl json_cx arg =
Expand Down Expand Up @@ -2057,7 +2062,7 @@ and dump_use_t_ (depth, tvars) cx t =
| AssertImportIsValueT _ -> p t
| BecomeT (_, arg) -> p ~extra:(kid arg) t
| BindT _ -> p t
| CallElemT (_, _, ix, _) -> p ~extra:(kid ix) t
| CallElemT (_, _, _, _) -> p t
| CallT (use_op, _, { call_args_tlist; call_tout; call_this_t; _ }) ->
p
~extra:
Expand Down
26 changes: 14 additions & 12 deletions src/typing/flow_js.ml
Expand Up @@ -4204,8 +4204,8 @@ struct
args
(ResolveSpreadsToCustomFunCall (mk_id (), kind, tout))
| ( CustomFunT (_, (ObjectAssign | ObjectGetPrototypeOf | ObjectSetPrototypeOf)),
MethodT (use_op, reason_call, _, Named (_, "call"), calltype, _) ) ->
rec_flow cx trace (l, CallT (use_op, reason_call, calltype))
MethodT (use_op, reason_call, _, Named (_, "call"), action, _) ) ->
rec_flow cx trace (l, apply_method_action use_op reason_call action)
(* Custom functions are still functions, so they have all the prototype properties *)
| (CustomFunT (r, _), _) when function_like_op u -> rec_flow cx trace (FunProtoT r, u)
(*********************************************)
Expand Down Expand Up @@ -4549,7 +4549,7 @@ struct
rec_flow
cx
trace
(this, MethodT (use_op, reason_op, reason_o, propref, funtype, None)))
(this, MethodT (use_op, reason_op, reason_o, propref, CallM funtype, None)))
in
(* return this *)
rec_flow cx trace (ret, ObjTestT (annot_reason ~annot_loc reason_op, this, t))
Expand Down Expand Up @@ -4601,7 +4601,8 @@ struct
rec_flow_t cx trace (AnyT.untyped reason_op, t)
(* Since we don't know the signature of a method on AnyT, assume every
parameter is an AnyT. *)
| (AnyT _, MethodT (use_op, reason_op, _, _, { call_args_tlist; call_tout; _ }, prop_t)) ->
| ( AnyT _,
MethodT (use_op, reason_op, _, _, CallM { call_args_tlist; call_tout; _ }, prop_t) ) ->
let any = AnyT.untyped reason_op in
call_args_iter (fun t -> rec_flow cx trace (t, UseT (use_op, any))) call_args_tlist;
Option.iter ~f:(fun prop_t -> rec_flow_t cx trace (any, prop_t)) prop_t;
Expand Down Expand Up @@ -4813,8 +4814,8 @@ struct
(* ... and their methods called *)
(********************************)
| ( DefT (reason_c, _, InstanceT (_, super, _, instance)),
MethodT (use_op, reason_call, reason_lookup, Named (reason_prop, x), funtype, prop_t)
) ->
MethodT (use_op, reason_call, reason_lookup, Named (reason_prop, x), action, prop_t) )
->
(* TODO: closure *)
let own_props = Context.find_props cx instance.own_props in
let proto_props = Context.find_props cx instance.proto_props in
Expand All @@ -4833,7 +4834,7 @@ struct
`CallT` will set its own ops during the call. if `funt` is something
else, then something like `VoidT ~> CallT` doesn't need the op either
because we want to point at the call and undefined thing. *)
rec_flow cx trace (funt, CallT (use_op, reason_call, funtype))
rec_flow cx trace (funt, apply_method_action use_op reason_call action)
| (DefT (_, _, InstanceT _), MethodT (_, reason_call, _, Computed _, _, _)) ->
(* Instances don't have proper dictionary support. All computed accesses
are converted to named property access to `$key` and `$value` during
Expand Down Expand Up @@ -5169,13 +5170,13 @@ struct
(********************************)
| (DefT (_, _, ObjT _), MethodT (_, _, _, Named (_, "constructor"), _, _)) -> ()
| ( DefT (reason_obj, _, ObjT o),
MethodT (use_op, reason_call, reason_lookup, propref, funtype, prop_t) ) ->
MethodT (use_op, reason_call, reason_lookup, propref, action, prop_t) ) ->
let t =
Tvar.mk_where cx reason_lookup (fun tout ->
read_obj_prop cx trace ~use_op o propref reason_obj reason_lookup tout)
in
Option.iter ~f:(fun prop_t -> rec_flow_t cx trace (t, prop_t)) prop_t;
rec_flow cx trace (t, CallT (use_op, reason_call, funtype))
rec_flow cx trace (t, apply_method_action use_op reason_call action)
(******************************************)
(* strings may have their characters read *)
(******************************************)
Expand All @@ -5197,8 +5198,8 @@ struct
| ((DefT (_, _, (ObjT _ | ArrT _)) | AnyT _), GetElemT (use_op, reason_op, key, tout)) ->
rec_flow cx trace (key, ElemT (use_op, reason_op, l, ReadElem tout))
| ( (DefT (_, _, (ObjT _ | ArrT _)) | AnyT _),
CallElemT (reason_call, reason_lookup, key, ft) ) ->
let action = CallElem (reason_call, ft) in
CallElemT (reason_call, reason_lookup, key, action) ) ->
let action = CallElem (reason_call, action) in
rec_flow cx trace (key, ElemT (unknown_use, reason_lookup, l, action))
| (_, ElemT (use_op, reason_op, (DefT (_, _, ObjT _) as obj), action)) ->
elem_action_on_obj cx trace ~use_op l obj reason_op action
Expand Down Expand Up @@ -10853,7 +10854,8 @@ struct
trace
(tin, UseT (use_op, VoidT.why (reason_of_t value) |> with_trust literal_trust));
Option.iter ~f:(fun t -> rec_flow_t cx trace (l, t)) tout
| (CallElem (reason_call, ft), _) -> rec_flow cx trace (value, CallT (use_op, reason_call, ft))
| (CallElem (reason_call, action), _) ->
rec_flow cx trace (value, apply_method_action use_op reason_call action)

and string_key s reason =
let key_reason = replace_desc_reason (RPropertyIsAString s) reason in
Expand Down
31 changes: 20 additions & 11 deletions src/typing/statement.ml
Expand Up @@ -3780,8 +3780,12 @@ and subscript ~is_cond cx ex =
cx
( super,
MethodT
(use_op, reason, reason_lookup, Named (reason_prop, name), funtype, Some prop_t)
))
( use_op,
reason,
reason_lookup,
Named (reason_prop, name),
CallM funtype,
Some prop_t ) ))
in
Some
( (loc, lhs_t),
Expand Down Expand Up @@ -3852,7 +3856,8 @@ and subscript ~is_cond cx ex =
Tvar.mk_where cx reason_call (fun t ->
let frame = Env.peek_frame () in
let funtype = mk_methodcalltype ot targts argts t ~frame in
Flow.flow cx (ot, CallElemT (reason_call, reason_lookup, elem_t, funtype))) ),
Flow.flow cx (ot, CallElemT (reason_call, reason_lookup, elem_t, CallM funtype)))
),
Member.PropertyExpression expr )
in
Some
Expand Down Expand Up @@ -3890,7 +3895,9 @@ and subscript ~is_cond cx ex =
local = true;
})
in
Flow.flow cx (super, MethodT (use_op, reason, super_reason, propref, funtype, None)))
Flow.flow
cx
(super, MethodT (use_op, reason, super_reason, propref, CallM funtype, None)))
in
Some
( (loc, lhs_t),
Expand Down Expand Up @@ -4389,8 +4396,9 @@ and method_call
let reason_expr = mk_reason (RProperty (Some name)) expr_loc in
let app = mk_methodcalltype obj_t targts argts t ~frame ~call_strict_arity in
let propref = Named (reason_prop, name) in
Flow.flow cx (obj_t, MethodT (use_op, reason, reason_expr, propref, app, Some prop_t)))
)
Flow.flow
cx
(obj_t, MethodT (use_op, reason, reason_expr, propref, CallM app, Some prop_t))) )

and identifier_ cx name loc =
if Type_inference_hooks_js.dispatch_id_hook cx name loc then
Expand Down Expand Up @@ -5331,11 +5339,12 @@ and jsx_desugar cx name component_t props attributes children locs =
reason,
reason_createElement,
Named (reason_createElement, "createElement"),
mk_methodcalltype
react
None
([Arg component_t; Arg props] @ Base.List.map ~f:(fun c -> Arg c) children)
tvar,
CallM
(mk_methodcalltype
react
None
([Arg component_t; Arg props] @ Base.List.map ~f:(fun c -> Arg c) children)
tvar),
None ) ))
| Options.Jsx_pragma (raw_jsx_expr, jsx_expr) ->
let reason = mk_reason (RJSXFunctionCall raw_jsx_expr) loc_element in
Expand Down
22 changes: 17 additions & 5 deletions src/typing/type.ml
Expand Up @@ -444,7 +444,7 @@ module rec TypeTerm : sig
(* The last position is an optional type that probes into the type of the
method called. This will be primarily used for type-table bookkeeping. *)
| MethodT of
use_op * (* call *) reason * (* lookup *) reason * propref * funcalltype * t option
use_op * (* call *) reason * (* lookup *) reason * propref * method_action * t option
(* Similar to the last element of the MethodT *)
| SetPropT of use_op * reason * propref * set_mode * write_ctx * t * t option
(* The boolean flag indicates whether or not it is a static lookup. We cannot know this when
Expand All @@ -466,7 +466,7 @@ module rec TypeTerm : sig
need to ensure that reads happen after writes. *)
| SetElemT of use_op * reason * t * set_mode * t * t option (*tout *)
| GetElemT of use_op * reason * t * t
| CallElemT of (* call *) reason * (* lookup *) reason * t * funcalltype
| CallElemT of (* call *) reason * (* lookup *) reason * t * method_action
| GetStaticsT of reason * t_out
| GetProtoT of reason * t_out
| SetProtoT of reason * t
Expand Down Expand Up @@ -759,6 +759,10 @@ module rec TypeTerm : sig
| NewChain
| ContinueChain

and method_action = CallM of funcalltype

and opt_method_action = OptCallM of opt_funcalltype

and specialize_cache = reason list option

and predicate =
Expand Down Expand Up @@ -1014,7 +1018,7 @@ module rec TypeTerm : sig
and elem_action =
| ReadElem of t
| WriteElem of t * t option (* tout *) * set_mode
| CallElem of reason (* call *) * funcalltype
| CallElem of reason * method_action

and propref =
| Named of reason * name
Expand Down Expand Up @@ -1795,13 +1799,13 @@ end = struct
(* the only unresolved tvars at this point are those that instantiate polymorphic types *)
| OpenT _
(* some types may not be evaluated yet; TODO *)

| EvalT _
| TypeAppT _
| KeysT _
| IntersectionT _
(* other types might wrap parts that are accessible directly *)

| OpaqueT _
| DefT (_, _, InstanceT _)
| DefT (_, _, PolyT _) ->
Expand Down Expand Up @@ -3949,6 +3953,10 @@ let apply_opt_funcalltype (this, targs, args, clos, strict) t_out =

let create_intersection rep = IntersectionT (locationless_reason (RCustom "intersection"), rep)

let apply_opt_action action t_out =
match action with
| OptCallM f -> CallM (apply_opt_funcalltype f t_out)

let apply_opt_use opt_use t_out =
match opt_use with
| OptCallT (u, r, f) -> CallT (u, r, apply_opt_funcalltype f t_out)
Expand All @@ -3962,6 +3970,10 @@ let mk_enum_type ~loc ~trust enum =
let reason = mk_reason (RType enum_name) loc in
DefT (reason, trust, EnumT enum)

let apply_method_action use_op reason_call action =
match action with
| CallM app -> CallT (use_op, reason_call, app)

module TypeParams : sig
val to_list : typeparams -> typeparam list

Expand Down
30 changes: 24 additions & 6 deletions src/typing/type_mapper.ml
Expand Up @@ -818,14 +818,14 @@ class virtual ['a] t_with_uses =
t
else
CallT (op, r, funcall')
| MethodT (op, r1, r2, prop, funcall, prop_t) ->
| MethodT (op, r1, r2, prop, action, prop_t) ->
let prop' = self#prop_ref cx map_cx prop in
let funcall' = self#fun_call_type cx map_cx funcall in
let action' = self#method_action cx map_cx action in
let prop_t' = OptionUtils.ident_map (self#type_ cx map_cx) prop_t in
if prop' == prop && funcall' == funcall && prop_t' == prop_t then
if prop' == prop && action' == action && prop_t' == prop_t then
t
else
MethodT (op, r1, r2, prop', funcall', prop_t')
MethodT (op, r1, r2, prop', action', prop_t')
| SetPropT (use_op, r, prop, mode, i, t', prop_t) ->
let prop' = self#prop_ref cx map_cx prop in
let t'' = self#type_ cx map_cx t' in
Expand Down Expand Up @@ -887,7 +887,7 @@ class virtual ['a] t_with_uses =
GetElemT (use_op, r, t1', t2')
| CallElemT (r1, r2, t', funcall) ->
let t'' = self#type_ cx map_cx t' in
let funcall' = self#fun_call_type cx map_cx funcall in
let funcall' = self#method_action cx map_cx funcall in
if t' == t'' && funcall' == funcall then
t
else
Expand Down Expand Up @@ -1482,7 +1482,7 @@ class virtual ['a] t_with_uses =
else
WriteElem (tin', tout', mode)
| CallElem (r, funcall) ->
let funcall' = self#fun_call_type cx map_cx funcall in
let funcall' = self#method_action cx map_cx funcall in
if funcall' == funcall then
t
else
Expand Down Expand Up @@ -1561,6 +1561,24 @@ class virtual ['a] t_with_uses =
else
ResolveSpreadsToCallT (funcalltype', t'')

method private opt_method_action cx map_cx t =
match t with
| OptCallM funtype ->
let funtype' = self#opt_fun_call_type cx map_cx funtype in
if funtype' == funtype then
t
else
OptCallM funtype'

method method_action cx map_cx t =
match t with
| CallM funtype ->
let funtype' = self#fun_call_type cx map_cx funtype in
if funtype' == funtype then
t
else
CallM funtype'

method fun_call_type cx map_cx t =
let {
call_this_t;
Expand Down
2 changes: 2 additions & 0 deletions src/typing/type_mapper.mli
Expand Up @@ -92,6 +92,8 @@ class virtual ['a] t_with_uses :

method fun_call_type : Context.t -> 'a -> Type.funcalltype -> Type.funcalltype

method method_action : Context.t -> 'a -> Type.method_action -> Type.method_action

method initial_state :
Context.t ->
'a ->
Expand Down
20 changes: 17 additions & 3 deletions src/typing/type_visitor.ml
Expand Up @@ -240,7 +240,7 @@ class ['a] t =
self#fun_call_type cx acc fn
| MethodT (_, _, _, p, fn, prop_t) ->
let acc = self#propref cx acc p in
let acc = self#fun_call_type cx acc fn in
let acc = self#method_action cx acc fn in
let acc = self#opt (self#type_ cx pole_TODO) acc prop_t in
acc
| SetPropT (_, _, p, _, _, t, prop_t) ->
Expand Down Expand Up @@ -274,7 +274,7 @@ class ['a] t =
acc
| CallElemT (_, _, t, fn) ->
let acc = self#type_ cx pole_TODO acc t in
let acc = self#fun_call_type cx acc fn in
let acc = self#method_action cx acc fn in
acc
| GetStaticsT (_, t)
| GetProtoT (_, t)
Expand Down Expand Up @@ -742,6 +742,20 @@ class ['a] t =
let acc = self#type_ cx pole_TODO acc call_tout in
acc

method private opt_fun_call_type cx acc (call_this_t, call_targs, call_args_tlist, _, _) =
let acc = self#type_ cx pole_TODO acc call_this_t in
let acc = self#opt (self#list (self#targ cx pole_TODO)) acc call_targs in
let acc = self#list (self#call_arg cx) acc call_args_tlist in
acc

method private method_action cx acc =
function
| CallM call -> self#fun_call_type cx acc call

method private opt_method_action cx acc =
function
| OptCallM call -> self#opt_fun_call_type cx acc call

method private propref cx acc =
function
| Named _ -> acc
Expand Down Expand Up @@ -792,7 +806,7 @@ class ['a] t =
let acc = self#type_ cx pole_TODO acc tin in
let acc = self#opt (self#type_ cx pole_TODO) acc tout in
acc
| CallElem (_, fn) -> self#fun_call_type cx acc fn
| CallElem (_, fn) -> self#method_action cx acc fn

method private cont cx acc =
function
Expand Down

0 comments on commit 33cacfe

Please sign in to comment.