Skip to content

Commit 15b1855

Browse files
committed
more work on error messages
1 parent 6ddf320 commit 15b1855

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
@@ -166,8 +166,7 @@ let error_expected_type_text ppf type_clash_context =
166166
fprintf ppf "But it's being compared to something of type:"
167167
| Some SwitchReturn -> fprintf ppf "But this switch is expected to return:"
168168
| Some LetUnwrapReturn ->
169-
fprintf ppf
170-
"But this @{<info>let?@} is used in a context expecting the type:"
169+
fprintf ppf "But this @{<info>let?@} is used where this type is expected:"
171170
| Some TryReturn -> fprintf ppf "But this try/catch is expected to return:"
172171
| Some WhileCondition ->
173172
fprintf ppf "But a @{<info>while@} loop condition must always be of type:"
@@ -319,11 +318,65 @@ let print_extra_type_clash_help ~extract_concrete_typedecl ~env loc ppf
319318
"\n\n\
320319
\ All branches in a @{<info>switch@} must return the same type.@,\
321320
To fix this, change your branch to return the expected type."
322-
| Some LetUnwrapReturn, _ ->
323-
fprintf ppf
324-
"\n\n\
325-
\ @{<info>let?@} can only be used in a context that expects \
326-
@{<info>option@} or @{<info>result@}."
321+
| Some LetUnwrapReturn, bottom_aliases -> (
322+
let kind =
323+
match bottom_aliases with
324+
| Some ({Types.desc = Tconstr (p, _, _)}, _)
325+
when Path.same p Predef.path_option ->
326+
`Option
327+
| Some (_, {Types.desc = Tconstr (p, _, _)})
328+
when Path.same p Predef.path_option ->
329+
`Option
330+
| Some ({Types.desc = Tconstr (p, _, _)}, _)
331+
when Path.same p Predef.path_result ->
332+
`Result
333+
| Some (_, {Types.desc = Tconstr (p, _, _)})
334+
when Path.same p Predef.path_result ->
335+
`Result
336+
| _ -> `Unknown
337+
in
338+
match kind with
339+
| `Option ->
340+
fprintf ppf
341+
"\n\n\
342+
\ This @{<info>let?@} unwraps an @{<info>option@}; use it where the \
343+
enclosing function or let binding returns an @{<info>option@} so \
344+
@{<info>None@} can propagate.\n\n\
345+
\ Possible solutions:\n\
346+
\ - Change the enclosing function or let binding to return \
347+
@{<info>option<'t>@} and use @{<info>Some@} for success; \
348+
@{<info>let?@} will propagate @{<info>None@}.\n\
349+
\ - Replace @{<info>let?@} with a @{<info>switch@} and handle the \
350+
@{<info>None@} case explicitly.\n\
351+
\ - If you want a default value instead of early return, unwrap using \
352+
@{<info>Option.getOr(default)@}."
353+
| `Result ->
354+
fprintf ppf
355+
"\n\n\
356+
\ This @{<info>let?@} unwraps a @{<info>result@}; use it where the \
357+
enclosing function or let binding returns a @{<info>result@} so \
358+
@{<info>Error@} can propagate.\n\n\
359+
\ Possible solutions:\n\
360+
\ - Change the enclosing function or let binding to return \
361+
@{<info>result<'ok, 'error>@}; use @{<info>Ok@} for success, and \
362+
@{<info>let?@} will propagate @{<info>Error@}.\n\
363+
\ - Replace @{<info>let?@} with a @{<info>switch@} and handle the \
364+
@{<info>Error@} case explicitly.\n\
365+
\ - If you want a default value instead of early return, unwrap using \
366+
@{<info>Result.getOr(default)@}."
367+
| `Unknown ->
368+
fprintf ppf
369+
"\n\n\
370+
\ @{<info>let?@} can only be used in a context that expects \
371+
@{<info>option@} or @{<info>result@}.\n\n\
372+
\ Possible solutions:\n\
373+
\ - Change the enclosing function or let binding to return an \
374+
@{<info>option<'t>@} or @{<info>result<'ok, 'error>@} and propagate \
375+
with @{<info>Some/Ok@}.\n\
376+
\ - Replace @{<info>let?@} with a @{<info>switch@} and handle the \
377+
@{<info>None/Error@} case explicitly.\n\
378+
\ - If you want a default value instead of early return, unwrap using \
379+
@{<info>Option.getOr(default)@} or @{<info>Result.getOr(default)@}.")
327380
| Some TryReturn, _ ->
328381
fprintf ppf
329382
"\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)