From 085609e964cee57bea1934e7ad4b12b40850b2f3 Mon Sep 17 00:00:00 2001 From: Sam Zhou Date: Tue, 7 Jan 2025 16:31:15 -0800 Subject: [PATCH] [flow] More understandable error for `ObjT ~> FunT` 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 --- src/typing/errors/error_message.ml | 1 + src/typing/errors/flow_intermediate_error.ml | 2 ++ .../errors/flow_intermediate_error_types.ml | 1 + src/typing/subtyping_kit.ml | 22 ++++++++---- tests/call_properties/call_properties.exp | 8 ++--- tests/component_syntax/component_syntax.exp | 23 +++++------- tests/component_type/component_type.exp | 36 +++++++++---------- tests/function/function.exp | 4 +-- tests/node_tests/node_tests.exp | 4 +-- tests/object_freeze/object_freeze.exp | 4 +-- tests/react/react.exp | 12 +++---- tests/react_16_3/react_16_3.exp | 8 ++--- tests/react_16_6/react_16_6.exp | 3 +- tests/react_children/react_children.exp | 7 ++-- tests/rec/rec.exp | 4 +-- 15 files changed, 68 insertions(+), 71 deletions(-) diff --git a/src/typing/errors/error_message.ml b/src/typing/errors/error_message.ml index b0f1f39edad..cf40a19beec 100644 --- a/src/typing/errors/error_message.ml +++ b/src/typing/errors/error_message.ml @@ -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) diff --git a/src/typing/errors/flow_intermediate_error.ml b/src/typing/errors/flow_intermediate_error.ml index 2f23f4ed957..4e239727e9f 100644 --- a/src/typing/errors/flow_intermediate_error.ml +++ b/src/typing/errors/flow_intermediate_error.ml @@ -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 "; diff --git a/src/typing/errors/flow_intermediate_error_types.ml b/src/typing/errors/flow_intermediate_error_types.ml index f486b6b4c51..2dc13907ec8 100644 --- a/src/typing/errors/flow_intermediate_error_types.ml +++ b/src/typing/errors/flow_intermediate_error_types.ml @@ -180,6 +180,7 @@ type 'loc explanation = casting_syntax: Options.CastingSyntax.t; } | ExplanationMultiplatform + | ExplanationNonCallableObjectToFunction | ExplanationPropertyInvariantTyping | ExplanationReactComponentPropsDeepReadOnly of 'loc | ExplanationReactComponentRefRequirement diff --git a/src/typing/subtyping_kit.ml b/src/typing/subtyping_kit.ml index 00d799cd589..764cd280619 100644 --- a/src/typing/subtyping_kit.ml +++ b/src/typing/subtyping_kit.ml @@ -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 *) diff --git a/tests/call_properties/call_properties.exp b/tests/call_properties/call_properties.exp index 80e460295d0..310785bdb31 100644 --- a/tests/call_properties/call_properties.exp +++ b/tests/call_properties/call_properties.exp @@ -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; @@ -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 diff --git a/tests/component_syntax/component_syntax.exp b/tests/component_syntax/component_syntax.exp index c9af326a461..c751a89d906 100644 --- a/tests/component_syntax/component_syntax.exp +++ b/tests/component_syntax/component_syntax.exp @@ -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 = ; // error @@ -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`. @@ -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, ...empty)); // err @@ -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 @@ -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| @@ -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| ; // error @@ -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| ; // error diff --git a/tests/component_type/component_type.exp b/tests/component_type/component_type.exp index 0b67665efd4..559a260d847 100644 --- a/tests/component_type/component_type.exp +++ b/tests/component_type/component_type.exp @@ -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 @@ -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 @@ -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`. @@ -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, ...any); // Error instance type is wrong @@ -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, ...any); // Ok, Instance is covariant @@ -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 @@ -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, ...Props); // Error @@ -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 @@ -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 @@ -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`. diff --git a/tests/function/function.exp b/tests/function/function.exp index e6b5adb4392..c66e35145ba 100644 --- a/tests/function/function.exp +++ b/tests/function/function.exp @@ -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); diff --git a/tests/node_tests/node_tests.exp b/tests/node_tests/node_tests.exp index bff827fc7dc..6f55d37c989 100644 --- a/tests/node_tests/node_tests.exp +++ b/tests/node_tests/node_tests.exp @@ -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 diff --git a/tests/object_freeze/object_freeze.exp b/tests/object_freeze/object_freeze.exp index 05a9834e92e..e6d87934d7e 100644 --- a/tests/object_freeze/object_freeze.exp +++ b/tests/object_freeze/object_freeze.exp @@ -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 diff --git a/tests/react/react.exp b/tests/react/react.exp index d3ca3f206e0..bc524761647 100644 --- a/tests/react/react.exp +++ b/tests/react/react.exp @@ -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); // ERROR @@ -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 @@ -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 @@ -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 diff --git a/tests/react_16_3/react_16_3.exp b/tests/react_16_3/react_16_3.exp index 34115b4a8dd..9aa47602dd9 100644 --- a/tests/react_16_3/react_16_3.exp +++ b/tests/react_16_3/react_16_3.exp @@ -37,8 +37,8 @@ References: Error ---------------------------------------------------------------------------------------------- forwardRef.js:19:38 Cannot create `FancyButton` 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]. forwardRef.js:19:38 @@ -82,8 +82,8 @@ References: Error ---------------------------------------------------------------------------------------------- forwardRef.js:42:35 Cannot create `UnionRef` 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]. forwardRef.js:42:35 diff --git a/tests/react_16_6/react_16_6.exp b/tests/react_16_6/react_16_6.exp index a3b1bb1c150..5e5b8a5294f 100644 --- a/tests/react_16_6/react_16_6.exp +++ b/tests/react_16_6/react_16_6.exp @@ -356,8 +356,7 @@ Error -------------------------------------------------------------------------- Cannot call `React.forwardRef` with `Demo` bound to `render` because in the second parameter: [incompatible-call] - Either property `current` is write-only in object type [1] but readable in object type [2]. - - 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. memo_ref.js:14:42 14| const Memo = React.memo(React.forwardRef(Demo)); diff --git a/tests/react_children/react_children.exp b/tests/react_children/react_children.exp index c2dfc920af6..5db4ade4aaf 100644 --- a/tests/react_children/react_children.exp +++ b/tests/react_children/react_children.exp @@ -641,8 +641,8 @@ References: Error ------------------------------------------------------------------------------------------------------ fun.js:56:3 -Cannot create `Fun` element because a call signature declaring the expected parameter / return type is missing in -`React.Element` [1] but exists in `Fn` [2] in property `children`. [prop-missing] +Cannot create `Fun` element because `React.Element` [1] is incompatible with `Fn` [2] in property `children`. +Non-callable objects are not compatible with functions. [incompatible-type] fun.js:56:3 56| @@ -1031,8 +1031,7 @@ References: Error ----------------------------------------------------------------------------------------------------- fun.js:149:3 Cannot create `FunArray` element because in property `children`: [incompatible-type] - - Either a call signature declaring the expected parameter / return type is missing in `React.Element` [1] but exists - in `Fn` [2]. + - Either `React.Element` [1] is incompatible with `Fn` [2]. Non-callable objects are not compatible with functions. - Or `React.Element` [1] is incompatible with array type [3]. fun.js:149:3 diff --git a/tests/rec/rec.exp b/tests/rec/rec.exp index 0848b55d51a..52d776bc8fd 100644 --- a/tests/rec/rec.exp +++ b/tests/rec/rec.exp @@ -358,8 +358,8 @@ References: Error --------------------------------------------------------------------------------------------------- test3.js:13:10 -Cannot return `x` because a call signature declaring the expected parameter / return type is missing in `Q` [1] but -exists in `P` [2] in the return value. [prop-missing] +Cannot return `x` because `Q` [1] is incompatible with `P` [2] in the return value. Non-callable objects are not +compatible with functions. [incompatible-return] test3.js:13:10 13| return x; // terminate despite expanding types, error