Skip to content

Commit 9236e6f

Browse files
committed
more work on error messages
1 parent 5f196a0 commit 9236e6f

6 files changed

+128
-9
lines changed

compiler/ml/error_message_utils.ml

Lines changed: 60 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -161,8 +161,7 @@ let error_expected_type_text ppf type_clash_context =
161161
fprintf ppf "But it's being compared to something of type:"
162162
| Some SwitchReturn -> fprintf ppf "But this switch is expected to return:"
163163
| Some LetUnwrapReturn ->
164-
fprintf ppf
165-
"But this @{<info>let?@} is used in a context expecting the type:"
164+
fprintf ppf "But this @{<info>let?@} is used where this type is expected:"
166165
| Some TryReturn -> fprintf ppf "But this try/catch is expected to return:"
167166
| Some WhileCondition ->
168167
fprintf ppf "But a @{<info>while@} loop condition must always be of type:"
@@ -313,11 +312,65 @@ let print_extra_type_clash_help ~extract_concrete_typedecl ~env loc ppf
313312
"\n\n\
314313
\ All branches in a @{<info>switch@} must return the same type.@,\
315314
To fix this, change your branch to return the expected type."
316-
| Some LetUnwrapReturn, _ ->
317-
fprintf ppf
318-
"\n\n\
319-
\ @{<info>let?@} can only be used in a context that expects \
320-
@{<info>option@} or @{<info>result@}."
315+
| Some LetUnwrapReturn, bottom_aliases -> (
316+
let kind =
317+
match bottom_aliases with
318+
| Some ({Types.desc = Tconstr (p, _, _)}, _)
319+
when Path.same p Predef.path_option ->
320+
`Option
321+
| Some (_, {Types.desc = Tconstr (p, _, _)})
322+
when Path.same p Predef.path_option ->
323+
`Option
324+
| Some ({Types.desc = Tconstr (p, _, _)}, _)
325+
when Path.same p Predef.path_result ->
326+
`Result
327+
| Some (_, {Types.desc = Tconstr (p, _, _)})
328+
when Path.same p Predef.path_result ->
329+
`Result
330+
| _ -> `Unknown
331+
in
332+
match kind with
333+
| `Option ->
334+
fprintf ppf
335+
"\n\n\
336+
\ This @{<info>let?@} unwraps an @{<info>option@}; use it where the \
337+
enclosing function or let binding returns an @{<info>option@} so \
338+
@{<info>None@} can propagate.\n\n\
339+
\ Possible solutions:\n\
340+
\ - Change the enclosing function or let binding to return \
341+
@{<info>option<'t>@} and use @{<info>Some@} for success; \
342+
@{<info>let?@} will propagate @{<info>None@}.\n\
343+
\ - Replace @{<info>let?@} with a @{<info>switch@} and handle the \
344+
@{<info>None@} case explicitly.\n\
345+
\ - If you want a default value instead of early return, unwrap using \
346+
@{<info>Option.getOr(default)@}."
347+
| `Result ->
348+
fprintf ppf
349+
"\n\n\
350+
\ This @{<info>let?@} unwraps a @{<info>result@}; use it where the \
351+
enclosing function or let binding returns a @{<info>result@} so \
352+
@{<info>Error@} can propagate.\n\n\
353+
\ Possible solutions:\n\
354+
\ - Change the enclosing function or let binding to return \
355+
@{<info>result<'ok, 'error>@}; use @{<info>Ok@} for success, and \
356+
@{<info>let?@} will propagate @{<info>Error@}.\n\
357+
\ - Replace @{<info>let?@} with a @{<info>switch@} and handle the \
358+
@{<info>Error@} case explicitly.\n\
359+
\ - If you want a default value instead of early return, unwrap using \
360+
@{<info>Result.getOr(default)@}."
361+
| `Unknown ->
362+
fprintf ppf
363+
"\n\n\
364+
\ @{<info>let?@} can only be used in a context that expects \
365+
@{<info>option@} or @{<info>result@}.\n\n\
366+
\ Possible solutions:\n\
367+
\ - Change the enclosing function or let binding to return an \
368+
@{<info>option<'t>@} or @{<info>result<'ok, 'error>@} and propagate \
369+
with @{<info>Some/Ok@}.\n\
370+
\ - Replace @{<info>let?@} with a @{<info>switch@} and handle the \
371+
@{<info>None/Error@} case explicitly.\n\
372+
\ - If you want a default value instead of early return, unwrap using \
373+
@{<info>Option.getOr(default)@} or @{<info>Result.getOr(default)@}.")
321374
| Some TryReturn, _ ->
322375
fprintf ppf
323376
"\n\n\

tests/build_tests/super_errors/expected/let_unwrap_return_type_mismatch.res.expected

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@
99
8 │ }
1010

1111
This has type: option<'a>
12-
But this let? is used in a context expecting the type: int
12+
But this let? is used where this type is expected: int
1313

14-
let? can only be used in a context that expects option or result.
14+
This let? unwraps an option; use it where the enclosing function or let binding returns an option so None can propagate.
15+
16+
Possible solutions:
17+
- Change the enclosing function or let binding to return option<'t> and use Some for success; let? will propagate None.
18+
- Replace let? with a switch and handle the None case explicitly.
19+
- If you want a default value instead of early return, unwrap using Option.getOr(default).
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
2+
We've found a bug for you!
3+
/.../fixtures/let_unwrap_return_type_mismatch_block.res:10:12-18
4+
5+
8 ┆ 1
6+
9 ┆ } else {
7+
10 ┆ let? Some(x) = None
8+
11 ┆ Some(x)
9+
12 ┆ }
10+
11+
This has type: option<'a>
12+
But this let? is used where this type is expected: int
13+
14+
This let? unwraps an option; use it where the enclosing function or let binding returns an option so None can propagate.
15+
16+
Possible solutions:
17+
- Change the enclosing function or let binding to return option<'t> and use Some for success; let? will propagate None.
18+
- Replace let? with a switch and handle the None case explicitly.
19+
- If you want a default value instead of early return, unwrap using Option.getOr(default).
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
2+
We've found a bug for you!
3+
/.../fixtures/let_unwrap_return_type_mismatch_result.res:6:8-12
4+
5+
4 │
6+
5 │ let fn = (): int => {
7+
6 │ let? Ok(v) = Error("fail")
8+
7 │ 42
9+
8 │ }
10+
11+
This has type: result<'a, string>
12+
But this let? is used where this type is expected: int
13+
14+
This let? unwraps a result; use it where the enclosing function or let binding returns a result so Error can propagate.
15+
16+
Possible solutions:
17+
- Change the enclosing function or let binding to return result<'ok, 'error>; use Ok for success, and let? will propagate Error.
18+
- Replace let? with a switch and handle the Error case explicitly.
19+
- If you want a default value instead of early return, unwrap using Result.getOr(default).
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
@@config({flags: ["-enable-experimental", "LetUnwrap"]})
2+
3+
let x = Some(1)
4+
5+
let fn = (): int => {
6+
let x = {
7+
if 1 > 2 {
8+
1
9+
} else {
10+
let? Some(x) = None
11+
Some(x)
12+
}
13+
}
14+
42
15+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
@@config({flags: ["-enable-experimental", "LetUnwrap"]})
2+
3+
let x = Ok(1)
4+
5+
let fn = (): int => {
6+
let? Ok(v) = Error("fail")
7+
42
8+
}

0 commit comments

Comments
 (0)