Skip to content

Commit

Permalink
Store object call properties in internal slot
Browse files Browse the repository at this point in the history
Summary:
Before this change, callable objects, declared classes, and interfaces all
stored the callable signature in a specially-named property `$call`.

It's perfectly legal to have a property with that name, however unlikely, so
this representation has inherent badness.

Furthermore, storing the callable signature in the propmap means that it
interacts with prototype lookup, but at runtime a callable signature can not be
inherited through the prototype chain.

Consider the following program, which is an error at runtime, but which Flow
accepts as valid.

```
function f() {}
var o = Object.create(f);
o(); // TypeError: o is not a function
```

Lastly, users have "figured out" this behavior and have started to actually
define objects with an explicit `$call` property instead of using the intended
syntax. Sometimes this capability is actually useful, specifically when the
desired callable signature is not a function type.

Reviewed By: panagosg7

Differential Revision: D8041565

fbshipit-source-id: 0f760cdf82b6e229a3c5db0c2e81a2331c7a2de1
  • Loading branch information
samwgoldman authored and facebook-github-bot committed Jun 11, 2018
1 parent 73df001 commit b9db648
Show file tree
Hide file tree
Showing 13 changed files with 288 additions and 124 deletions.
26 changes: 22 additions & 4 deletions src/typing/class_sig.ml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ type signature = {
methods: (Loc.t option * Func_sig.t) Nel.t SMap.t;
getters: (Loc.t option * Func_sig.t) SMap.t;
setters: (Loc.t option * Func_sig.t) SMap.t;
calls: Func_sig.t list;
}

type t = {
Expand Down Expand Up @@ -62,6 +63,7 @@ let empty id reason tparams tparams_map super =
methods = SMap.empty;
getters = SMap.empty;
setters = SMap.empty;
calls = [];
} in
let structural, super, ssuper, implements =
let open Type in
Expand Down Expand Up @@ -199,6 +201,11 @@ let append_method ~static name loc fsig x =
setters = SMap.remove name s.setters;
}) x

let append_call ~static fsig = map_sig ~static (fun s -> {
s with
calls = fsig :: s.calls
})

let add_getter ~static name loc fsig x =
let flat = static || x.structural in
map_sig ~static (fun s -> {
Expand Down Expand Up @@ -245,6 +252,7 @@ let subst_sig cx map s =
methods = SMap.map (Nel.map subst_func_sig) s.methods;
getters = SMap.map (subst_func_sig) s.getters;
setters = SMap.map (subst_func_sig) s.setters;
calls = List.map (Func_sig.subst cx map) s.calls;
}

let generate_tests cx f x =
Expand Down Expand Up @@ -343,6 +351,15 @@ let elements cx ~tparams_map ?constructor s =
SMap.add name (to_field fld) acc
) s.proto_fields methods in

let call = match List.rev_map (Func_sig.methodtype cx) s.calls with
| [] -> None
| [t] -> Some t
| t0::t1::ts ->
let open Type in
let t = DefT (reason_of_t t0, IntersectionT (InterRep.make t0 t1 ts)) in
Some t
in

(* Only un-initialized fields require annotations, so determine now
* (syntactically) which fields have initializers *)
let initialized_field_names = SMap.fold (fun x (_, _, field) acc ->
Expand All @@ -351,15 +368,15 @@ let elements cx ~tparams_map ?constructor s =
| Infer _ -> SSet.add x acc
) s.fields SSet.empty in

initialized_field_names, fields, methods
initialized_field_names, fields, methods, call

let arg_polarities x =
List.fold_left Type.(fun acc tp ->
SMap.add tp.name tp.polarity acc
) SMap.empty x.tparams

let statictype cx ~tparams_map s =
let inited_fields, fields, methods = elements cx ~tparams_map s in
let inited_fields, fields, methods, call = elements cx ~tparams_map s in
let props = SMap.union fields methods
~combine:(fun _ _ ->
Utils_js.assert_false (Utils_js.spf
Expand All @@ -369,7 +386,7 @@ let statictype cx ~tparams_map s =
(* Statics are not exact, because we allow width subtyping between them.
Specifically, given class A and class B extends A, Class<B> <: Class<A>. *)
let static =
Obj_type.mk_with_proto cx s.reason ~props s.super
Obj_type.mk_with_proto cx s.reason ~props ?call s.super
~sealed:true ~exact:false
in
let open Type in
Expand All @@ -389,7 +406,7 @@ let insttype cx ~tparams_map ~initialized_static_field_names s =
let t = DefT (reason_of_t t0, IntersectionT (InterRep.make t0 t1 ts)) in
Some (loc0, t)
in
let inited_fields, fields, methods = elements cx ~tparams_map ?constructor s.instance in
let inited_fields, fields, methods, call = elements cx ~tparams_map ?constructor s.instance in
{ Type.
class_id = s.id;
type_args = SMap.map (fun t -> (Type.reason_of_t t, t)) s.tparams_map;
Expand All @@ -398,6 +415,7 @@ let insttype cx ~tparams_map ~initialized_static_field_names s =
initialized_field_names = inited_fields;
initialized_static_field_names;
methods_tmap = Context.make_property_map cx methods;
inst_call_t = call;
has_unknown_react_mixins = false;
structural = s.structural;
}
Expand Down
2 changes: 2 additions & 0 deletions src/typing/class_sig.mli
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ val add_method: static:bool -> string -> Loc.t option -> Func_sig.t -> t -> t
of a single overloaded method. *)
val append_method: static:bool -> string -> Loc.t option -> Func_sig.t -> t -> t

val append_call: static:bool -> Func_sig.t -> t -> t

(** Add getter to signature. *)
val add_getter: static:bool -> string -> Loc.t option -> Func_sig.t -> t -> t

Expand Down
2 changes: 1 addition & 1 deletion src/typing/codegen.ml
Original file line number Diff line number Diff line change
Expand Up @@ -253,7 +253,7 @@ let rec gen_type t env = Type.(
add_str "number" env
| DefT (_, NumT (Truthy|AnyLiteral)) -> add_str "number" env
| DefT (_, NullT) | NullProtoT _ -> add_str "null" env
| DefT (_, ObjT {flags = _; dict_t; props_tmap; proto_t = _;}) -> (
| DefT (_, ObjT {flags = _; dict_t; call_t = _; props_tmap; proto_t = _;}) -> (
let env = add_str "{" env in

(* Generate prop entries *)
Expand Down
Loading

0 comments on commit b9db648

Please sign in to comment.