Skip to content

Commit

Permalink
[flow] More understandable error for ObjT ~> FunT
Browse files Browse the repository at this point in the history
Summary:
The current error is technically correct, but very difficult to understand. Let's just use plain English and say non-callable objects are not compatible with functions.

Changelog: [errors] When a non-callable object is passed to a place expecting functions, we will have a clearer message saying "non-callable object is incompatible with function" instead of the current error talking about the callable property. Some error code might change, which requires new suppressions.

Reviewed By: panagosg7

Differential Revision: D67907577

fbshipit-source-id: 5059a2c280ce73fb5e39bb4b5969514b06662b79
  • Loading branch information
SamChou19815 authored and facebook-github-bot committed Jan 8, 2025
1 parent 2e8fbc8 commit 085609e
Show file tree
Hide file tree
Showing 15 changed files with 68 additions and 71 deletions.
1 change: 1 addition & 0 deletions src/typing/errors/error_message.ml
Original file line number Diff line number Diff line change
Expand Up @@ -789,6 +789,7 @@ let map_loc_of_explanation (f : 'a -> 'b) =
| ExplanationConcreteEnumCasting { representation_type; casting_syntax } ->
ExplanationConcreteEnumCasting { representation_type; casting_syntax }
| ExplanationMultiplatform -> ExplanationMultiplatform
| ExplanationNonCallableObjectToFunction -> ExplanationNonCallableObjectToFunction
| ExplanationPropertyInvariantTyping -> ExplanationPropertyInvariantTyping
| ExplanationReactComponentPropsDeepReadOnly loc ->
ExplanationReactComponentPropsDeepReadOnly (f loc)
Expand Down
2 changes: 2 additions & 0 deletions src/typing/errors/flow_intermediate_error.ml
Original file line number Diff line number Diff line change
Expand Up @@ -1096,6 +1096,8 @@ let to_printable_error :
text "Read the docs on Flow's multi-platform support for more information: ";
text "https://flow.org/en/docs/react/multiplatform";
]
| ExplanationNonCallableObjectToFunction ->
[text "Non-callable objects are not compatible with functions"]
| ExplanationPropertyInvariantTyping ->
[
text "This property is invariantly typed. See ";
Expand Down
1 change: 1 addition & 0 deletions src/typing/errors/flow_intermediate_error_types.ml
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,7 @@ type 'loc explanation =
casting_syntax: Options.CastingSyntax.t;
}
| ExplanationMultiplatform
| ExplanationNonCallableObjectToFunction
| ExplanationPropertyInvariantTyping
| ExplanationReactComponentPropsDeepReadOnly of 'loc
| ExplanationReactComponentRefRequirement
Expand Down
22 changes: 15 additions & 7 deletions src/typing/subtyping_kit.ml
Original file line number Diff line number Diff line change
Expand Up @@ -1924,24 +1924,32 @@ module Make (Flow : INPUT) : OUTPUT = struct
(* You can cast an object to a function *)
(****************************************)
| (DefT (reason, (ObjT _ | InstanceT _)), DefT (reason_op, FunT _)) ->
let prop_name = Some (OrdinaryName "$call") in
let use_op =
Frame (PropertyCompatibility { prop = prop_name; lower = reason; upper = reason_op }, use_op)
in
let fun_t =
match l with
| DefT (_, ObjT { call_t = Some id; _ })
| DefT (_, InstanceT { inst = { inst_call_t = Some id; _ }; _ }) ->
Context.find_call cx id
| _ ->
let reason_prop = replace_desc_reason (RProperty prop_name) reason_op in
let error_message =
Error_message.EPropNotFound
{ reason_prop; reason_obj = reason; prop_name; use_op; suggestion = None }
Error_message.EIncompatibleWithUseOp
{
reason_lower = reason;
reason_upper = reason_op;
use_op;
explanation =
Some Flow_intermediate_error_types.ExplanationNonCallableObjectToFunction;
}
in
add_output cx error_message;
AnyT.error reason_op
in
let use_op =
Frame
( PropertyCompatibility
{ prop = Some (OrdinaryName "$call"); lower = reason; upper = reason_op },
use_op
)
in
rec_flow_t cx trace ~use_op (fun_t, u)
(********************************************)
(* array types deconstruct into their parts *)
Expand Down
8 changes: 4 additions & 4 deletions tests/call_properties/call_properties.exp
Original file line number Diff line number Diff line change
Expand Up @@ -170,8 +170,8 @@ References:

Error ------------------------------------------------------------------------------------------------------- C.js:23:10

Cannot return `x` because a call signature declaring the expected parameter / return type is missing in object type [1]
but exists in function type [2]. [prop-missing]
Cannot return `x` because object type [1] is incompatible with function type [2]. Non-callable objects are not
compatible with functions. [incompatible-return]

C.js:23:10
23| return x;
Expand Down Expand Up @@ -430,8 +430,8 @@ References:

Error --------------------------------------------------------------------------------------------------- use_ops.js:4:2

Cannot cast `a` to `B` because a call signature declaring the expected parameter / return type is missing in object
type [1] but exists in function type [2] in property `p`. [prop-missing]
Cannot cast `a` to `B` because object type [1] is incompatible with function type [2] in property `p`. Non-callable
objects are not compatible with functions. [incompatible-cast]

use_ops.js:4:2
4| (a: B); // error HERE and preserve use ops
Expand Down
23 changes: 9 additions & 14 deletions tests/component_syntax/component_syntax.exp
Original file line number Diff line number Diff line change
Expand Up @@ -2754,8 +2754,7 @@ Error --------------------------------------------------------------------------

Cannot create `meta` element because in property `ref`: [incompatible-type]
- Either `HTMLAnchorElement` [1] is incompatible with `HTMLMetaElement` [2] in property `current`.
- Or a call signature declaring the expected parameter / return type is missing in object type [3] but exists in
function type [4].
- Or object type [3] is incompatible with function type [4]. Non-callable objects are not compatible with functions.

propsof.js:28:22
28| let b = <meta ref={ref} />; // error
Expand Down Expand Up @@ -2818,8 +2817,7 @@ Error --------------------------------------------------------------------------

Cannot declare ref because: [incompatible-type]
- Either null [1] is incompatible with component Reffed [2] in property `current`.
- Or a call signature declaring the expected parameter / return type is missing in object type [3] but exists in
function type [4].
- Or object type [3] is incompatible with function type [4]. Non-callable objects are not compatible with functions.

The `ref` parameter must be a subtype of `React.RefSetter`.

Expand Down Expand Up @@ -3103,8 +3101,8 @@ References:

Error ----------------------------------------------------------------------------------------------------- refs.js:15:2

Cannot cast `Bar` to component because a call signature declaring the expected parameter / return type is missing in
object type [1] but exists in function type [2]. [prop-missing]
Cannot cast `Bar` to component because object type [1] is incompatible with function type [2]. Non-callable objects are
not compatible with functions. [incompatible-cast]

refs.js:15:2
15| (Bar: component(ref: React.RefSetter<string>, ...empty)); // err
Expand Down Expand Up @@ -3169,8 +3167,8 @@ References:
Error ----------------------------------------------------------------------------------------------------- refs.js:17:2

Cannot cast `Baz` to component because: [incompatible-cast]
- Either a call signature declaring the expected parameter / return type is missing in object type [1] but exists in
function type [2].
- Either object type [1] is incompatible with function type [2]. Non-callable objects are not compatible with
functions.
- Or component Reffed [3] is incompatible with string [4] in property `current`.

refs.js:17:2
Expand Down Expand Up @@ -3226,8 +3224,7 @@ Error --------------------------------------------------------------------------
Cannot create `MyNestedInput` element because in property `ref`: [incompatible-type]
- Either `HTMLElement` [1] is incompatible with nullable `HTMLInputElement` [2] in property `current`.
- Or `HTMLElement` [1] is incompatible with `HTMLInputElement` [3] in property `current`.
- Or a call signature declaring the expected parameter / return type is missing in object type [4] but exists in
function type [5].
- Or object type [4] is incompatible with function type [5]. Non-callable objects are not compatible with functions.

refs.js:64:53
64| <MyNestedInput {...otherProps} ref={ref} />
Expand Down Expand Up @@ -3280,8 +3277,7 @@ Error --------------------------------------------------------------------------

Cannot create `Foo` element because in property `ref`: [incompatible-type]
- Either number [1] is incompatible with boolean [2] in property `bar` of property `current`.
- Or a call signature declaring the expected parameter / return type is missing in object type [3] but exists in
function type [4].
- Or object type [3] is incompatible with function type [4]. Non-callable objects are not compatible with functions.

refs.js:92:13
92| <Foo ref={badRef} />; // error
Expand All @@ -3307,8 +3303,7 @@ Error --------------------------------------------------------------------------
Cannot create `GenericRef1` element because in property `ref`: [incompatible-type]
- Either `HTMLElement` [1] is not a subtype of object type [2]. Class instances are not subtypes of object types;
consider rewriting object type [2] as an interface.
- Or a call signature declaring the expected parameter / return type is missing in `HTMLElement` [1] but exists in
function type [3].
- Or `HTMLElement` [1] is incompatible with function type [3]. Non-callable objects are not compatible with functions.

refs.js:105:23
105| <GenericRef1 ref={new HTMLElement()} />; // error
Expand Down
36 changes: 16 additions & 20 deletions tests/component_type/component_type.exp
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,8 @@ References:
Error ----------------------------------------------------------------------------------- class_to_component_type.js:7:1

Cannot cast `Foo` to component because: [incompatible-cast]
- Either a call signature declaring the expected parameter / return type is missing in object type [1] but exists in
function type [2].
- Either object type [1] is incompatible with function type [2]. Non-callable objects are not compatible with
functions.
- Or `Foo` [3] is incompatible with string [4] in property `current`.

class_to_component_type.js:7:1
Expand All @@ -88,8 +88,8 @@ Error --------------------------------------------------------------------------
Cannot cast `Foo` to component because: [incompatible-cast]
- Either `ImNotARefSetter` [1] is not a subtype of object type [2]. Class instances are not subtypes of object types;
consider rewriting object type [2] as an interface.
- Or a call signature declaring the expected parameter / return type is missing in `ImNotARefSetter` [1] but exists in
function type [3].
- Or `ImNotARefSetter` [1] is incompatible with function type [3]. Non-callable objects are not compatible with
functions.

class_to_component_type.js:11:1
11| Foo as component( // error again due to bad ref
Expand All @@ -112,8 +112,8 @@ Error --------------------------------------------------------------------------
Cannot declare ref because: [incompatible-type]
- Either `ImNotARefSetter` [1] is not a subtype of object type [2]. Class instances are not subtypes of object types;
consider rewriting object type [2] as an interface.
- Or a call signature declaring the expected parameter / return type is missing in `ImNotARefSetter` [1] but exists in
function type [3].
- Or `ImNotARefSetter` [1] is incompatible with function type [3]. Non-callable objects are not compatible with
functions.

The `ref` parameter must be a subtype of `React.RefSetter`.

Expand Down Expand Up @@ -216,8 +216,7 @@ Error --------------------------------------------------------------------------

Cannot cast `ComponentNarrower` to component because: [incompatible-cast]
- Either `ComponentNarrower` [1] is incompatible with `Component` [2] in property `current`.
- Or a call signature declaring the expected parameter / return type is missing in object type [3] but exists in
function type [4].
- Or object type [3] is incompatible with function type [4]. Non-callable objects are not compatible with functions.

classes_lower.js:19:1
19| ComponentNarrower as component(ref: React.RefSetter<Component>, ...any); // Error instance type is wrong
Expand Down Expand Up @@ -267,8 +266,7 @@ Error --------------------------------------------------------------------------

Cannot cast `Component` to component because: [incompatible-cast]
- Either `Component` [1] is incompatible with `Subclass` [2] in property `current`.
- Or a call signature declaring the expected parameter / return type is missing in object type [3] but exists in
function type [4].
- Or object type [3] is incompatible with function type [4]. Non-callable objects are not compatible with functions.

classes_lower.js:25:1
25| Component as component(ref: React.RefSetter<Subclass>, ...any); // Ok, Instance is covariant
Expand Down Expand Up @@ -418,8 +416,8 @@ References:
Error ------------------------------------------------------------------------------------------ create_element.js:22:41

Cannot create `C` element because in property `ref`: [incompatible-type]
- Either a call signature declaring the expected parameter / return type is missing in `React.RefObject` [1] but exists
in function type [2].
- Either `React.RefObject` [1] is incompatible with function type [2]. Non-callable objects are not compatible with
functions.
- Or `React.RefObject` [1] is incompatible with null [3].

create_element.js:22:41
Expand Down Expand Up @@ -642,8 +640,7 @@ Error --------------------------------------------------------------------------

Cannot cast `Component` to component because: [incompatible-cast]
- Either undefined [1] is incompatible with null [2] in property `current`.
- Or a call signature declaring the expected parameter / return type is missing in object type [1] but exists in
function type [3].
- Or object type [1] is incompatible with function type [3]. Non-callable objects are not compatible with functions.

function_lower.js:12:1
12| Component as component(ref: React.RefSetter<number>, ...Props); // Error
Expand Down Expand Up @@ -773,8 +770,7 @@ Error --------------------------------------------------------------------------

Cannot cast `Foo` to component because: [incompatible-cast]
- Either undefined [1] is incompatible with null [2] in property `current`.
- Or a call signature declaring the expected parameter / return type is missing in object type [1] but exists in
function type [3].
- Or object type [1] is incompatible with function type [3]. Non-callable objects are not compatible with functions.

function_to_component_type.js:8:1
8| Foo as component( // error: void ~> string
Expand All @@ -797,8 +793,8 @@ Error --------------------------------------------------------------------------
Cannot cast `Foo` to component because: [incompatible-cast]
- Either `ImNotARefSetter` [1] is not a subtype of object type [2]. Class instances are not subtypes of object types;
consider rewriting object type [2] as an interface.
- Or a call signature declaring the expected parameter / return type is missing in `ImNotARefSetter` [1] but exists in
function type [3].
- Or `ImNotARefSetter` [1] is incompatible with function type [3]. Non-callable objects are not compatible with
functions.

function_to_component_type.js:12:1
12| Foo as component( // error again due to bad ref
Expand All @@ -821,8 +817,8 @@ Error --------------------------------------------------------------------------
Cannot declare ref because: [incompatible-type]
- Either `ImNotARefSetter` [1] is not a subtype of object type [2]. Class instances are not subtypes of object types;
consider rewriting object type [2] as an interface.
- Or a call signature declaring the expected parameter / return type is missing in `ImNotARefSetter` [1] but exists in
function type [3].
- Or `ImNotARefSetter` [1] is incompatible with function type [3]. Non-callable objects are not compatible with
functions.

The `ref` parameter must be a subtype of `React.RefSetter`.

Expand Down
4 changes: 2 additions & 2 deletions tests/function/function.exp
Original file line number Diff line number Diff line change
Expand Up @@ -303,8 +303,8 @@ References:

Error ---------------------------------------------------------------------------------------------------- apply.js:34:2

Cannot call `test.bind.apply` because a call signature declaring the expected parameter / return type is missing in
function type [1] but exists in function type [2]. [prop-missing]
Cannot call `test.bind.apply` because function type [1] is incompatible with function type [2]. Non-callable objects are
not compatible with functions. [incompatible-call]

apply.js:34:2
34| (test.bind.apply(test, [0, 123]): (b: number) => number);
Expand Down
4 changes: 2 additions & 2 deletions tests/node_tests/node_tests.exp
Original file line number Diff line number Diff line change
Expand Up @@ -281,8 +281,8 @@ References:
Error -------------------------------------------------------------------------------------------------- dns/dns.js:29:5

Cannot call `dns.lookup` because: [incompatible-call]
- Either a call signature declaring the expected parameter / return type is missing in object literal [1] but exists in
function type [2].
- Either object literal [1] is incompatible with function type [2]. Non-callable objects are not compatible with
functions.
- Or function type [3] requires another argument from call of method `lookup` [4].

dns/dns.js:29:5
Expand Down
4 changes: 2 additions & 2 deletions tests/object_freeze/object_freeze.exp
Original file line number Diff line number Diff line change
Expand Up @@ -366,8 +366,8 @@ References:

Error ------------------------------------------------------------------------------------------- object_freeze.js:29:38

Cannot assign `baz` to `frozenObjectWithSpread.constructor` because a call signature declaring the expected parameter /
return type is missing in object literal [1] but exists in function [2]. [prop-missing]
Cannot assign `baz` to `frozenObjectWithSpread.constructor` because object literal [1] is incompatible with
function [2]. Non-callable objects are not compatible with functions. [incompatible-type]

object_freeze.js:29:38
29| frozenObjectWithSpread.constructor = baz; // error baz not a function
Expand Down
12 changes: 4 additions & 8 deletions tests/react/react.exp
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,7 @@ Error --------------------------------------------------------------------------
Cannot cast `y` to `React.RefSetter` because: [incompatible-cast]
- Either boolean [1] is incompatible with number [2] in property `current`.
- Or boolean [1] is incompatible with string [3] in property `current`.
- Or a call signature declaring the expected parameter / return type is missing in object type [4] but exists in
function type [5].
- Or object type [4] is incompatible with function type [5]. Non-callable objects are not compatible with functions.

contravariant_refsetter.js:12:2
12| (y: React.RefSetter<number | string | boolean>); // ERROR
Expand Down Expand Up @@ -1793,8 +1792,7 @@ References:
Error ------------------------------------------------------------------------------------------ useEffect_hook.js:22:30

Cannot call `React.useEffect` with async function bound to `create` because in the return value: [incompatible-call]
- Either a call signature declaring the expected parameter / return type is missing in `Promise` [1] but exists in
function type [2].
- Either `Promise` [1] is incompatible with function type [2]. Non-callable objects are not compatible with functions.
- Or `Promise` [1] is incompatible with undefined [3].

useEffect_hook.js:22:30
Expand Down Expand Up @@ -1855,8 +1853,7 @@ Error --------------------------------------------------------------------------

Cannot call `React.useImperativeHandle` with `ref` bound to `ref` because: [incompatible-call]
- Either property `focus` is missing in object literal [1] but exists in `Interface` [2] in property `current`.
- Or a call signature declaring the expected parameter / return type is missing in object type [3] but exists in
function type [4].
- Or object type [3] is incompatible with function type [4]. Non-callable objects are not compatible with functions.

useImperativeHandle_hook.js:29:29
29| React.useImperativeHandle(ref, () => ({})); // Error: inexact object literal is incompatible with exact Interface
Expand Down Expand Up @@ -1954,8 +1951,7 @@ Error --------------------------------------------------------------------------

Cannot call `React.useLayoutEffect` with async function bound to `create` because in the return value:
[incompatible-call]
- Either a call signature declaring the expected parameter / return type is missing in `Promise` [1] but exists in
function type [2].
- Either `Promise` [1] is incompatible with function type [2]. Non-callable objects are not compatible with functions.
- Or `Promise` [1] is incompatible with undefined [3].

useLayoutEffect_hook.js:22:36
Expand Down
Loading

0 comments on commit 085609e

Please sign in to comment.