11open Types
22open Typedtree
33open Parsetree
4+ open Asttypes
45
56let mkpat desc = Ast_helper.Pat. mk desc
67
8+ let is_generated_optional_constructor (lid : Longident.t Location.loc ) =
9+ match lid.txt with
10+ | Longident. Lident name ->
11+ String. length name > = 2 && name.[0 ] = '#' && name.[1 ] = '$'
12+ | _ -> false
13+
14+ (* Optional fields become “option-of-option” internally: the outer layer is
15+ added by the compiler to track presence, while the inner layer is the user’s
16+ payload. When printing counterexamples we only need to know which of these
17+ situations we saw. *)
18+ type optional_absence =
19+ | Not_special (* Regular user patterns: `{b: Some(_)}`, `{b}`, `_`, etc. *)
20+ | Missing_field
21+ (* The outer constructor was the synthetic `#$None…`, i.e. the field was
22+ not provided at all. This is what should print as `{b: ?None}`. *)
23+ | Present_missing_value
24+ (* The outer constructor was the synthetic `#$Some…` but its payload was
25+ still the synthetic `None`. That means the field exists but the user’s
26+ inner option is `None`, so we should print `{b: None}`. *)
27+
28+ (* Optional record fields are lowered into an extra option layer; we re-infer
29+ whether we’re looking at a missing field vs. a present-but-`None` value so
30+ we can render useful surface syntax in error messages. *)
31+ let rec classify_optional_absence pat =
32+ match pat.pat_desc with
33+ | Tpat_construct (lid, cstr, [] )
34+ when is_generated_optional_constructor lid && cstr.cstr_name = " None" ->
35+ Missing_field
36+ | Tpat_construct (lid, cstr, [inner])
37+ when is_generated_optional_constructor lid && cstr.cstr_name = " Some" -> (
38+ match classify_optional_absence inner with
39+ | Missing_field | Present_missing_value -> Present_missing_value
40+ | Not_special -> Not_special )
41+ | _ -> Not_special
42+
43+ let none_pattern =
44+ mkpat (Ppat_construct (mknoloc (Longident. Lident " None" ), None ))
45+
46+ let strip_synthetic_some pat =
47+ match pat.pat_desc with
48+ | Tpat_construct (lid, cstr, [inner])
49+ when is_generated_optional_constructor lid && cstr.cstr_name = " Some" ->
50+ inner
51+ | _ -> pat
52+
753let untype typed =
854 let rec loop pat =
955 match pat.pat_desc with
@@ -30,12 +76,32 @@ let untype typed =
3076 let arg = Option. map loop p_opt in
3177 mkpat (Ppat_variant (label, arg))
3278 | Tpat_record (subpatterns , closed_flag ) ->
79+ let special_case_seen = ref false in
3380 let fields =
3481 List. map
3582 (fun (_ , lbl , p , opt ) ->
36- {lid = mknoloc (Longident. Lident lbl.lbl_name); x = loop p; opt})
83+ let classification =
84+ if lbl.lbl_optional then classify_optional_absence p
85+ else Not_special
86+ in
87+ (match classification with
88+ | Missing_field | Present_missing_value -> special_case_seen := true
89+ | Not_special -> () );
90+ let opt =
91+ match classification with
92+ | Missing_field -> true
93+ | Present_missing_value | Not_special -> opt
94+ in
95+ let par_pat =
96+ match classification with
97+ | Missing_field -> none_pattern
98+ | Present_missing_value -> loop (strip_synthetic_some p)
99+ | Not_special -> loop p
100+ in
101+ {lid = mknoloc (Longident. Lident lbl.lbl_name); x = par_pat; opt})
37102 subpatterns
38103 in
104+ let closed_flag = if ! special_case_seen then Closed else closed_flag in
39105 mkpat (Ppat_record (fields, closed_flag))
40106 | Tpat_array lst -> mkpat (Ppat_array (List. map loop lst))
41107 in
0 commit comments