Skip to content

Commit 9d7f2c2

Browse files
authored
Try making top level unit error message clearer (#6407)
* try making top level unit error message clearer * indent message * changelog
1 parent 49bd91f commit 9d7f2c2

File tree

9 files changed

+79
-9
lines changed

9 files changed

+79
-9
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
#### :nail_care: Polish
2727

2828
- A little performance improvement for JSX V4 runtime helper by removing one object allocation for components with key prop. https://github.com/rescript-lang/rescript-compiler/pull/6376
29+
- The error message for "toplevel expressions should evaluate to unit" has been revamped and improved. https://github.com/rescript-lang/rescript-compiler/pull/6407
2930

3031
# 11.0.0-rc.3
3132

jscomp/build_tests/super_errors/expected/partial_app.res.expected

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,10 @@
77
5 │ f(1, 2)
88
6 │
99

10-
Toplevel expression is expected to have unit type.
10+
This function call is at the top level and is expected to return `unit`. But, it's returning `int => int`.
11+
12+
In ReScript, anything at the top level must evaluate to `unit`. You can fix this by assigning the expression to a value, or piping it into the `ignore` function.
13+
14+
Possible solutions:
15+
- Assigning to a value that is then ignored: `let _ = yourFunctionCall()`
16+
- Piping into the built-in ignore function to ignore the result: `yourFunctionCall()->ignore`
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
2+
Warning number 109 (configured as error)
3+
/.../fixtures/top_level_fn_call_not_unit.res:3:1-18
4+
5+
1 │ let returnsSomething = () => 123
6+
2 │
7+
3 │ returnsSomething()
8+
4 │
9+
10+
This function call is at the top level and is expected to return `unit`. But, it's returning `int`.
11+
12+
In ReScript, anything at the top level must evaluate to `unit`. You can fix this by assigning the expression to a value, or piping it into the `ignore` function.
13+
14+
Possible solutions:
15+
- Assigning to a value that is then ignored: `let _ = yourFunctionCall()`
16+
- Piping into the built-in ignore function to ignore the result: `yourFunctionCall()->ignore`
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
2+
Warning number 109 (configured as error)
3+
/.../fixtures/top_level_value_not_unit.res:1:1-4
4+
5+
1 │ 1234
6+
2 │
7+
8+
This is at the top level and is expected to return `unit`. But, it's returning `int`.
9+
10+
In ReScript, anything at the top level must evaluate to `unit`. You can fix this by assigning the expression to a value, or piping it into the `ignore` function.
11+
12+
Possible solutions:
13+
- Assigning to a value that is then ignored: `let _ = yourExpression`
14+
- Piping into the built-in ignore function to ignore the result: `yourExpression->ignore`
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
let returnsSomething = () => 123
2+
3+
returnsSomething()
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
1234

jscomp/ext/warnings.ml

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ type loc = {
2626
loc_ghost : bool;
2727
}
2828

29+
type topLevelUnitHelp = FunctionCall | Other
30+
2931
type t =
3032
| Comment_start (* 1 *)
3133
| Comment_not_end (* 2 *)
@@ -83,7 +85,7 @@ type t =
8385
| Bs_unimplemented_primitive of string (* 106 *)
8486
| Bs_integer_literal_overflow (* 107 *)
8587
| Bs_uninterpreted_delimiters of string (* 108 *)
86-
| Bs_toplevel_expression_unit (* 109 *)
88+
| Bs_toplevel_expression_unit of (string * topLevelUnitHelp) option (* 109 *)
8789

8890
(* If you remove a warning, leave a hole in the numbering. NEVER change
8991
the numbers of existing warnings.
@@ -148,7 +150,7 @@ let number = function
148150
| Bs_unimplemented_primitive _ -> 106
149151
| Bs_integer_literal_overflow -> 107
150152
| Bs_uninterpreted_delimiters _ -> 108
151-
| Bs_toplevel_expression_unit -> 109
153+
| Bs_toplevel_expression_unit _ -> 109
152154

153155
let last_warning_number = 110
154156

@@ -490,8 +492,23 @@ let message = function
490492
| Bs_integer_literal_overflow ->
491493
"Integer literal exceeds the range of representable integers of type int"
492494
| Bs_uninterpreted_delimiters s -> "Uninterpreted delimiters " ^ s
493-
| Bs_toplevel_expression_unit ->
494-
"Toplevel expression is expected to have unit type."
495+
| Bs_toplevel_expression_unit help ->
496+
Printf.sprintf "This%sis at the top level and is expected to return `unit`. But, it's returning %s.\n\n In ReScript, anything at the top level must evaluate to `unit`. You can fix this by assigning the expression to a value, or piping it into the `ignore` function.%s"
497+
(match help with
498+
| Some (_, FunctionCall) -> " function call "
499+
| _ -> " ")
500+
501+
(match help with
502+
| Some (returnType, _) -> Printf.sprintf "`%s`" returnType
503+
| None -> "something that is not `unit`")
504+
505+
(match help with
506+
| Some (_, helpTyp) ->
507+
let helpText = (match helpTyp with
508+
| FunctionCall -> "yourFunctionCall()"
509+
| Other -> "yourExpression") in
510+
Printf.sprintf "\n\n Possible solutions:\n - Assigning to a value that is then ignored: `let _ = %s`\n - Piping into the built-in ignore function to ignore the result: `%s->ignore`" helpText helpText
511+
| _ -> "")
495512

496513
let sub_locs = function
497514
| Deprecated (_, def, use) ->

jscomp/ext/warnings.mli

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ type loc = {
1919
loc_ghost : bool;
2020
}
2121

22+
type topLevelUnitHelp = FunctionCall | Other
23+
2224
type t =
2325
| Comment_start (* 1 *)
2426
| Comment_not_end (* 2 *)
@@ -76,7 +78,7 @@ type t =
7678
| Bs_unimplemented_primitive of string (* 106 *)
7779
| Bs_integer_literal_overflow (* 107 *)
7880
| Bs_uninterpreted_delimiters of string (* 108 *)
79-
| Bs_toplevel_expression_unit (* 109 *)
81+
| Bs_toplevel_expression_unit of (string * topLevelUnitHelp) option (* 109 *)
8082

8183
val parse_options : bool -> string -> unit
8284

jscomp/ml/typecore.ml

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3743,11 +3743,21 @@ let type_expression env sexp =
37433743
Typetexp.reset_type_variables();
37443744
begin_def();
37453745
let exp = type_exp env sexp in
3746-
if Warnings.is_active Bs_toplevel_expression_unit then
3746+
if Warnings.is_active (Bs_toplevel_expression_unit None) then
37473747
(try unify env exp.exp_type
37483748
(instance_def Predef.type_unit) with
3749-
| Unify _
3750-
| Tags _ -> Location.prerr_warning sexp.pexp_loc Bs_toplevel_expression_unit);
3749+
| Unify _ ->
3750+
let buffer = Buffer.create 10 in
3751+
let formatter = Format.formatter_of_buffer buffer in
3752+
Printtyp.type_expr formatter exp.exp_type;
3753+
Format.pp_print_flush formatter ();
3754+
let returnType = Buffer.contents buffer in
3755+
Location.prerr_warning sexp.pexp_loc (Bs_toplevel_expression_unit (
3756+
match sexp.pexp_desc with
3757+
| Pexp_apply _ -> Some (returnType, FunctionCall)
3758+
| _ -> Some (returnType, Other)
3759+
))
3760+
| Tags _ -> Location.prerr_warning sexp.pexp_loc (Bs_toplevel_expression_unit None));
37513761
end_def();
37523762
if not (is_nonexpansive exp) then generalize_expansive env exp.exp_type;
37533763
generalize exp.exp_type;

0 commit comments

Comments
 (0)