Skip to content

Commit

Permalink
[flow] More understandable error for FunT ~> ObjT
Browse files Browse the repository at this point in the history
Summary:
In vast majority of cases `FunT ~> ObjT` should fail, unless the function has some statics which will make it compatible with some inexact object types. However, we are paying the cost of including likely irrelevant details of missing props in statics, while it's much simpler to just say that "function without statics is incompatible with object".

This is especially harmful for `React.RefSetter` related errors, when there are incompatibilities, we often see functions setters flowing into object refs.

In this diff, I use the heuristic that if we see empty statics object flowing into non-empty object, we will just say "function without statics is incompatible with object".

Changelog: [errors] When a function without statics is passed to a place that expects inexact objects, the error will just say "function without statics is incompatible with object", instead of listing all the missing props in statics in errors. Error code might change.

Reviewed By: panagosg7

Differential Revision: D67909545

fbshipit-source-id: e16cd83bb155073dc1f7f8a2019d2991104c7eef
  • Loading branch information
SamChou19815 authored and facebook-github-bot committed Jan 8, 2025
1 parent 085609e commit 8d7f63b
Show file tree
Hide file tree
Showing 14 changed files with 87 additions and 66 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 @@ -788,6 +788,7 @@ let map_loc_of_explanation (f : 'a -> 'b) =
{ name; declaration = f declaration; providers = Base.List.map ~f providers }
| ExplanationConcreteEnumCasting { representation_type; casting_syntax } ->
ExplanationConcreteEnumCasting { representation_type; casting_syntax }
| ExplanationFunctionsWithStaticsToObject -> ExplanationFunctionsWithStaticsToObject
| ExplanationMultiplatform -> ExplanationMultiplatform
| ExplanationNonCallableObjectToFunction -> ExplanationNonCallableObjectToFunction
| ExplanationPropertyInvariantTyping -> ExplanationPropertyInvariantTyping
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 @@ -1091,6 +1091,8 @@ let to_printable_error :
text
"https://flow.org/en/docs/faq/#why-cant-i-pass-an-arraystring-to-a-function-that-takes-an-arraystring-number";
]
| ExplanationFunctionsWithStaticsToObject ->
[text "Functions without statics are not compatible with objects"]
| ExplanationMultiplatform ->
[
text "Read the docs on Flow's multi-platform support for more information: ";
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 @@ -179,6 +179,7 @@ type 'loc explanation =
representation_type: string;
casting_syntax: Options.CastingSyntax.t;
}
| ExplanationFunctionsWithStaticsToObject
| ExplanationMultiplatform
| ExplanationNonCallableObjectToFunction
| ExplanationPropertyInvariantTyping
Expand Down
42 changes: 29 additions & 13 deletions src/typing/flow_js_utils.ml
Original file line number Diff line number Diff line change
Expand Up @@ -807,19 +807,35 @@ let quick_error_fun_as_obj cx ~use_op reason statics reason_o props =
not (optional || is_function_prototype x || NameUtils.Map.mem x statics_own_props))
props
in
NameUtils.Map.iter
(fun x _ ->
let use_op =
Frame (PropertyCompatibility { prop = Some x; lower = reason; upper = reason_o }, use_op)
in
let reason_prop = update_desc_reason (fun desc -> RPropertyOf (x, desc)) reason_o in
let err =
Error_message.EPropNotFound
{ prop_name = Some x; reason_prop; reason_obj = reason; use_op; suggestion = None }
in
add_output cx err)
props_not_found;
not (NameUtils.Map.is_empty props_not_found)
if NameUtils.Map.is_empty props_not_found then
false
else if NameUtils.Map.is_empty statics_own_props && not (NameUtils.Map.is_empty props) then (
let error_message =
Error_message.EIncompatibleWithUseOp
{
reason_lower = reason;
reason_upper = reason_o;
use_op;
explanation = Some Flow_intermediate_error_types.ExplanationFunctionsWithStaticsToObject;
}
in
add_output cx error_message;
true
) else (
NameUtils.Map.iter
(fun x _ ->
let use_op =
Frame (PropertyCompatibility { prop = Some x; lower = reason; upper = reason_o }, use_op)
in
let reason_prop = update_desc_reason (fun desc -> RPropertyOf (x, desc)) reason_o in
let err =
Error_message.EPropNotFound
{ prop_name = Some x; reason_prop; reason_obj = reason; use_op; suggestion = None }
in
add_output cx err)
props_not_found;
true
)
| None -> false

(** Instantiation *)
Expand Down
4 changes: 2 additions & 2 deletions tests/call_properties/call_properties.exp
Original file line number Diff line number Diff line change
Expand Up @@ -227,8 +227,8 @@ References:

Error -------------------------------------------------------------------------------------------------------- E.js:2:32

Cannot assign function to `a` because property `someProp` is missing in function [1] but exists in object type [2].
[prop-missing]
Cannot assign function to `a` because function [1] is incompatible with object type [2]. Functions without statics are
not compatible with objects. [incompatible-type]

E.js:2:32
2| var a : { someProp: number } = function () {};
Expand Down
13 changes: 7 additions & 6 deletions tests/component_syntax/component_syntax.exp
Original file line number Diff line number Diff line change
Expand Up @@ -2729,7 +2729,7 @@ Error --------------------------------------------------------------------------

Cannot create `meta` element because in property `ref`: [incompatible-type]
- Either `HTMLAnchorElement` [1] is incompatible with `HTMLMetaElement` [2] in the first parameter.
- Or property `current` is missing in function type [3] but exists in object type [4].
- Or function type [3] is incompatible with object type [4]. Functions without statics are not compatible with objects.

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

Cannot declare ref because: [incompatible-type]
- Either component Reffed [1] is incompatible with null [2] in the first parameter.
- Or property `current` is missing in function type [3] but exists in object type [4].
- Or function type [3] is incompatible with object type [4]. Functions without statics are not compatible with objects.

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

Expand Down Expand Up @@ -3142,7 +3142,8 @@ References:
Error ----------------------------------------------------------------------------------------------------- refs.js:17:2

Cannot cast `Baz` to component because: [incompatible-cast]
- Either property `current` is missing in function type [1] but exists in object type [2].
- Either function type [1] is incompatible with object type [2]. Functions without statics are not compatible with
objects.
- Or component Reffed [3] is incompatible with string [4] in the first parameter.

refs.js:17:2
Expand Down Expand Up @@ -3195,7 +3196,7 @@ Error --------------------------------------------------------------------------
Cannot create `MyNestedInput` element because in property `ref`: [incompatible-type]
- Either `HTMLElement` [1] is incompatible with nullable `HTMLInputElement` [2] in the first parameter.
- Or `HTMLElement` [1] is incompatible with `HTMLInputElement` [3] in the first parameter.
- Or property `current` is missing in function type [4] but exists in object type [5].
- Or function type [4] is incompatible with object type [5]. Functions without statics are not compatible with objects.

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

Cannot create `Foo` element because in property `ref`: [incompatible-type]
- Either number [1] is incompatible with boolean [2] in property `bar` of the first parameter.
- Or property `current` is missing in function type [3] but exists in object type [4].
- Or function type [3] is incompatible with object type [4]. Functions without statics are not compatible with objects.

refs.js:92:13
92| <Foo ref={badRef} />; // error
Expand Down Expand Up @@ -3321,7 +3322,7 @@ References:
Error --------------------------------------------------------------------------------------------------- refs.js:108:23

Cannot create `GenericRef2` element because in property `ref`: [incompatible-type]
- Either property `current` is missing in function [1] but exists in object type [2].
- Either function [1] is incompatible with object type [2]. Functions without statics are not compatible with objects.
- Or array type [3] is incompatible with string [4] in the first parameter.

refs.js:108:23
Expand Down
11 changes: 6 additions & 5 deletions tests/component_type/component_type.exp
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ References:
Error ----------------------------------------------------------------------------------- class_to_component_type.js:7:1

Cannot cast `Foo` to component because: [incompatible-cast]
- Either property `current` is missing in function type [1] but exists in object type [2].
- Either function type [1] is incompatible with object type [2]. Functions without statics are not compatible with
objects.
- Or `Foo` [3] is incompatible with string [4] in the first parameter.

class_to_component_type.js:7:1
Expand Down Expand Up @@ -191,7 +192,7 @@ Error --------------------------------------------------------------------------

Cannot cast `ComponentNarrower` to component because: [incompatible-cast]
- Either `ComponentNarrower` [1] is incompatible with `Component` [2] in the first parameter.
- Or property `current` is missing in function type [3] but exists in object type [4].
- Or function type [3] is incompatible with object type [4]. Functions without statics are not compatible with objects.

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

Cannot cast `Component` to component because: [incompatible-cast]
- Either `Component` [1] is incompatible with `Subclass` [2] in the first parameter.
- Or property `current` is missing in function type [3] but exists in object type [4].
- Or function type [3] is incompatible with object type [4]. Functions without statics are not compatible with objects.

classes_lower.js:25:1
25| Component as component(ref: React.RefSetter<Subclass>, ...any); // Ok, Instance is covariant
Expand Down Expand Up @@ -618,7 +619,7 @@ Error --------------------------------------------------------------------------

Cannot cast `Component` to component because: [incompatible-cast]
- Either undefined [1] is incompatible with null [2] in the first parameter.
- Or property `current` is missing in function type [1] but exists in object type [3].
- Or function type [1] is incompatible with object type [3]. Functions without statics are not compatible with objects.

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

Cannot cast `Foo` to component because: [incompatible-cast]
- Either undefined [1] is incompatible with null [2] in the first parameter.
- Or property `current` is missing in function type [1] but exists in object type [3].
- Or function type [1] is incompatible with object type [3]. Functions without statics are not compatible with objects.

function_to_component_type.js:8:1
8| Foo as component( // error: void ~> string
Expand Down
12 changes: 6 additions & 6 deletions tests/function_statics/function_statics.exp
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,8 @@ References:

Error --------------------------------------------------------------------------------------------------- arrow.js:33:43

Cannot assign function to `f` because property `a` is missing in function [1] but exists in object type [2].
[prop-missing]
Cannot assign function to `f` because function [1] is incompatible with object type [2]. Functions without statics are
not compatible with objects. [incompatible-type]

arrow.js:33:43
33| const f: {(): number, a: string, ...} = () => 1; // ERROR on assignment
Expand Down Expand Up @@ -255,8 +255,8 @@ References:

Error ------------------------------------------------------------------------------------- function_expression.js:41:43

Cannot assign function to `f` because property `a` is missing in function [1] but exists in object type [2].
[prop-missing]
Cannot assign function to `f` because function [1] is incompatible with object type [2]. Functions without statics are
not compatible with objects. [incompatible-type]

function_expression.js:41:43
41| const f: {(): number, a: string, ...} = function () { return 1}; // ERROR on assignment
Expand Down Expand Up @@ -298,8 +298,8 @@ References:

Error ---------------------------------------------------------------------------------- used_as_callable_object.js:33:2

Cannot cast `add` to `InexactCallableObj` because property `bar` is missing in function [1] but exists in
`InexactCallableObj` [2]. [prop-missing]
Cannot cast `add` to `InexactCallableObj` because function [1] is incompatible with `InexactCallableObj` [2]. Functions
without statics are not compatible with objects. [incompatible-cast]

used_as_callable_object.js:33:2
33| (add: InexactCallableObj); // error prop 'bar' missing
Expand Down
37 changes: 17 additions & 20 deletions tests/new_react/new_react.exp
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ References:

Error -------------------------------------------------------------------------------------------------- classes.js:23:3

Cannot extend `React.Component` [1] with `Foo` because property `y_` is missing in function type [2] but exists in
object type [3] in the first parameter of property `setState`. [prop-missing]
Cannot extend `React.Component` [1] with `Foo` because property `y_` is missing in object type [2] but exists in object
type [3] in the first parameter of property `setState`. [prop-missing]

classes.js:23:3
23| setState(o: { y_: string }): void { }
Expand All @@ -53,39 +53,36 @@ References:
classes.js:7:19
7| class Foo extends React.Component<Props, State> {
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ [1]
<BUILTINS>/react.js:39:49
39| partialState: ?$ReadOnly<Partial<State>> | ((State, Props) => ?$ReadOnly<Partial<State>>),
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ [2]
classes.js:7:42
7| class Foo extends React.Component<Props, State> {
^^^^^ [2]
classes.js:23:15
23| setState(o: { y_: string }): void { }
^^^^^^^^^^^^^^ [3]


Error -------------------------------------------------------------------------------------------------- classes.js:23:3
Error ------------------------------------------------------------------------------------------------- classes.js:23:15

Cannot extend `React.Component` [1] with `Foo` because property `y_` is missing in object type [2] but exists in object
type [3] in the first parameter of property `setState`. [prop-missing]
Cannot extend `React.Component` [1] with `Foo` because object type [2] is incompatible with nullable `$ReadOnly` [3] in
the first parameter of property `setState`. [incompatible-extend]

classes.js:23:3
classes.js:23:15
23| setState(o: { y_: string }): void { }
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
^^^^^^^^^^^^^^ [2]

References:
classes.js:7:19
7| class Foo extends React.Component<Props, State> {
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ [1]
classes.js:7:42
7| class Foo extends React.Component<Props, State> {
^^^^^ [2]
classes.js:23:15
23| setState(o: { y_: string }): void { }
^^^^^^^^^^^^^^ [3]
<BUILTINS>/react.js:39:19
39| partialState: ?$ReadOnly<Partial<State>> | ((State, Props) => ?$ReadOnly<Partial<State>>),
^^^^^^^^^^^^^^^^^^^^^^^^^^ [3]


Error ------------------------------------------------------------------------------------------------- classes.js:23:15

Cannot extend `React.Component` [1] with `Foo` because object type [2] is incompatible with nullable `$ReadOnly` [3] in
the first parameter of property `setState`. [incompatible-extend]
Cannot extend `React.Component` [1] with `Foo` because object type [2] is incompatible with function type [3] in the
first parameter of property `setState`. Functions without statics are not compatible with objects. [incompatible-extend]

classes.js:23:15
23| setState(o: { y_: string }): void { }
Expand All @@ -95,9 +92,9 @@ References:
classes.js:7:19
7| class Foo extends React.Component<Props, State> {
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ [1]
<BUILTINS>/react.js:39:19
<BUILTINS>/react.js:39:49
39| partialState: ?$ReadOnly<Partial<State>> | ((State, Props) => ?$ReadOnly<Partial<State>>),
^^^^^^^^^^^^^^^^^^^^^^^^^^ [3]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ [3]


Error ------------------------------------------------------------------------------------------------- classes.js:15:21
Expand Down
12 changes: 6 additions & 6 deletions tests/react/react.exp
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ Error --------------------------------------------------------------------------
Cannot cast `y` to `React.RefSetter` because: [incompatible-cast]
- Either boolean [1] is incompatible with number [2] in the first parameter.
- Or boolean [1] is incompatible with string [3] in the first parameter.
- Or property `current` is missing in function type [4] but exists in object type [5].
- Or function type [4] is incompatible with object type [5]. Functions without statics are not compatible with objects.

contravariant_refsetter.js:12:2
12| (y: React.RefSetter<number | string | boolean>); // ERROR
Expand Down Expand Up @@ -1376,7 +1376,7 @@ Error --------------------------------------------------------------------------

Cannot create `Foo` element because in property `ref`: [incompatible-type]
- Either number [1] is incompatible with `Foo` [2] in the first parameter.
- Or property `current` is missing in function [3] but exists in object type [4].
- Or function [3] is incompatible with object type [4]. Functions without statics are not compatible with objects.

ref.js:10:11
10| <Foo ref={(foo: number) => {}} />; // Error: `Foo` is not a `number`.
Expand Down Expand Up @@ -1456,7 +1456,7 @@ Error --------------------------------------------------------------------------

Cannot create `FooExact` element because in property `ref`: [incompatible-type]
- Either number [1] is incompatible with `FooExact` [2] in the first parameter.
- Or property `current` is missing in function [3] but exists in object type [4].
- Or function [3] is incompatible with object type [4]. Functions without statics are not compatible with objects.

ref.js:21:16
21| <FooExact ref={(foo: number) => {}} />; // Error: `FooExact` is not a `number`.
Expand All @@ -1478,7 +1478,7 @@ Error --------------------------------------------------------------------------

Cannot create `FooExact` element because in property `ref`: [incompatible-type]
- Either `FooExact` [1] is incompatible with null [2] in the first parameter.
- Or property `current` is missing in function [3] but exists in object type [4].
- Or function [3] is incompatible with object type [4]. Functions without statics are not compatible with objects.

ref.js:22:16
22| <FooExact ref={(foo: FooExact) => {}} />; // Error: `FooExact` may be null.
Expand All @@ -1500,7 +1500,7 @@ Error --------------------------------------------------------------------------

Cannot create `FooExact` element because in property `ref`: [incompatible-type]
- Either `FooExact` [1] is incompatible with `Bar` [2] in the first parameter.
- Or property `current` is missing in function [3] but exists in object type [4].
- Or function [3] is incompatible with object type [4]. Functions without statics are not compatible with objects.

ref.js:24:16
24| <FooExact ref={(foo: Bar | null) => {}} />; // Error: `FooExact` is not `Bar`.
Expand Down Expand Up @@ -1878,7 +1878,7 @@ Error --------------------------------------------------------------------------

Cannot call `React.useImperativeHandle` with `refSetter` bound to `ref` because: [incompatible-call]
- Either property `focus` is missing in object literal [1] but exists in `Interface` [2] in the first parameter.
- Or property `current` is missing in function [3] but exists in object type [4].
- Or function [3] is incompatible with object type [4]. Functions without statics are not compatible with objects.

useImperativeHandle_hook.js:32:29
32| React.useImperativeHandle(refSetter, () => ({})); // Error: inexact object literal is incompatible with exact Interface
Expand Down
2 changes: 1 addition & 1 deletion tests/react_16_3/react_16_3.exp
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ Error --------------------------------------------------------------------------

Cannot create `FancyButton` element because in property `ref`: [incompatible-type]
- Either `HTMLButtonElement` [1] is incompatible with `HTMLDivElement` [2] in the first parameter.
- Or property `current` is missing in function [3] but exists in object type [4].
- Or function [3] is incompatible with object type [4]. Functions without statics are not compatible with objects.

forwardRef.js:22:38
22| const _g = <FancyButton foo={3} ref={(x: null | HTMLDivElement) => x} />; // Incorrect ref type
Expand Down
Loading

0 comments on commit 8d7f63b

Please sign in to comment.