Skip to content

Fix Function.prototype.apply.bind #7879

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions src/typing/debug_js.ml
Original file line number Diff line number Diff line change
@@ -170,11 +170,18 @@ and _json_of_t_impl json_cx t = Hh_json.(
| NullProtoT _
| ObjProtoT _
| FunProtoT _
| FunProtoApplyT _
| FunProtoApplyT (_, None, _)
| FunProtoBindT _
| FunProtoCallT _
-> []

| FunProtoApplyT (_, Some t, call_args_tlist) ->
let arg_types = Core_list.map ~f:(json_of_funcallarg json_cx) call_args_tlist in
[
"thisType", _json_of_t json_cx t;
"argTypes", JSON_Array arg_types;
]

| DefT (_, _, FunT (static, proto, funtype)) -> [
"static", _json_of_t json_cx static;
"prototype", _json_of_t json_cx proto;
@@ -1794,9 +1801,11 @@ let rec dump_t_ (depth, tvars) cx t =
| NullProtoT _
| ObjProtoT _
| FunProtoT _
| FunProtoApplyT _
| FunProtoApplyT (_, None, _)
| FunProtoBindT _
| FunProtoCallT _ -> p t
| FunProtoApplyT (_, Some arg, _ (* TODO *)) ->
p ~extra:(kid arg) t
| DefT (_, trust, PolyT (_, tps, c, id)) -> p ~trust:(Some trust) ~extra:(spf "%s [%s] #%d"
(kid c)
(String.concat "; " (Core_list.map ~f:(fun tp -> tp.name) (Nel.to_list tps)))
25 changes: 21 additions & 4 deletions src/typing/flow_js.ml
Original file line number Diff line number Diff line change
@@ -5586,8 +5586,12 @@ let rec __flow cx ((l: Type.t), (u: Type.use_t)) trace =
(*******************************************)

(* resolves the arguments... *)
| FunProtoApplyT lreason,
CallT (use_op, reason_op, ({call_this_t = func; call_args_tlist; _} as funtype)) ->
| FunProtoApplyT (lreason, arg, arg_tlist),
CallT (use_op, reason_op, ({call_this_t; call_args_tlist; _} as funtype)) ->
let func = match arg with
| Some t -> t
| None -> call_this_t
in
(* Drop the specific AST derived argument reasons. Our new arguments come
* from arbitrary positions in the array. *)
let use_op = match use_op with
@@ -5597,6 +5601,8 @@ let rec __flow cx ((l: Type.t), (u: Type.use_t)) trace =
| _ -> use_op
in

let call_args_tlist = arg_tlist @ call_args_tlist in

begin match call_args_tlist with
(* func.apply() *)
| [] ->
@@ -5710,6 +5716,13 @@ let rec __flow cx ((l: Type.t), (u: Type.use_t)) trace =
) call_args_tlist;
rec_flow_t cx trace (l, call_tout)

| FunProtoApplyT (lreason, Some this_t, _), BindT (_, _, { call_this_t; call_args_tlist; call_tout; _ }, _) ->
rec_flow_t cx trace (call_this_t, this_t);
rec_flow_t cx trace (FunProtoApplyT (lreason, Some this_t, call_args_tlist), call_tout)

| FunProtoApplyT (lreason, _, _), BindT (_, _, { call_this_t; call_args_tlist; call_tout; _ }, _) ->
rec_flow_t cx trace (FunProtoApplyT (lreason, Some call_this_t, call_args_tlist), call_tout)

| _, BindT (_, _, { call_tout; _ }, true) ->
rec_flow_t cx trace (l, call_tout)

@@ -6512,7 +6525,7 @@ let rec __flow cx ((l: Type.t), (u: Type.use_t)) trace =
rec_flow cx trace (AnyT.make AnyError lreason, u);

(* Special cases of FunT *)
| FunProtoApplyT reason, _
| FunProtoApplyT (reason, _, _), _
| FunProtoBindT reason, _
| FunProtoCallT reason, _ ->
rec_flow cx trace (FunProtoT reason, u)
@@ -7296,9 +7309,13 @@ and any_propagated_use cx trace use_op any l =
covariant_flow ~use_op instance;
true

| FunProtoApplyT (_, Some t, _ (* TODO *)) ->
contravariant_flow ~use_op t;
true

(* These types have no negative positions in their lower bounds *)
| ExistsT _
| FunProtoApplyT _
| FunProtoApplyT (_, None, _)
| FunProtoBindT _
| FunProtoCallT _
| FunProtoT _
7 changes: 6 additions & 1 deletion src/typing/resolvableTypeJob.ml
Original file line number Diff line number Diff line change
@@ -247,7 +247,7 @@ and collect_of_type ?log_unresolved cx acc = function

| FunProtoBindT _
| FunProtoCallT _
| FunProtoApplyT _
| FunProtoApplyT (_, None, _)
| FunProtoT _
| NullProtoT _
| ObjProtoT _
@@ -258,6 +258,11 @@ and collect_of_type ?log_unresolved cx acc = function
->
acc

| FunProtoApplyT (_, Some t, ts) ->
let arg_types =
Core_list.map ~f:(function Arg t | SpreadArg t -> t) ts in
collect_of_types ?log_unresolved cx acc (arg_types @ [t])

and collect_of_destructor ?log_unresolved cx acc = function
| NonMaybeType -> acc
| PropertyType _ -> acc
6 changes: 3 additions & 3 deletions src/typing/type.ml
Original file line number Diff line number Diff line change
@@ -93,7 +93,7 @@ module rec TypeTerm : sig
appears as an upper bound of an object type, otherwise the same. *)
| NullProtoT of reason

| FunProtoApplyT of reason (* Function.prototype.apply *)
| FunProtoApplyT of reason * t option (* this type *) * call_arg list (* Function.prototype.apply *)
| FunProtoBindT of reason (* Function.prototype.bind *)
| FunProtoCallT of reason (* Function.prototype.call *)

@@ -2140,7 +2140,7 @@ end = struct
| ExistsT reason -> reason
| InternalT (ExtendsT (reason, _, _)) -> reason
| FunProtoT reason -> reason
| FunProtoApplyT reason -> reason
| FunProtoApplyT (reason, _, _) -> reason
| FunProtoBindT reason -> reason
| FunProtoCallT reason -> reason
| KeysT (reason, _) -> reason
@@ -2303,7 +2303,7 @@ end = struct
| ExactT (reason, t) -> ExactT (f reason, t)
| ExistsT reason -> ExistsT (f reason)
| InternalT (ExtendsT (reason, t1, t2)) -> InternalT (ExtendsT (f reason, t1, t2))
| FunProtoApplyT (reason) -> FunProtoApplyT (f reason)
| FunProtoApplyT (reason, t1, args) -> FunProtoApplyT (f reason, t1, args)
| FunProtoT (reason) -> FunProtoT (f reason)
| FunProtoBindT (reason) -> FunProtoBindT (f reason)
| FunProtoCallT (reason) -> FunProtoCallT (f reason)
2 changes: 1 addition & 1 deletion src/typing/type_annotation.ml
Original file line number Diff line number Diff line change
@@ -854,7 +854,7 @@ let rec convert cx tparams_map = Ast.Type.(function
| "Function$Prototype$Apply" ->
check_type_arg_arity cx loc t_ast targs 0 (fun () ->
let reason = mk_reason RFunctionType loc in
reconstruct_ast (FunProtoApplyT reason) None
reconstruct_ast (FunProtoApplyT (reason, None, [])) None
)

| "Function$Prototype$Bind" ->
29 changes: 17 additions & 12 deletions src/typing/type_mapper.ml
Original file line number Diff line number Diff line change
@@ -105,9 +105,14 @@ class virtual ['a] t = object(self)
| FunProtoT _
| ObjProtoT _
| NullProtoT _
| FunProtoApplyT _
| FunProtoApplyT (_, None, _)
| FunProtoBindT _
| FunProtoCallT _ -> t
| FunProtoApplyT (r, Some t', call_args_tlist) ->
let t'' = self#type_ cx map_cx t' in
let call_args_tlist' = ListUtils.ident_map (self#call_arg cx map_cx) call_args_tlist in
if t'' == t' && call_args_tlist' == call_args_tlist' then t
else FunProtoApplyT (r, Some t'', call_args_tlist')
| AnyWithLowerBoundT t' ->
let t'' = self#type_ cx map_cx t' in
if t'' == t' then t
@@ -211,6 +216,17 @@ class virtual ['a] t = object(self)
if t'' == t' then t
else ExplicitArg t''

method call_arg cx map_cx t =
match t with
| Arg t' ->
let t'' = self#type_ cx map_cx t' in
if t'' == t' then t
else Arg t''
| SpreadArg t' ->
let t'' = self#type_ cx map_cx t' in
if t'' == t' then t
else SpreadArg t''

method def_type cx map_cx t =
match t with
| NumT _
@@ -1154,17 +1170,6 @@ class virtual ['a] t_with_uses = object(self)
call_strict_arity;
}

method call_arg cx map_cx t =
match t with
| Arg t' ->
let t'' = self#type_ cx map_cx t' in
if t'' == t' then t
else Arg t''
| SpreadArg t' ->
let t'' = self#type_ cx map_cx t' in
if t'' == t' then t
else SpreadArg t''

method lookup_kind cx map_cx t =
match t with
| Strict _ -> t
4 changes: 2 additions & 2 deletions src/typing/type_mapper.mli
Original file line number Diff line number Diff line change
@@ -6,6 +6,8 @@
*)
class virtual ['a] t :
object
method call_arg :
Context.t -> 'a -> Type.call_arg -> Type.call_arg
method arr_type :
Context.t -> 'a -> Type.arrtype -> Type.arrtype
method bounds :
@@ -49,8 +51,6 @@ end
class virtual ['a] t_with_uses :
object
inherit ['a] t
method call_arg :
Context.t -> 'a -> Type.call_arg -> Type.call_arg
method choice_use_tool :
Context.t ->
'a -> Type.choice_use_tool -> Type.choice_use_tool
7 changes: 6 additions & 1 deletion src/typing/type_visitor.ml
Original file line number Diff line number Diff line change
@@ -32,13 +32,18 @@ class ['a] t = object(self)
acc

| FunProtoT _
| FunProtoApplyT _
| FunProtoApplyT (_, None, _)
| FunProtoBindT _
| FunProtoCallT _
| ObjProtoT _
| NullProtoT _
-> acc

| FunProtoApplyT (_, Some t, call_args_tlist) ->
let acc = self#type_ cx pole acc t in
let acc = self#list (self#call_arg cx) acc call_args_tlist in
acc

| CustomFunT (_, kind) -> self#custom_fun_kind cx acc kind

| EvalT (t, defer_use_t, id) ->
18 changes: 17 additions & 1 deletion tests/function/bind.js
Original file line number Diff line number Diff line change
@@ -20,9 +20,25 @@ let tests = [
},

// callable objects with overridden `bind` method
function(x: {(a: string, b: string): void, bind(a: string): void}) {
function(x: { (a: string, b: string): void, bind(a: string): void }) {
(x.bind('foo'): void); // ok
(x.bind(123): void); // error, number !~> string
},

function(x: (x: number) => void) {
const appliedFn = Function.apply.bind(x); // ok
appliedFn(null, ['']); // error
},


function(x: (x: number) => void, y: (x: string) => void) {
const appliedFn = Function.apply.bind(x); // ok
const reappliedFn = appliedFn.bind(y); // error
reappliedFn(null, ['']); // error
},

function(x: (x: number) => void) {
const appliedFn = Function.apply.bind(x, null); // ok
appliedFn(['']); // error
},
];
67 changes: 63 additions & 4 deletions tests/function/function.exp
Original file line number Diff line number Diff line change
@@ -246,9 +246,68 @@ Cannot call `x.bind` with `123` bound to `a` because number [1] is incompatible
^^^ [1]

References:
bind.js:23:54
23| function(x: {(a: string, b: string): void, bind(a: string): void}) {
^^^^^^ [2]
bind.js:23:55
23| function(x: { (a: string, b: string): void, bind(a: string): void }) {
^^^^^^ [2]


Error ---------------------------------------------------------------------------------------------------- bind.js:30:22

Cannot call `appliedFn` with string bound to `x` because string [1] is incompatible with number [2].

bind.js:30:22
30| appliedFn(null, ['']); // error
^^ [1]

References:
bind.js:28:19
28| function(x: (x: number) => void) {
^^^^^^ [2]


Error ---------------------------------------------------------------------------------------------------- bind.js:36:40

string [1] is incompatible with number [2].

bind.js:36:40
36| const reappliedFn = appliedFn.bind(y); // error
^

References:
bind.js:34:43
34| function(x: (x: number) => void, y: (x: string) => void) {
^^^^^^ [1]
bind.js:34:19
34| function(x: (x: number) => void, y: (x: string) => void) {
^^^^^^ [2]


Error ---------------------------------------------------------------------------------------------------- bind.js:37:24

Cannot call `reappliedFn` with string bound to `x` because string [1] is incompatible with number [2].

bind.js:37:24
37| reappliedFn(null, ['']); // error
^^ [1]

References:
bind.js:34:19
34| function(x: (x: number) => void, y: (x: string) => void) {
^^^^^^ [2]


Error ---------------------------------------------------------------------------------------------------- bind.js:42:16

Cannot call `appliedFn` with string bound to `x` because string [1] is incompatible with number [2].

bind.js:42:16
42| appliedFn(['']); // error
^^ [1]

References:
bind.js:40:19
40| function(x: (x: number) => void) {
^^^^^^ [2]


Error ----------------------------------------------------------------------------------------------------- call.js:4:10
@@ -717,4 +776,4 @@ References:



Found 52 errors
Found 56 errors