Skip to content

Improve error messages for pattern matching mismatches for options/concrete values #7035

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Sep 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

#### :nail_care: Polish

- Improve error messages for pattern matching on option vs non-option, and vice versa. https://github.com/rescript-lang/rescript-compiler/pull/7035
- Improve bigint literal comparison. https://github.com/rescript-lang/rescript-compiler/pull/7029
- Improve output of `@variadic` bindings. https://github.com/rescript-lang/rescript-compiler/pull/7030

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@

We've found a bug for you!
/.../fixtures/pattern_matching_on_option_but_value_not_option.res:4:3-9

2 │
3 │ switch x {
4 │ | Some(1) => ()
5 │ }
6 │

This pattern matches values of type option<'a>
but a pattern was expected which matches values of type int

You're expecting the value you're pattern matching on to be an option, but the value is actually not an option.
Change your pattern match to work on the concrete value (remove Some(_) or None from the pattern) to make it work.
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@

We've found a bug for you!
/.../fixtures/pattern_matching_on_value_but_is_option.res:4:3

2 │
3 │ switch x {
4 │ | 1 => ()
5 │ }
6 │

This pattern matches values of type int
but a pattern was expected which matches values of type option<int>

The value you're pattern matching on here is wrapped in an option, but you're trying to match on the actual value.
Wrap the highlighted pattern in Some() to make it work.
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
let x = 1

switch x {
| Some(1) => ()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
let x = Some(1)

switch x {
| 1 => ()
}
24 changes: 24 additions & 0 deletions jscomp/ml/error_message_utils.ml
Original file line number Diff line number Diff line change
Expand Up @@ -213,3 +213,27 @@ let type_clash_context_in_statement sexp =
match sexp.Parsetree.pexp_desc with
| Pexp_apply _ -> Some (Statement FunctionCall)
| _ -> None

let print_contextual_unification_error ppf t1 t2 =
(* TODO: Maybe we should do the same for Null.t and Nullable.t as we do for options
below, now that they also are more first class for values that might not exist? *)

match (t1.Types.desc, t2.Types.desc) with
| Tconstr (p1, _, _), Tconstr (p2, _, _)
when Path.same p1 Predef.path_option
&& Path.same p2 Predef.path_option <> true ->
fprintf ppf
"@,@\n\
@[<v 0>You're expecting the value you're pattern matching on to be an \
@{<info>option@}, but the value is actually not an option.@ Change your \
pattern match to work on the concrete value (remove @{<error>Some(_)@} \
or @{<error>None@} from the pattern) to make it work.@]"
| Tconstr (p1, _, _), Tconstr (p2, _, _)
when Path.same p2 Predef.path_option
&& Path.same p1 Predef.path_option <> true ->
fprintf ppf
"@,@\n\
@[<v 0>The value you're pattern matching on here is wrapped in an \
@{<info>option@}, but you're trying to match on the actual value.@ Wrap \
the highlighted pattern in @{<info>Some()@} to make it work.@]"
| _ -> ()
10 changes: 6 additions & 4 deletions jscomp/ml/printtyp.ml
Original file line number Diff line number Diff line change
Expand Up @@ -1471,7 +1471,7 @@ let super_trace ppf =
| _ -> ()
in super_trace true ppf

let super_unification_error unif tr txt1 ppf txt2 = begin
let super_unification_error ?print_extra_info unif tr txt1 ppf txt2 = begin
reset ();
trace_same_names tr;
let tr = List.map (fun (t, t') -> (t, hide_variant_name t')) tr in
Expand All @@ -1490,18 +1490,20 @@ let super_unification_error unif tr txt1 ppf txt2 = begin
@[<hov 2>%t@ %a@]\
%a\
%t\
%t\
@]"
txt1 (super_type_expansion ~tag:"error" t1) t1'
txt2 (super_type_expansion ~tag:"info" t2) t2'
super_trace tr
(explanation unif mis);
(explanation unif mis)
(fun ppf -> match print_extra_info with | None -> () | Some f -> f ppf t1 t2);
with exn ->
raise exn
end

let super_report_unification_error ppf env ?(unif=true)
let super_report_unification_error ?print_extra_info ppf env ?(unif=true)
tr txt1 txt2 =
wrap_printing_env env (fun () -> super_unification_error unif tr txt1 ppf txt2)
wrap_printing_env env (fun () -> super_unification_error ?print_extra_info unif tr txt1 ppf txt2)
;;


Expand Down
1 change: 1 addition & 0 deletions jscomp/ml/printtyp.mli
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ val report_unification_error:


val super_report_unification_error:
?print_extra_info:(formatter -> type_expr -> type_expr -> unit) ->
formatter -> Env.t -> ?unif:bool -> (type_expr * type_expr) list ->
(formatter -> unit) -> (formatter -> unit) ->
unit
Expand Down
1 change: 1 addition & 0 deletions jscomp/ml/typecore.ml
Original file line number Diff line number Diff line change
Expand Up @@ -3771,6 +3771,7 @@ let report_error env ppf = function
| Pattern_type_clash trace ->
(* modified *)
super_report_unification_error ppf env trace
~print_extra_info:Error_message_utils.print_contextual_unification_error
(function ppf ->
fprintf ppf "This pattern matches values of type")
(function ppf ->
Expand Down