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_field_state =
19+ | Field_normal (* Regular user patterns: `{b: Some(_)}`, `{b}`, `_`, etc. *)
20+ | Field_missing
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+ | Field_present_none
24+ (* The outer constructor was the synthetic `#$Some…` but its payload was `None`.
25+ This means the optional field exists with value `None`, so we should
26+ 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 [@ warning " -4" ] rec classify_optional_field_state pat =
32+ match pat.pat_desc with
33+ | Tpat_construct (lid, cstr, [] )
34+ when is_generated_optional_constructor lid && cstr.cstr_name = " None" ->
35+ Field_missing
36+ | Tpat_construct (lid, cstr, [inner])
37+ when is_generated_optional_constructor lid && cstr.cstr_name = " Some" -> (
38+ match classify_optional_field_state inner with
39+ | Field_missing | Field_present_none -> Field_present_none
40+ | Field_normal -> Field_normal )
41+ | _ -> Field_normal
42+
43+ let none_pattern =
44+ mkpat (Ppat_construct (mknoloc (Longident. Lident " None" ), None ))
45+
46+ let [@ warning " -4" ] 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,26 @@ let untype typed =
3076 let arg = Option. map loop p_opt in
3177 mkpat (Ppat_variant (label, arg))
3278 | 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
79+ let fields, saw_optional_rewrite =
80+ List. fold_right
81+ (fun (_ , lbl , p , opt ) (fields , saw_optional_rewrite ) ->
82+ let state =
83+ if lbl.lbl_optional then classify_optional_field_state p
84+ else Field_normal
85+ in
86+ let opt, par_pat, rewrote_optional =
87+ match state with
88+ | Field_missing -> (true , none_pattern, true )
89+ | Field_present_none -> (opt, loop (strip_synthetic_some p), true )
90+ | Field_normal -> (opt, loop p, false )
91+ in
92+ let field =
93+ {lid = mknoloc (Longident. Lident lbl.lbl_name); x = par_pat; opt}
94+ in
95+ (field :: fields, saw_optional_rewrite || rewrote_optional))
96+ subpatterns ([] , false )
3897 in
98+ let closed_flag = if saw_optional_rewrite then Closed else closed_flag in
3999 mkpat (Ppat_record (fields, closed_flag))
40100 | Tpat_array lst -> mkpat (Ppat_array (List. map loop lst))
41101 in
0 commit comments