Skip to content

Commit 8988351

Browse files
authored
Fix printing of optional record fields in pattern matching errors (#8019)
* Fix printing of optional record fields in pattern matching errors * CHANGELOG * Disable warning -4 once more * Add another test case
1 parent a39eb8e commit 8988351

10 files changed

+152
-5
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
#### :bug: Bug fix
2222

2323
- Fix printing of tagged template literals. https://github.com/rescript-lang/rescript/pull/8018
24+
- Fix printing of optional record fields in pattern matching errors. https://github.com/rescript-lang/rescript/pull/8019
2425

2526
#### :memo: Documentation
2627

compiler/common/pattern_printer.ml

Lines changed: 66 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,56 @@
11
open Types
22
open Typedtree
33
open Parsetree
4+
open Asttypes
45

56
let mkpat desc = Ast_helper.Pat.mk desc
67

8+
let[@warning "-4"] is_generated_optional_constructor
9+
(lid : Longident.t Location.loc) =
10+
match lid.txt with
11+
| Longident.Lident name ->
12+
String.length name >= 2 && name.[0] = '#' && name.[1] = '$'
13+
| _ -> false
14+
15+
(* Optional fields become “option-of-option” internally: the outer layer is
16+
added by the compiler to track presence, while the inner layer is the user’s
17+
payload. When printing counterexamples we only need to know which of these
18+
situations we saw. *)
19+
type optional_field_state =
20+
| Field_normal (* Regular user patterns: `{b: Some(_)}`, `{b}`, `_`, etc. *)
21+
| Field_missing
22+
(* The outer constructor was the synthetic `#$None…`, i.e. the field was
23+
not provided at all. This is what should print as `{b: ?None}`. *)
24+
| Field_present_none
25+
(* The outer constructor was the synthetic `#$Some…` but its payload was `None`.
26+
This means the optional field exists with value `None`, so we should
27+
print `{b: None}`. *)
28+
29+
(* Optional record fields are lowered into an extra option layer; we re-infer
30+
whether we’re looking at a missing field vs. a present-but-`None` value so
31+
we can render useful surface syntax in error messages. *)
32+
let[@warning "-4"] rec classify_optional_field_state pat =
33+
match pat.pat_desc with
34+
| Tpat_construct (lid, cstr, [])
35+
when is_generated_optional_constructor lid && cstr.cstr_name = "None" ->
36+
Field_missing
37+
| Tpat_construct (lid, cstr, [inner])
38+
when is_generated_optional_constructor lid && cstr.cstr_name = "Some" -> (
39+
match classify_optional_field_state inner with
40+
| Field_missing | Field_present_none -> Field_present_none
41+
| Field_normal -> Field_normal)
42+
| _ -> Field_normal
43+
44+
let none_pattern =
45+
mkpat (Ppat_construct (mknoloc (Longident.Lident "None"), None))
46+
47+
let[@warning "-4"] strip_synthetic_some pat =
48+
match pat.pat_desc with
49+
| Tpat_construct (lid, cstr, [inner])
50+
when is_generated_optional_constructor lid && cstr.cstr_name = "Some" ->
51+
inner
52+
| _ -> pat
53+
754
let untype typed =
855
let rec loop pat =
956
match pat.pat_desc with
@@ -30,12 +77,26 @@ let untype typed =
3077
let arg = Option.map loop p_opt in
3178
mkpat (Ppat_variant (label, arg))
3279
| Tpat_record (subpatterns, closed_flag) ->
33-
let fields =
34-
List.map
35-
(fun (_, lbl, p, opt) ->
36-
{lid = mknoloc (Longident.Lident lbl.lbl_name); x = loop p; opt})
37-
subpatterns
80+
let fields, saw_optional_rewrite =
81+
List.fold_right
82+
(fun (_, lbl, p, opt) (fields, saw_optional_rewrite) ->
83+
let state =
84+
if lbl.lbl_optional then classify_optional_field_state p
85+
else Field_normal
86+
in
87+
let opt, par_pat, rewrote_optional =
88+
match state with
89+
| Field_missing -> (true, none_pattern, true)
90+
| Field_present_none -> (opt, loop (strip_synthetic_some p), true)
91+
| Field_normal -> (opt, loop p, false)
92+
in
93+
let field =
94+
{lid = mknoloc (Longident.Lident lbl.lbl_name); x = par_pat; opt}
95+
in
96+
(field :: fields, saw_optional_rewrite || rewrote_optional))
97+
subpatterns ([], false)
3898
in
99+
let closed_flag = if saw_optional_rewrite then Closed else closed_flag in
39100
mkpat (Ppat_record (fields, closed_flag))
40101
| Tpat_array lst -> mkpat (Ppat_array (List.map loop lst))
41102
in
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
2+
Warning number 8
3+
/.../fixtures/optional_record_field_missing_case.res:5:9-8:1
4+
5+
3 │ let a: t = Obj.magic()
6+
4 │
7+
5 │ let _ = switch a {
8+
6 │ | {b: None} => ()
9+
7 │ | {b: Some(_)} => ()
10+
8 │ }
11+
9 │
12+
13+
You forgot to handle a possible case here, for example:
14+
| {b: ?None}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
2+
Warning number 8
3+
/.../fixtures/optional_record_field_missing_case_inline.res:5:9-8:1
4+
5+
3 │ let v: t = Obj.magic()
6+
4 │
7+
5 │ let _ = switch v {
8+
6 │ | A({b: None}) => ()
9+
7 │ | A({b: Some(_)}) => ()
10+
8 │ }
11+
9 │
12+
13+
You forgot to handle a possible case here, for example:
14+
| A({b: ?None})
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
2+
Warning number 8
3+
/.../fixtures/optional_record_field_missing_case_result.res:4:3-7:3
4+
5+
2 │
6+
3 │ let f = v =>
7+
4 │ switch v {
8+
5 │  | {b: Ok(x)} => x
9+
6 │  | {b: Error(y)} => y
10+
7 │  }
11+
8 │
12+
13+
You forgot to handle a possible case here, for example:
14+
| {b: ?None}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
2+
Warning number 8
3+
/.../fixtures/optional_record_field_missing_cases_nested.res:5:9-7:1
4+
5+
3 │ let a: t = Obj.magic()
6+
4 │
7+
5 │ let _ = switch a {
8+
6 │ | {b: Some(Some(_))} => ()
9+
7 │ }
10+
8 │
11+
12+
You forgot to handle a possible case here, for example:
13+
| {b: Some(None)} | {b: None} | {b: ?None}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
type t = {b?: option<int>}
2+
3+
let a: t = Obj.magic()
4+
5+
let _ = switch a {
6+
| {b: None} => ()
7+
| {b: Some(_)} => ()
8+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
type t = A({b?: option<int>})
2+
3+
let v: t = Obj.magic()
4+
5+
let _ = switch v {
6+
| A({b: None}) => ()
7+
| A({b: Some(_)}) => ()
8+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
type t = {b?: result<string, string>}
2+
3+
let f = v =>
4+
switch v {
5+
| {b: Ok(x)} => x
6+
| {b: Error(y)} => y
7+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
type t = {b?: option<option<int>>}
2+
3+
let a: t = Obj.magic()
4+
5+
let _ = switch a {
6+
| {b: Some(Some(_))} => ()
7+
}

0 commit comments

Comments
 (0)