Skip to content

Commit 081a517

Browse files
authored
[Super errors] Make the second worst ReasonReact error a bit shorter (#2612)
* [Super errors] Make the second worst ReasonReact error a bit shorter The "state escape scope" one. This one shouldn't catch any false positives, so we can safely remove the original type clash error. Also, we only detected state escaping scope, not that it's actually related to ReasonReact.componentSpec. So every state escape scope error ended up giving a ReasonReact error correction tip. This is fixed now. Sorry about that. * [Super errors] Clean up trace walking logic
1 parent 6ac6d63 commit 081a517

File tree

3 files changed

+81
-35
lines changed

3 files changed

+81
-35
lines changed

jscomp/super_errors/super_reason_react.ml

Lines changed: 62 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -8,32 +8,32 @@ let rec drill_through_tlink_and_tsubst t =
88
match t.desc with
99
| Tlink t
1010
| Tsubst t -> drill_through_tlink_and_tsubst t
11-
| t -> t
11+
| _ -> t
1212

1313
let is_weak_type_after_drilling t =
1414
match drill_through_tlink_and_tsubst t with
15-
| Tvar _ -> true
15+
| {desc = Tvar _} -> true
1616
| _ -> false
1717

1818
let component_spec_weak_type_variables t =
1919
match drill_through_tlink_and_tsubst t with
2020
(* ReasonReact <=0.3.4 *)
21-
| Tconstr (
21+
| {desc = Tconstr (
2222
Pdot ((Pident {name = "ReasonReact"}), "componentSpec", _),
2323
[state; _initial_state; retained_props; _initial_retained_props; action],
2424
_
25-
) ->
25+
)} ->
2626
(
2727
state |> is_weak_type_after_drilling,
2828
retained_props |> is_weak_type_after_drilling,
2929
action |> is_weak_type_after_drilling
3030
)
3131
(* future ReasonReact version with retainedProps removed *)
32-
| Tconstr (
32+
| {desc = Tconstr (
3333
Pdot ((Pident {name = "ReasonReact"}), "componentSpec", _),
3434
[state; _initial_state; action],
3535
_
36-
) ->
36+
)} ->
3737
(
3838
state |> is_weak_type_after_drilling,
3939
false,
@@ -57,31 +57,73 @@ let component_spec_weak_type_variables_in_module_type (mty : Types.module_type)
5757
)
5858
| _ -> []
5959

60-
(* recursively drill down the types (first item is the type alias, if any. Second is the content of the alias) *)
61-
let rec get_to_bottom_of_aliases f = function
62-
| (_alias1, type1) :: (_alias2, type2) :: rest ->
63-
begin match get_to_bottom_of_aliases f rest with
64-
| false -> f (type1, type2)
65-
| true -> true
66-
end
60+
(* `trace` is a funny data structure. It's an always even list of tuples. This error:
61+
this is foo (aliased as array(int)), wanted bar (aliased as array(string))
62+
the incompatible part: int vs string
63+
gives the following `trace` data structure:
64+
[
65+
(foo, array(int)),
66+
(bar, array(string)),
67+
(_, int),
68+
(_, string)
69+
]
70+
*)
71+
(* recursively walk the trace from right to left, calling f and checking if f matches part of the trace *)
72+
let check_each_trace_chunk_bottom_up f = fun t ->
73+
let t_flipped = List.rev t in
74+
let rec check f = function
75+
(* we flipped the trace, so instead of [t1, t2, t3, t4, ...] it's [t4, t3, ...] *)
76+
| (_alias2, type2) :: (_alias1, type1) :: rest ->
77+
if f (type1, type2) then true
78+
else check f rest
6779
| _ -> false
80+
in
81+
check f t_flipped
6882

69-
let state_escape_scope = get_to_bottom_of_aliases (function
83+
84+
let state_escape_scope = check_each_trace_chunk_bottom_up (function
7085
(* https://github.com/BuckleScript/ocaml/blob/ddf5a739cc0978dab5e553443825791ba7b0cef9/typing/printtyp.ml?#L1348 *)
7186
(* so apparently that's the logic for detecting "the constructor out of scope" error *)
7287
| ({desc = Tconstr (p, _, _)}, {desc = Tvar _; level})
7388
when level < Path.binding_time p -> true
7489
| _ -> false
7590
)
7691

77-
let is_array_wanted_reactElement = get_to_bottom_of_aliases (function
78-
| ({desc = Tconstr (path1, _, _)}, {desc = Tconstr (path2, _, _)})
79-
when Path.last path1 = "array" && Path.last path2 = "reactElement" -> true
92+
let trace_both_component_spec = check_each_trace_chunk_bottom_up (function
93+
| ({desc = Tconstr (
94+
(Pdot ((Pident {name = "ReasonReact"}), "componentSpec", _)),
95+
([state1; _; _; _; action1] | [state1; _; action1]),
96+
_
97+
)},
98+
{desc = Tconstr (
99+
(Pdot ((Pident {name = "ReasonReact"}), "componentSpec", _)),
100+
([state2; _; _; _; action2] | [state2; _; action2]),
101+
_
102+
)})
103+
-> true
80104
| _ -> false
81105
)
82106

83-
let is_componentSpec_wanted_reactElement = get_to_bottom_of_aliases (function
84-
| ({desc = Tconstr (path1, _, _)}, {desc = Tconstr (path2, _, _)})
85-
when Path.last path1 = "componentSpec" && Path.last path2 = "reactElement" -> true
107+
let is_array_wanted_react_element = check_each_trace_chunk_bottom_up (function
108+
| ({desc = Tconstr (path1, _, _)},
109+
{desc = Tconstr (
110+
(Pdot ((Pident {name = "ReasonReact"}), "reactElement", _)),
111+
_,
112+
_
113+
)}) when Path.last path1 = "array"-> true
114+
| _ -> false
115+
)
116+
117+
let is_component_spec_wanted_react_element = check_each_trace_chunk_bottom_up (function
118+
| ({desc = Tconstr (
119+
(Pdot ((Pident {name = "ReasonReact"}), "componentSpec", _)),
120+
([state1; _; _; _; action1] | [state1; _; action1]),
121+
_
122+
)},
123+
{desc = Tconstr (
124+
(Pdot ((Pident {name = "ReasonReact"}), "reactElement", _)),
125+
_,
126+
_
127+
)}) -> true
86128
| _ -> false
87129
)

jscomp/super_errors/super_reason_react.mli

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,11 @@ val component_spec_weak_type_variables_in_module_type: Types.module_type -> (boo
77
val state_escape_scope: (Types.type_expr * Types.type_expr) list -> bool
88
(** Used by super_typecore when we detect the message "The type constructor state would escape its scope" *)
99

10-
val is_array_wanted_reactElement: (Types.type_expr * Types.type_expr) list -> bool
10+
val trace_both_component_spec: (Types.type_expr * Types.type_expr) list -> bool
11+
(** Used by super_typecore when we detect the message "The type constructor state would escape its scope" *)
12+
13+
val is_array_wanted_react_element: (Types.type_expr * Types.type_expr) list -> bool
1114
(** Used by super_typecore when we detect the message "This has type array but expected reactElement" *)
1215

13-
val is_componentSpec_wanted_reactElement: (Types.type_expr * Types.type_expr) list -> bool
16+
val is_component_spec_wanted_react_element: (Types.type_expr * Types.type_expr) list -> bool
1417
(** Used by super_typecore when we detect the message "This has type componentSpec but expected reactElement" *)

jscomp/super_errors/super_typecore.ml

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -104,25 +104,26 @@ let check_bs_arity_mismatch ppf trace =
104104
let print_expr_type_clash env trace ppf =
105105
(* this is the most frequent error. Do whatever we can to provide specific
106106
guidance to this generic error before giving up *)
107-
if Super_reason_react.state_escape_scope trace then
107+
if Super_reason_react.state_escape_scope trace && Super_reason_react.trace_both_component_spec trace then
108108
fprintf ppf "@[<v>\
109109
@[@{<info>Is this a ReasonReact reducerComponent or component with retained props?@}@ \
110-
If so, is the type for state, retained props or action declared _after_ the component declaration?@ \
111-
Moving these types above the component declaration should resolve this!@]@,@,\
112-
@[@{<info>Here's the original error message@}@]@,\
110+
If so, is the type for state, retained props or action declared _after_@ the component declaration?@ \
111+
@{<info>Moving these types above the component declaration@} should resolve this!@]\
113112
@]"
114-
else if Super_reason_react.is_array_wanted_reactElement trace then
113+
(* This one above shouldn't catch any false positives, so we can safely not display the original type clash error. *)
114+
else begin
115+
if Super_reason_react.is_array_wanted_react_element trace then
115116
fprintf ppf "@[<v>\
116117
@[@{<info>Did you pass an array as a ReasonReact DOM (lower-case) component's children?@}@ If not, disregard this.@ \
117118
If so, please use `ReasonReact.createDomElement`:@ https://reasonml.github.io/reason-react/docs/en/children.html@]@,@,\
118119
@[@{<info>Here's the original error message@}@]@,\
119120
@]"
120-
else if Super_reason_react.is_componentSpec_wanted_reactElement trace then
121-
fprintf ppf "@[<v>\
122-
@[@{<info>Did you want to create a ReasonReact element without using JSX?@}@ If not, disregard this.@ \
123-
If so, don't forget to wrap this value in `ReasonReact.element` yourself:@ https://reasonml.github.io/reason-react/docs/en/jsx.html#capitalized@]@,@,\
124-
@[@{<info>Here's the original error message@}@]@,\
125-
@]";
121+
else if Super_reason_react.is_component_spec_wanted_react_element trace then
122+
fprintf ppf "@[<v>\
123+
@[@{<info>Did you want to create a ReasonReact element without using JSX?@}@ If not, disregard this.@ \
124+
If so, don't forget to wrap this value in `ReasonReact.element` yourself:@ https://reasonml.github.io/reason-react/docs/en/jsx.html#capitalized@]@,@,\
125+
@[@{<info>Here's the original error message@}@]@,\
126+
@]";
126127
begin
127128
let bottom_aliases_result = bottom_aliases trace in
128129
let missing_arguments = match bottom_aliases_result with
@@ -137,7 +138,7 @@ let print_expr_type_clash env trace ppf =
137138
else fprintf ppf "@[(~%s: %a)@]" label type_expr argtype
138139
)
139140
in
140-
begin match missing_arguments with
141+
match missing_arguments with
141142
| Some [singleArgument] ->
142143
(* btw, you can't say "final arguments". Intermediate labeled
143144
arguments might be the ones missing *)
@@ -170,7 +171,7 @@ let print_expr_type_clash env trace ppf =
170171
(function ppf ->
171172
fprintf ppf "But somewhere wanted:");
172173
show_extra_help ppf env trace;
173-
end;
174+
end
174175
end
175176
(* taken from https://github.com/BuckleScript/ocaml/blob/d4144647d1bf9bc7dc3aadc24c25a7efa3a67915/typing/typecore.ml#L3769 *)
176177
(* modified branches are commented *)

0 commit comments

Comments
 (0)