Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package ocaml-sexplib0 for openSUSE:Factory 
checked in at 2023-05-12 20:38:37
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/ocaml-sexplib0 (Old)
 and      /work/SRC/openSUSE:Factory/.ocaml-sexplib0.new.1533 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "ocaml-sexplib0"

Fri May 12 20:38:37 2023 rev:7 rq:1086722 version:0.16.0

Changes:
--------
--- /work/SRC/openSUSE:Factory/ocaml-sexplib0/ocaml-sexplib0.changes    
2023-01-05 15:01:19.581170026 +0100
+++ /work/SRC/openSUSE:Factory/.ocaml-sexplib0.new.1533/ocaml-sexplib0.changes  
2023-05-12 20:41:14.355170487 +0200
@@ -1,0 +2,6 @@
+Fri May  5 05:05:05 UTC 2023 - oher...@suse.de
+
+- Update to version 0.16.0
+  No changelog provided
+
+-------------------------------------------------------------------

Old:
----
  ocaml-sexplib0-0.15.1.tar.xz

New:
----
  ocaml-sexplib0-0.16.0.tar.xz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ ocaml-sexplib0.spec ++++++
--- /var/tmp/diff_new_pack.NFAIwI/_old  2023-05-12 20:41:14.827173081 +0200
+++ /var/tmp/diff_new_pack.NFAIwI/_new  2023-05-12 20:41:14.835173125 +0200
@@ -17,18 +17,19 @@
 
 
 Name:           ocaml-sexplib0
-Version:        0.15.1
+Version:        0.16.0
 Release:        0
 %{?ocaml_preserve_bytecode}
 Summary:        Library containing the definition of S-expressions and some 
base converters
 License:        MIT
+ExclusiveArch:  aarch64 ppc64 ppc64le riscv64 s390x x86_64
 Group:          Development/Languages/OCaml
 BuildRoot:      %_tmppath/%name-%version-build
 URL:            https://opam.ocaml.org/packages/sexplib0
 Source0:        %name-%version.tar.xz
 BuildRequires:  ocaml-dune
 BuildRequires:  ocaml-rpm-macros >= 20230101
-BuildRequires:  ocaml(ocaml_base_version) >= 4.04
+BuildRequires:  ocaml(ocaml_base_version) >= 4.08
 
 %description
 Library containing the definition of S-expressions and some base converters.

++++++ _service ++++++
--- /var/tmp/diff_new_pack.NFAIwI/_old  2023-05-12 20:41:14.895173455 +0200
+++ /var/tmp/diff_new_pack.NFAIwI/_new  2023-05-12 20:41:14.899173477 +0200
@@ -1,7 +1,7 @@
 <services>
   <service name="tar_scm" mode="disabled">
     <param name="filename">ocaml-sexplib0</param>
-    <param name="revision">4c3387b5890ed7f27d761d4926022efef5710463</param>
+    <param name="revision">df916dfe8d0538a0dbfcdcb35c6e933ac9fc8130</param>
     <param name="scm">git</param>
     <param name="submodules">disable</param>
     <param name="url">https://github.com/janestreet/sexplib0.git</param>

++++++ ocaml-sexplib0-0.15.1.tar.xz -> ocaml-sexplib0-0.16.0.tar.xz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ocaml-sexplib0-0.15.1/CHANGES.md 
new/ocaml-sexplib0-0.16.0/CHANGES.md
--- old/ocaml-sexplib0-0.15.1/CHANGES.md        1970-01-01 01:00:00.000000000 
+0100
+++ new/ocaml-sexplib0-0.16.0/CHANGES.md        2023-04-25 15:12:25.000000000 
+0200
@@ -0,0 +1,7 @@
+## Release v0.16.0
+
+* Added `Sexp_conv_record`. Supports improvements to `ppx_sexp_conv` for 
deriving
+  `of_sexp` on record types. Provides a GADT-based generic interface to 
parsing record
+  sexps. This avoids having to generate the same field-parsing code over and 
over.
+
+* Added `sexp_grammar_with_tags` and `sexp_grammar_with_tag_list` to 
`Sexp_conv_grammar`.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ocaml-sexplib0-0.15.1/LICENSE.md 
new/ocaml-sexplib0-0.16.0/LICENSE.md
--- old/ocaml-sexplib0-0.15.1/LICENSE.md        2022-06-15 18:57:16.000000000 
+0200
+++ new/ocaml-sexplib0-0.16.0/LICENSE.md        2023-04-25 15:12:25.000000000 
+0200
@@ -1,6 +1,6 @@
 The MIT License
 
-Copyright (c) 2005--2022 Jane Street Group, LLC <opensou...@janestreet.com>
+Copyright (c) 2005--2023 Jane Street Group, LLC 
<opensource-conta...@janestreet.com>
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ocaml-sexplib0-0.15.1/bench/bench_record.ml 
new/ocaml-sexplib0-0.16.0/bench/bench_record.ml
--- old/ocaml-sexplib0-0.15.1/bench/bench_record.ml     1970-01-01 
01:00:00.000000000 +0100
+++ new/ocaml-sexplib0-0.16.0/bench/bench_record.ml     2023-04-25 
15:12:25.000000000 +0200
@@ -0,0 +1,105 @@
+open Sexplib0.Sexp_conv
+
+let bench_t_of_sexp ~t_of_sexp string =
+  let sexp = Sys.opaque_identity (Parsexp.Single.parse_string_exn string) in
+  fun () -> t_of_sexp sexp
+;;
+
+type t =
+  { a : int
+  ; b : int option
+  ; c : bool
+  ; d : int array
+  ; e : int list
+  ; f : int option
+  ; g : int
+  ; h : 'a. 'a list
+  }
+
+let t_of_sexp =
+  let open struct
+    type poly = { h : 'a. 'a list } [@@unboxed]
+  end in
+  Sexplib0.Sexp_conv_record.record_of_sexp
+    ~caller:"Record.t"
+    ~fields:
+      (Field
+         { name = "a"
+         ; kind = Required
+         ; conv = int_of_sexp
+         ; rest =
+             Field
+               { name = "b"
+               ; kind = Omit_nil
+               ; conv = option_of_sexp int_of_sexp
+               ; rest =
+                   Field
+                     { name = "c"
+                     ; kind = Sexp_bool
+                     ; conv = ()
+                     ; rest =
+                         Field
+                           { name = "d"
+                           ; kind = Sexp_array
+                           ; conv = int_of_sexp
+                           ; rest =
+                               Field
+                                 { name = "e"
+                                 ; kind = Sexp_list
+                                 ; conv = int_of_sexp
+                                 ; rest =
+                                     Field
+                                       { name = "f"
+                                       ; kind = Sexp_option
+                                       ; conv = int_of_sexp
+                                       ; rest =
+                                           Field
+                                             { name = "g"
+                                             ; kind = Default (fun () -> 0)
+                                             ; conv = int_of_sexp
+                                             ; rest =
+                                                 Field
+                                                   { name = "h"
+                                                   ; kind = Required
+                                                   ; conv =
+                                                       (fun sexp ->
+                                                          { h =
+                                                              list_of_sexp
+                                                                
(Sexplib0.Sexp_conv_error
+                                                                 
.record_poly_field_value
+                                                                   "Record.t")
+                                                                sexp
+                                                          })
+                                                   ; rest = Empty
+                                                   }
+                                             }
+                                       }
+                                 }
+                           }
+                     }
+               }
+         })
+    ~index_of_field:(function
+      | "a" -> 0
+      | "b" -> 1
+      | "c" -> 2
+      | "d" -> 3
+      | "e" -> 4
+      | "f" -> 5
+      | "g" -> 6
+      | "h" -> 7
+      | _ -> -1)
+    ~allow_extra_fields:false
+    ~create:(fun (a, (b, (c, (d, (e, (f, (g, ({ h }, ())))))))) ->
+      { a; b; c; d; e; f; g; h })
+;;
+
+let%bench_fun "t_of_sexp, full, in order" =
+  bench_t_of_sexp ~t_of_sexp "((a 1) (b (2)) (c) (d (3 4)) (e (5 6)) (f 7) (g 
8) (h ()))"
+;;
+
+let%bench_fun "t_of_sexp, full, reverse order" =
+  bench_t_of_sexp ~t_of_sexp "((h ()) (g 8) (f 7) (e (5 6)) (d (3 4)) (c) (b 
(2)) (a 1))"
+;;
+
+let%bench_fun "t_of_sexp, empty" = bench_t_of_sexp ~t_of_sexp "((a 0) (h ()))"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ocaml-sexplib0-0.15.1/bench/bench_record.mli 
new/ocaml-sexplib0-0.16.0/bench/bench_record.mli
--- old/ocaml-sexplib0-0.15.1/bench/bench_record.mli    1970-01-01 
01:00:00.000000000 +0100
+++ new/ocaml-sexplib0-0.16.0/bench/bench_record.mli    2023-04-25 
15:12:25.000000000 +0200
@@ -0,0 +1 @@
+(*_ This signature is deliberately empty. *)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ocaml-sexplib0-0.15.1/bench/dune 
new/ocaml-sexplib0-0.16.0/bench/dune
--- old/ocaml-sexplib0-0.15.1/bench/dune        1970-01-01 01:00:00.000000000 
+0100
+++ new/ocaml-sexplib0-0.16.0/bench/dune        2023-04-25 15:12:25.000000000 
+0200
@@ -0,0 +1,2 @@
+(library (name sexplib0_bench) (libraries parsexp sexplib0)
+ (preprocess (pps ppx_bench)))
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ocaml-sexplib0-0.15.1/bench/sexplib0_bench.ml 
new/ocaml-sexplib0-0.16.0/bench/sexplib0_bench.ml
--- old/ocaml-sexplib0-0.15.1/bench/sexplib0_bench.ml   1970-01-01 
01:00:00.000000000 +0100
+++ new/ocaml-sexplib0-0.16.0/bench/sexplib0_bench.ml   2023-04-25 
15:12:25.000000000 +0200
@@ -0,0 +1 @@
+(*_ Deliberately empty. *)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ocaml-sexplib0-0.15.1/sexplib0.opam 
new/ocaml-sexplib0-0.16.0/sexplib0.opam
--- old/ocaml-sexplib0-0.15.1/sexplib0.opam     2022-06-15 18:57:16.000000000 
+0200
+++ new/ocaml-sexplib0-0.16.0/sexplib0.opam     2023-04-25 15:12:25.000000000 
+0200
@@ -1,5 +1,5 @@
 opam-version: "2.0"
-version: "v0.15.0"
+version: "v0.16.0"
 maintainer: "Jane Street developers"
 authors: ["Jane Street Group, LLC"]
 homepage: "https://github.com/janestreet/sexplib0";
@@ -11,9 +11,10 @@
   ["dune" "build" "-p" name "-j" jobs]
 ]
 depends: [
-  "ocaml" {>= "4.04.2"}
+  "ocaml" {>= "4.08.0"}
   "dune"  {>= "2.0.0"}
 ]
+available: arch != "arm32" & arch != "x86_32"
 synopsis: "Library containing the definition of S-expressions and some base 
converters"
 description: "
 Part of Jane Street's Core library
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ocaml-sexplib0-0.15.1/src/sexp.ml 
new/ocaml-sexplib0-0.16.0/src/sexp.ml
--- old/ocaml-sexplib0-0.15.1/src/sexp.ml       2022-06-15 18:57:16.000000000 
+0200
+++ new/ocaml-sexplib0-0.16.0/src/sexp.ml       2023-04-25 15:12:25.000000000 
+0200
@@ -134,10 +134,7 @@
     Bytes.unsafe_to_string res
   ;;
 
-  let index_of_newline str start =
-    try Some (String.index_from str start '\n') with
-    | Not_found -> None
-  ;;
+  let index_of_newline str start = String.index_from_opt str start '\n'
 
   let get_substring str index end_pos_opt =
     let end_pos =
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ocaml-sexplib0-0.15.1/src/sexp_conv.mli 
new/ocaml-sexplib0-0.16.0/src/sexp_conv.mli
--- old/ocaml-sexplib0-0.15.1/src/sexp_conv.mli 2022-06-15 18:57:16.000000000 
+0200
+++ new/ocaml-sexplib0-0.16.0/src/sexp_conv.mli 2023-04-25 15:12:25.000000000 
+0200
@@ -277,6 +277,7 @@
     -> (exn -> Sexp.t)
     -> unit
 
+
   module For_unit_tests_only : sig
     val size : unit -> int
   end
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ocaml-sexplib0-0.15.1/src/sexp_conv_grammar.ml 
new/ocaml-sexplib0-0.16.0/src/sexp_conv_grammar.ml
--- old/ocaml-sexplib0-0.15.1/src/sexp_conv_grammar.ml  2022-06-15 
18:57:16.000000000 +0200
+++ new/ocaml-sexplib0-0.16.0/src/sexp_conv_grammar.ml  2023-04-25 
15:12:25.000000000 +0200
@@ -1,3 +1,15 @@
+open StdLabels
+
+let sexp_grammar_with_tags grammar ~tags =
+  List.fold_right tags ~init:grammar ~f:(fun (key, value) grammar ->
+    Sexp_grammar.Tagged { key; value; grammar })
+;;
+
+let sexp_grammar_with_tag_list x ~tags =
+  List.fold_right tags ~init:x ~f:(fun (key, value) grammar ->
+    Sexp_grammar.Tag { key; value; grammar })
+;;
+
 let unit_sexp_grammar : unit Sexp_grammar.t = { untyped = List Empty }
 let bool_sexp_grammar : bool Sexp_grammar.t = { untyped = Bool }
 let string_sexp_grammar : string Sexp_grammar.t = { untyped = String }
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ocaml-sexplib0-0.15.1/src/sexp_conv_grammar.mli 
new/ocaml-sexplib0-0.16.0/src/sexp_conv_grammar.mli
--- old/ocaml-sexplib0-0.15.1/src/sexp_conv_grammar.mli 2022-06-15 
18:57:16.000000000 +0200
+++ new/ocaml-sexplib0-0.16.0/src/sexp_conv_grammar.mli 2023-04-25 
15:12:25.000000000 +0200
@@ -1,3 +1,15 @@
+(** Grammar constructors. *)
+
+val sexp_grammar_with_tags
+  :  Sexp_grammar.grammar
+  -> tags:(string * Sexp.t) list
+  -> Sexp_grammar.grammar
+
+val sexp_grammar_with_tag_list
+  :  'a Sexp_grammar.with_tag_list
+  -> tags:(string * Sexp.t) list
+  -> 'a Sexp_grammar.with_tag_list
+
 (** Sexp grammar definitions. *)
 
 val unit_sexp_grammar : unit Sexp_grammar.t
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ocaml-sexplib0-0.15.1/src/sexp_conv_record.ml 
new/ocaml-sexplib0-0.16.0/src/sexp_conv_record.ml
--- old/ocaml-sexplib0-0.15.1/src/sexp_conv_record.ml   1970-01-01 
01:00:00.000000000 +0100
+++ new/ocaml-sexplib0-0.16.0/src/sexp_conv_record.ml   2023-04-25 
15:12:25.000000000 +0200
@@ -0,0 +1,297 @@
+open! StdLabels
+open! Sexp_conv
+open! Sexp_conv_error
+
+module Kind = struct
+  type (_, _) t =
+    | Default : (unit -> 'a) -> ('a, Sexp.t -> 'a) t
+    | Omit_nil : ('a, Sexp.t -> 'a) t
+    | Required : ('a, Sexp.t -> 'a) t
+    | Sexp_array : ('a array, Sexp.t -> 'a) t
+    | Sexp_bool : (bool, unit) t
+    | Sexp_list : ('a list, Sexp.t -> 'a) t
+    | Sexp_option : ('a option, Sexp.t -> 'a) t
+end
+
+module Fields = struct
+  type _ t =
+    | Empty : unit t
+    | Field :
+        { name : string
+        ; kind : ('a, 'conv) Kind.t
+        ; conv : 'conv
+        ; rest : 'b t
+        }
+        -> ('a * 'b) t
+
+  let length =
+    let rec length_loop : type a. a t -> int -> int =
+      fun t acc ->
+        match t with
+        | Field { rest; _ } -> length_loop rest (acc + 1)
+        | Empty -> acc
+    in
+    fun t -> length_loop t 0
+  ;;
+end
+
+module Malformed = struct
+  (* Represents errors that can occur due to malformed record sexps. 
Accumulated as a
+     value so we can report multiple names at once for extra fields, duplicate 
fields, or
+     missing fields. *)
+  type t =
+    | Bool_payload
+    | Extras of string list
+    | Dups of string list
+    | Missing of string list
+    | Non_pair of Sexp.t option
+
+  let combine a b =
+    match a, b with
+    (* choose the first bool-payload or non-pair error that occurs *)
+    | ((Bool_payload | Non_pair _) as t), _ -> t
+    | _, ((Bool_payload | Non_pair _) as t) -> t
+    (* combine lists of similar errors *)
+    | Extras a, Extras b -> Extras (a @ b)
+    | Dups a, Dups b -> Dups (a @ b)
+    | Missing a, Missing b -> Missing (a @ b)
+    (* otherwise, dups > extras > missing *)
+    | (Dups _ as t), _ | _, (Dups _ as t) -> t
+    | (Extras _ as t), _ | _, (Extras _ as t) -> t
+  ;;
+
+  let raise t ~caller ~context =
+    match t with
+    | Bool_payload -> record_sexp_bool_with_payload caller context
+    | Extras names -> record_extra_fields caller (List.rev names) context
+    | Dups names -> record_duplicate_fields caller (List.rev names) context
+    | Missing names ->
+      List.map names ~f:(fun name -> true, name)
+      |> record_undefined_elements caller context
+    | Non_pair maybe_context ->
+      let context = Option.value maybe_context ~default:context in
+      record_only_pairs_expected caller context
+  ;;
+end
+
+exception Malformed of Malformed.t
+
+module State = struct
+  (* Stores sexps corresponding to record fields, in the order the fields were 
declared.
+     Excludes fields already parsed in the fast path.
+
+     List sexps represent a field that is present, such as (x 1) for a field 
named "x".
+     Atom sexps represent a field that is absent, or at least not yet seen. *)
+  type t = { state : Sexp.t array } [@@unboxed]
+
+  let unsafe_get t pos = Array.unsafe_get t.state pos
+  let unsafe_set t pos sexp = Array.unsafe_set t.state pos sexp
+  let absent = Sexp.Atom ""
+  let create len = { state = Array.make len absent }
+end
+
+(* Parsing field values from state. *)
+
+let rec parse_value_malformed
+  : type a b. Malformed.t -> fields:(a * b) Fields.t -> state:State.t -> 
pos:int -> a
+  =
+  fun malformed ~fields ~state ~pos ->
+  let (Field field) = fields in
+  let malformed =
+    match parse_values ~fields:field.rest ~state ~pos:(pos + 1) with
+    | (_ : b) -> malformed
+    | exception Malformed other -> Malformed.combine malformed other
+  in
+  raise (Malformed malformed)
+
+and parse_value : type a b. fields:(a * b) Fields.t -> state:State.t -> 
pos:int -> a * b =
+  fun ~fields ~state ~pos ->
+  let (Field { name; kind; conv; rest }) = fields in
+  let value : a =
+    match kind, State.unsafe_get state pos with
+    (* well-formed *)
+    | Required, List [ _; sexp ] -> conv sexp
+    | Default _, List [ _; sexp ] -> conv sexp
+    | Omit_nil, List [ _; sexp ] -> conv sexp
+    | Sexp_option, List [ _; sexp ] -> Some (conv sexp)
+    | Sexp_list, List [ _; sexp ] -> list_of_sexp conv sexp
+    | Sexp_array, List [ _; sexp ] -> array_of_sexp conv sexp
+    | Sexp_bool, List [ _ ] -> true
+    (* ill-formed *)
+    | ( (Required | Default _ | Omit_nil | Sexp_option | Sexp_list | 
Sexp_array)
+      , (List (_ :: _ :: _ :: _) as sexp) ) ->
+      parse_value_malformed (Non_pair (Some sexp)) ~fields ~state ~pos
+    | ( (Required | Default _ | Omit_nil | Sexp_option | Sexp_list | 
Sexp_array)
+      , List ([] | [ _ ]) ) -> parse_value_malformed (Non_pair None) ~fields 
~state ~pos
+    | Sexp_bool, List ([] | _ :: _ :: _) ->
+      parse_value_malformed Bool_payload ~fields ~state ~pos
+    (* absent *)
+    | Required, Atom _ -> parse_value_malformed (Missing [ name ]) ~fields 
~state ~pos
+    | Default default, Atom _ -> default ()
+    | Omit_nil, Atom _ -> conv (List [])
+    | Sexp_option, Atom _ -> None
+    | Sexp_list, Atom _ -> []
+    | Sexp_array, Atom _ -> [||]
+    | Sexp_bool, Atom _ -> false
+  in
+  value, parse_values ~fields:rest ~state ~pos:(pos + 1)
+
+and parse_values : type a. fields:a Fields.t -> state:State.t -> pos:int -> a =
+  fun ~fields ~state ~pos ->
+  match fields with
+  | Field _ -> parse_value ~fields ~state ~pos
+  | Empty -> ()
+;;
+
+(* Populating state. Handles slow path cases where there may be reordered, 
duplicated,
+   missing, or extra fields. *)
+
+let rec parse_spine_malformed malformed ~index ~extra ~seen ~state ~len sexps =
+  let malformed =
+    match parse_spine_slow ~index ~extra ~seen ~state ~len sexps with
+    | () -> malformed
+    | exception Malformed other -> Malformed.combine malformed other
+  in
+  raise (Malformed malformed)
+
+and parse_spine_slow ~index ~extra ~seen ~state ~len sexps =
+  match (sexps : Sexp.t list) with
+  | [] -> ()
+  | (List (Atom name :: _) as field) :: sexps ->
+    let i = index name in
+    (match seen <= i && i < len with
+     | true ->
+       (* valid field for slow-path parsing *)
+       let pos = i - seen in
+       (match State.unsafe_get state pos with
+        | Atom _ ->
+          (* field not seen yet *)
+          State.unsafe_set state pos field;
+          parse_spine_slow ~index ~extra ~seen ~state ~len sexps
+        | List _ ->
+          (* field already seen *)
+          parse_spine_malformed (Dups [ name ]) ~index ~extra ~seen ~state 
~len sexps)
+     | false ->
+       (match 0 <= i && i < seen with
+        | true ->
+          (* field seen in fast path *)
+          parse_spine_malformed (Dups [ name ]) ~index ~extra ~seen ~state 
~len sexps
+        | false ->
+          (* extra field *)
+          (match extra with
+           | true -> parse_spine_slow ~index ~extra ~seen ~state ~len sexps
+           | false ->
+             parse_spine_malformed (Extras [ name ]) ~index ~extra ~seen 
~state ~len sexps)))
+  | sexp :: sexps ->
+    parse_spine_malformed (Non_pair (Some sexp)) ~index ~extra ~seen ~state 
~len sexps
+;;
+
+(* Slow path for record parsing. Uses state to store fields as they are 
discovered. *)
+
+let parse_record_slow ~fields ~index ~extra ~seen sexps =
+  let unseen = Fields.length fields in
+  let state = State.create unseen in
+  let len = seen + unseen in
+  (* populate state *)
+  parse_spine_slow ~index ~extra ~seen ~state ~len sexps;
+  (* parse values from state *)
+  parse_values ~fields ~state ~pos:0
+;;
+
+(* Fast path for record parsing. Directly parses and returns fields in the 
order they are
+   declared. Falls back on slow path if any fields are absent, reordered, or 
malformed. *)
+
+let rec parse_field_fast
+  : type a b.
+    fields:(a * b) Fields.t
+    -> index:(string -> int)
+    -> extra:bool
+    -> seen:int
+    -> Sexp.t list
+    -> a * b
+  =
+  fun ~fields ~index ~extra ~seen sexps ->
+  let (Field { name; kind; conv; rest }) = fields in
+  match sexps with
+  | List (Atom atom :: args) :: others when String.equal atom name ->
+    (match kind, args with
+     | Required, [ sexp ] ->
+       conv sexp, parse_spine_fast ~fields:rest ~index ~extra ~seen:(seen + 1) 
others
+     | Default _, [ sexp ] ->
+       conv sexp, parse_spine_fast ~fields:rest ~index ~extra ~seen:(seen + 1) 
others
+     | Omit_nil, [ sexp ] ->
+       conv sexp, parse_spine_fast ~fields:rest ~index ~extra ~seen:(seen + 1) 
others
+     | Sexp_option, [ sexp ] ->
+       ( Some (conv sexp)
+       , parse_spine_fast ~fields:rest ~index ~extra ~seen:(seen + 1) others )
+     | Sexp_list, [ sexp ] ->
+       ( list_of_sexp conv sexp
+       , parse_spine_fast ~fields:rest ~index ~extra ~seen:(seen + 1) others )
+     | Sexp_array, [ sexp ] ->
+       ( array_of_sexp conv sexp
+       , parse_spine_fast ~fields:rest ~index ~extra ~seen:(seen + 1) others )
+     | Sexp_bool, [] ->
+       true, parse_spine_fast ~fields:rest ~index ~extra ~seen:(seen + 1) 
others
+     (* malformed field of some kind, dispatch to slow path *)
+     | _, _ -> parse_record_slow ~fields ~index ~extra ~seen sexps)
+  (* malformed or out-of-order field, dispatch to slow path *)
+  | _ -> parse_record_slow ~fields ~index ~extra ~seen sexps
+
+and parse_spine_fast
+  : type a.
+    fields:a Fields.t
+    -> index:(string -> int)
+    -> extra:bool
+    -> seen:int
+    -> Sexp.t list
+    -> a
+  =
+  fun ~fields ~index ~extra ~seen sexps ->
+  match fields with
+  | Field _ -> parse_field_fast ~fields ~index ~extra ~seen sexps
+  | Empty ->
+    (match sexps with
+     | [] -> ()
+     | _ :: _ ->
+       (* extra sexps, dispatch to slow path *)
+       parse_record_slow ~fields ~index ~extra ~seen sexps)
+;;
+
+let parse_record_fast ~fields ~index ~extra sexps =
+  parse_spine_fast ~fields ~index ~extra ~seen:0 sexps
+;;
+
+(* Entry points. *)
+
+let record_of_sexps
+      ~caller
+      ~context
+      ~fields
+      ~index_of_field
+      ~allow_extra_fields
+      ~create
+      sexps
+  =
+  let allow_extra_fields =
+    allow_extra_fields || not !Sexp_conv.record_check_extra_fields
+  in
+  match
+    parse_record_fast ~fields ~index:index_of_field ~extra:allow_extra_fields 
sexps
+  with
+  | value -> create value
+  | exception Malformed malformed -> Malformed.raise malformed ~caller ~context
+;;
+
+let record_of_sexp ~caller ~fields ~index_of_field ~allow_extra_fields ~create 
sexp =
+  match (sexp : Sexp.t) with
+  | Atom _ as context -> record_list_instead_atom caller context
+  | List sexps as context ->
+    record_of_sexps
+      ~caller
+      ~context
+      ~fields
+      ~index_of_field
+      ~allow_extra_fields
+      ~create
+      sexps
+;;
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ocaml-sexplib0-0.15.1/src/sexp_conv_record.mli 
new/ocaml-sexplib0-0.16.0/src/sexp_conv_record.mli
--- old/ocaml-sexplib0-0.15.1/src/sexp_conv_record.mli  1970-01-01 
01:00:00.000000000 +0100
+++ new/ocaml-sexplib0-0.16.0/src/sexp_conv_record.mli  2023-04-25 
15:12:25.000000000 +0200
@@ -0,0 +1,54 @@
+module Kind : sig
+  (** A GADT specifying how to parse a record field. See documentation for
+      [ppx_sexp_conv]. *)
+  type (_, _) t =
+    | Default : (unit -> 'a) -> ('a, Sexp.t -> 'a) t
+    | Omit_nil : ('a, Sexp.t -> 'a) t
+    | Required : ('a, Sexp.t -> 'a) t
+    | Sexp_array : ('a array, Sexp.t -> 'a) t
+    | Sexp_bool : (bool, unit) t
+    | Sexp_list : ('a list, Sexp.t -> 'a) t
+    | Sexp_option : ('a option, Sexp.t -> 'a) t
+end
+
+module Fields : sig
+  (** A GADT specifying record fields. *)
+  type _ t =
+    | Empty : unit t
+    | Field :
+        { name : string
+        ; kind : ('a, 'conv) Kind.t
+        ; conv : 'conv
+        ; rest : 'b t
+        }
+        -> ('a * 'b) t
+end
+
+(** Parses a record from a sexp that must be a list of fields.
+
+    Uses [caller] as the source for error messages. Parses using the given 
[field]s. Uses
+    [index_of_field] to look up field names found in sexps. If 
[allow_extra_fields] is
+    true, extra fields are allowed and discarded without error. [create] is 
used to
+    construct the final returned value.
+*)
+val record_of_sexp
+  :  caller:string
+  -> fields:'a Fields.t
+  -> index_of_field:(string -> int)
+  -> allow_extra_fields:bool
+  -> create:('a -> 'b)
+  -> Sexp.t
+  -> 'b
+
+(** Like [record_of_sexp], but for a list of sexps with no [List] wrapper. 
Used, for
+    example, to parse arguments to a variant constructor with an inlined 
record argument.
+    Reports [context] for parse errors when no more specific sexp is 
applicable. *)
+val record_of_sexps
+  :  caller:string
+  -> context:Sexp.t
+  -> fields:'a Fields.t
+  -> index_of_field:(string -> int)
+  -> allow_extra_fields:bool
+  -> create:('a -> 'b)
+  -> Sexp.t list
+  -> 'b
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ocaml-sexplib0-0.15.1/src/sexp_grammar.ml 
new/ocaml-sexplib0-0.16.0/src/sexp_grammar.ml
--- old/ocaml-sexplib0-0.15.1/src/sexp_grammar.ml       2022-06-15 
18:57:16.000000000 +0200
+++ new/ocaml-sexplib0-0.16.0/src/sexp_grammar.ml       2023-04-25 
15:12:25.000000000 +0200
@@ -33,20 +33,12 @@
   | Tyvar of string
   (** Name of a type variable, e.g. [Tyvar "a"] for ['a]. Only meaningful when 
the body of
       the innermost enclosing [defn] defines a corresponding type variable. *)
-  | Tycon of string * grammar list
-  (** Type constructor applied to arguments. For example, [Tycon ("list", [ 
Integer ])]
-      represents [int list]. Only meaningful when the innermost enclosing 
[Recursive]
-      grammar defines a corresponding type constructor. *)
-  | Recursive of grammar * defn list
-  (** [Recursive (grammar, definitions)] allows [grammar] to refer to type 
constructors
-      from the mutually recursive [definitions]. The definitions may also 
refer to each
-      others' type constructors.
+  | Tycon of string * grammar list * defn list
+  (** Type constructor applied to arguments, and its definition.
 
-      Ordinarily, [grammar] itself is just a [Tycon] argument, although 
technically it can
-      be any grammar.
-
-      For example, the following definitions define a binary tree 
parameterized by a type
-      stored at its leaves.
+      For example, writing [Tycon ("tree", [ Integer ], defns)] represents 
[int tree], for
+      whatever [tree] is defined as in [defns]. The following defines [tree] 
as a binary
+      tree with the parameter type stored at the leaves.
 
       {[
         let defns =
@@ -57,17 +49,17 @@
                   { name_kind = Capitalized
                   ; clauses =
                       [ { name = "Node"
-                        ; args = Cons (Tycon ("node", [Tyvar "a"]), Empty)
+                        ; args = Cons (Recursive ("node", [Tyvar "a"]), Empty)
                         }
-                      ; { name = "Tree"
-                        ; args = Cons (Tycon ("leaf", [Tyvar "a"]), Empty)
+                      ; { name = "Leaf"
+                        ; args = Cons (Recursive ("leaf", [Tyvar "a"]), Empty)
                         }
                       ]
                   }
             }
           ; { tycon = "node"
             ; tyvars = ["a"]
-            ; grammar = List (Many (Tycon "tree", [Tyvar "a"]))
+            ; grammar = List (Many (Recursive "tree", [Tyvar "a"]))
             }
           ; { tycon = "leaf"
             ; tyvars = ["a"]
@@ -77,31 +69,32 @@
         ;;
       ]}
 
-      Normally, the type of a tree storing integers would be written like this:
+      To illustrate the meaning of [Tycon] with respect to [defns], and to 
demonstrate one
+      way to access them, it is equivalent to expand the definition of "tree" 
one level
+      and move the [defns] to enclosed recursive references:
 
       {[
-        Recursive (Tycon ("tree", [ Integer ]), defns)
+        Tycon ("tree", [ Integer ], defns)
+        -->
+        Variant
+          { name_kind = Capitalized
+          ; clauses =
+              [ { name = "Node"
+                ; args = Cons (Tycon ("node", [Tyvar "a"], defns), Empty)
+                }
+              ; { name = "Leaf"
+                ; args = Cons (Tycon ("leaf", [Tyvar "a"], defns), Empty)
+                }
+              ]
+          }
       ]}
 
-      It is equivalent, though needlessly verbose, to replace the [Tycon] 
reference with
-      the grammar of ["tree"], substituting [Integer] for [Tyvar "a"]:
-
-      {[
-        Recursive
-          ( Variant
-              { name_kind = Capitalized
-              ; clauses =
-                  [ { name = "Node"
-                    ; args = Cons (Tycon ("node", [Tyvar "a"]), Empty)
-                    }
-                  ; { name = "Tree"
-                    ; args = Cons (Tycon ("leaf", [Tyvar "a"]), Empty)
-                    }
-                  ]
-              }
-          , defns )
-      ]}
-  *)
+      This transformation exposes the structure of a grammar with recursive 
references,
+      while preserving the meaning of recursively-defined elements. *)
+  | Recursive of string * grammar list
+  (** Type constructor applied to arguments. Used to denote recursive type 
references.
+      Only meaningful when used inside the [defn]s of a [Tycon] grammar, to 
refer to a
+      type constructor in the nearest enclosing [defn] list. *)
   | Lazy of grammar lazy_t
   (** Lazily computed grammar. Use [Lazy] to avoid top-level side effects. To 
define
       recursive grammars, use [Recursive] instead. *)
@@ -187,5 +180,28 @@
 
 let coerce (type a b) ({ untyped = _ } as t : a t) : b t = t
 
+let tag (type a) ({ untyped = grammar } : a t) ~key ~value : a t =
+  { untyped = Tagged { key; value; grammar } }
+;;
+
 (** This reserved key is used for all tags generated from doc comments. *)
 let doc_comment_tag = "sexp_grammar.doc_comment"
+
+(** This reserved key can be used to associate a type name with a grammar. *)
+let type_name_tag = "sexp_grammar.type_name"
+
+(** This reserved key indicates that a sexp represents a key/value 
association. The tag's
+    value is ignored. *)
+let assoc_tag = "sexp_grammar.assoc"
+
+(** This reserved key indicates that a sexp is a key in a key/value 
association. The tag's
+    value is ignored. *)
+let assoc_key_tag = "sexp_grammar.assoc.key"
+
+(** This reserved key indicates that a sexp is a value in a key/value 
association. The
+    tag's value is ignored. *)
+let assoc_value_tag = "sexp_grammar.assoc.value"
+
+(** When the key is set to [Atom "false"] for a variant clause, that clause 
should not be
+    suggested in auto-completion based on the sexp grammar. *)
+let completion_suggested = "sexp_grammar.completion-suggested"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ocaml-sexplib0-0.15.1/src/sexplib0.ml 
new/ocaml-sexplib0-0.16.0/src/sexplib0.ml
--- old/ocaml-sexplib0-0.15.1/src/sexplib0.ml   2022-06-15 18:57:16.000000000 
+0200
+++ new/ocaml-sexplib0-0.16.0/src/sexplib0.ml   2023-04-25 15:12:25.000000000 
+0200
@@ -1,5 +1,6 @@
 module Sexp = Sexp
 module Sexp_conv = Sexp_conv
 module Sexp_conv_error = Sexp_conv_error
+module Sexp_conv_record = Sexp_conv_record
 module Sexp_grammar = Sexp_grammar
 module Sexpable = Sexpable
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ocaml-sexplib0-0.15.1/test/dune 
new/ocaml-sexplib0-0.16.0/test/dune
--- old/ocaml-sexplib0-0.15.1/test/dune 1970-01-01 01:00:00.000000000 +0100
+++ new/ocaml-sexplib0-0.16.0/test/dune 2023-04-25 15:12:25.000000000 +0200
@@ -0,0 +1,4 @@
+(library (name sexplib0_test)
+ (libraries base expect_test_helpers_core.expect_test_helpers_base sexplib0)
+ (preprocess
+  (pps ppx_compare ppx_expect ppx_here ppx_sexp_conv ppx_sexp_value)))
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ocaml-sexplib0-0.15.1/test/sexplib0_test.ml 
new/ocaml-sexplib0-0.16.0/test/sexplib0_test.ml
--- old/ocaml-sexplib0-0.15.1/test/sexplib0_test.ml     1970-01-01 
01:00:00.000000000 +0100
+++ new/ocaml-sexplib0-0.16.0/test/sexplib0_test.ml     2023-04-25 
15:12:25.000000000 +0200
@@ -0,0 +1,452 @@
+open! Base
+open Expect_test_helpers_base
+open Sexplib0
+
+let () = sexp_style := Sexp_style.simple_pretty
+
+module type S = sig
+  type t [@@deriving equal, sexp]
+end
+
+let test (type a) (module M : S with type t = a) string =
+  let sexp = Parsexp.Single.parse_string_exn string in
+  let result = Or_error.try_with (fun () -> M.t_of_sexp sexp) in
+  print_s [%sexp (result : M.t Or_error.t)]
+;;
+
+let%expect_test "simple record" =
+  let module M = struct
+    type t =
+      { x : int
+      ; y : int
+      }
+    [@@deriving equal, sexp_of]
+
+    let t_of_sexp =
+      Sexp_conv_record.record_of_sexp
+        ~caller:"M.t"
+        ~fields:
+          (Field
+             { name = "x"
+             ; kind = Required
+             ; conv = int_of_sexp
+             ; rest =
+                 Field { name = "y"; kind = Required; conv = int_of_sexp; rest 
= Empty }
+             })
+        ~index_of_field:(function
+          | "x" -> 0
+          | "y" -> 1
+          | _ -> -1)
+        ~allow_extra_fields:false
+        ~create:(fun (x, (y, ())) -> { x; y })
+    ;;
+  end
+  in
+  let test = test (module M) in
+  (* in order *)
+  test "((x 1) (y 2))";
+  [%expect {| (Ok ((x 1) (y 2))) |}];
+  (* reverse order *)
+  test "((y 2) (x 1))";
+  [%expect {| (Ok ((x 1) (y 2))) |}];
+  (* duplicate fields *)
+  test "((x 1) (x 2) (y 3) (y 4))";
+  [%expect
+    {|
+    (Error
+     (Of_sexp_error
+      "M.t_of_sexp: duplicate fields: x y"
+      (invalid_sexp ((x 1) (x 2) (y 3) (y 4))))) |}];
+  (* extra fields *)
+  test "((a 1) (b 2) (c 3))";
+  [%expect
+    {|
+    (Error
+     (Of_sexp_error
+      "M.t_of_sexp: extra fields: a b c"
+      (invalid_sexp ((a 1) (b 2) (c 3))))) |}];
+  (* missing field *)
+  test "((x 1))";
+  [%expect
+    {|
+    (Error
+     (Of_sexp_error
+      "M.t_of_sexp: the following record elements were undefined: y"
+      (invalid_sexp ((x 1))))) |}];
+  (* other missing field *)
+  test "((y 2))";
+  [%expect
+    {|
+    (Error
+     (Of_sexp_error
+      "M.t_of_sexp: the following record elements were undefined: x"
+      (invalid_sexp ((y 2))))) |}];
+  (* multiple missing fields *)
+  test "()";
+  [%expect
+    {|
+    (Error
+     (Of_sexp_error
+      "M.t_of_sexp: the following record elements were undefined: x y"
+      (invalid_sexp ()))) |}];
+  ()
+;;
+
+let%expect_test "record with extra fields" =
+  let module M = struct
+    type t =
+      { x : int
+      ; y : int
+      }
+    [@@deriving equal, sexp_of]
+
+    let t_of_sexp =
+      Sexp_conv_record.record_of_sexp
+        ~caller:"M.t"
+        ~fields:
+          (Field
+             { name = "x"
+             ; kind = Required
+             ; conv = int_of_sexp
+             ; rest =
+                 Field { name = "y"; kind = Required; conv = int_of_sexp; rest 
= Empty }
+             })
+        ~index_of_field:(function
+          | "x" -> 0
+          | "y" -> 1
+          | _ -> -1)
+        ~allow_extra_fields:true
+        ~create:(fun (x, (y, ())) -> { x; y })
+    ;;
+  end
+  in
+  let test = test (module M) in
+  (* in order *)
+  test "((x 1) (y 2))";
+  [%expect {| (Ok ((x 1) (y 2))) |}];
+  (* reversed order *)
+  test "((y 2) (x 1))";
+  [%expect {| (Ok ((x 1) (y 2))) |}];
+  (* extra field *)
+  test "((x 1) (y 2) (z 3))";
+  [%expect {| (Ok ((x 1) (y 2))) |}];
+  (* missing field *)
+  test "((x 1))";
+  [%expect
+    {|
+    (Error
+     (Of_sexp_error
+      "M.t_of_sexp: the following record elements were undefined: y"
+      (invalid_sexp ((x 1))))) |}];
+  (* other missing field *)
+  test "((y 2))";
+  [%expect
+    {|
+    (Error
+     (Of_sexp_error
+      "M.t_of_sexp: the following record elements were undefined: x"
+      (invalid_sexp ((y 2))))) |}];
+  (* multiple missing fields *)
+  test "()";
+  [%expect
+    {|
+    (Error
+     (Of_sexp_error
+      "M.t_of_sexp: the following record elements were undefined: x y"
+      (invalid_sexp ()))) |}];
+  ()
+;;
+
+let%expect_test "record with defaults" =
+  let module M = struct
+    type t =
+      { x : int
+      ; y : int
+      }
+    [@@deriving equal, sexp_of]
+
+    let t_of_sexp =
+      Sexp_conv_record.record_of_sexp
+        ~caller:"M.t"
+        ~fields:
+          (Field
+             { name = "x"
+             ; kind = Default (fun () -> 0)
+             ; conv = int_of_sexp
+             ; rest =
+                 Field
+                   { name = "y"
+                   ; kind = Default (fun () -> 0)
+                   ; conv = int_of_sexp
+                   ; rest = Empty
+                   }
+             })
+        ~index_of_field:(function
+          | "x" -> 0
+          | "y" -> 1
+          | _ -> -1)
+        ~allow_extra_fields:false
+        ~create:(fun (x, (y, ())) -> { x; y })
+    ;;
+  end
+  in
+  let test = test (module M) in
+  (* in order *)
+  test "((x 1) (y 2))";
+  [%expect {| (Ok ((x 1) (y 2))) |}];
+  (* reverse order *)
+  test "((y 2) (x 1))";
+  [%expect {| (Ok ((x 1) (y 2))) |}];
+  (* extra field *)
+  test "((x 1) (y 2) (z 3))";
+  [%expect
+    {|
+    (Error
+     (Of_sexp_error
+      "M.t_of_sexp: extra fields: z"
+      (invalid_sexp ((x 1) (y 2) (z 3))))) |}];
+  (* missing field *)
+  test "((x 1))";
+  [%expect {| (Ok ((x 1) (y 0))) |}];
+  (* other missing field *)
+  test "((y 2))";
+  [%expect {| (Ok ((x 0) (y 2))) |}];
+  (* multiple missing fields *)
+  test "()";
+  [%expect {| (Ok ((x 0) (y 0))) |}];
+  ()
+;;
+
+let%expect_test "record with omit nil" =
+  let module M = struct
+    type t =
+      { a : int option
+      ; b : int list
+      }
+    [@@deriving equal, sexp_of]
+
+    let t_of_sexp =
+      Sexp_conv_record.record_of_sexp
+        ~caller:"M.t"
+        ~fields:
+          (Field
+             { name = "a"
+             ; kind = Omit_nil
+             ; conv = option_of_sexp int_of_sexp
+             ; rest =
+                 Field
+                   { name = "b"
+                   ; kind = Omit_nil
+                   ; conv = list_of_sexp int_of_sexp
+                   ; rest = Empty
+                   }
+             })
+        ~index_of_field:(function
+          | "a" -> 0
+          | "b" -> 1
+          | _ -> -1)
+        ~allow_extra_fields:false
+        ~create:(fun (a, (b, ())) -> { a; b })
+    ;;
+  end
+  in
+  let test = test (module M) in
+  (* in order *)
+  test "((a (1)) (b (2 3)))";
+  [%expect {| (Ok ((a (1)) (b (2 3)))) |}];
+  (* reverse order *)
+  test "((b ()) (a ()))";
+  [%expect {| (Ok ((a ()) (b ()))) |}];
+  (* extra field *)
+  test "((a (1)) (b (2 3)) (z ()))";
+  [%expect
+    {|
+    (Error
+     (Of_sexp_error
+      "M.t_of_sexp: extra fields: z"
+      (invalid_sexp ((a (1)) (b (2 3)) (z ()))))) |}];
+  (* missing field *)
+  test "((a (1)))";
+  [%expect {| (Ok ((a (1)) (b ()))) |}];
+  (* other missing field *)
+  test "((b (2 3)))";
+  [%expect {| (Ok ((a ()) (b (2 3)))) |}];
+  (* multiple missing fields *)
+  test "()";
+  [%expect {| (Ok ((a ()) (b ()))) |}];
+  ()
+;;
+
+let%expect_test "record with sexp types" =
+  let module M = struct
+    type t =
+      { a : int option
+      ; b : int list
+      ; c : int array
+      ; d : bool
+      }
+    [@@deriving equal, sexp_of]
+
+    let t_of_sexp =
+      Sexp_conv_record.record_of_sexp
+        ~caller:"M.t"
+        ~fields:
+          (Field
+             { name = "a"
+             ; kind = Sexp_option
+             ; conv = int_of_sexp
+             ; rest =
+                 Field
+                   { name = "b"
+                   ; kind = Sexp_list
+                   ; conv = int_of_sexp
+                   ; rest =
+                       Field
+                         { name = "c"
+                         ; kind = Sexp_array
+                         ; conv = int_of_sexp
+                         ; rest =
+                             Field
+                               { name = "d"; kind = Sexp_bool; conv = (); rest 
= Empty }
+                         }
+                   }
+             })
+        ~index_of_field:(function
+          | "a" -> 0
+          | "b" -> 1
+          | "c" -> 2
+          | "d" -> 3
+          | _ -> -1)
+        ~allow_extra_fields:false
+        ~create:(fun (a, (b, (c, (d, ())))) -> { a; b; c; d })
+    ;;
+  end
+  in
+  let test = test (module M) in
+  (* in order *)
+  test "((a 1) (b (2 3)) (c (4 5)) (d))";
+  [%expect {| (Ok ((a (1)) (b (2 3)) (c (4 5)) (d true))) |}];
+  (* reverse order *)
+  test "((d) (c ()) (b ()) (a 1))";
+  [%expect {| (Ok ((a (1)) (b ()) (c ()) (d true))) |}];
+  (* missing field d *)
+  test "((a 1) (b (2 3)) (c (4 5)))";
+  [%expect {| (Ok ((a (1)) (b (2 3)) (c (4 5)) (d false))) |}];
+  (* missing field c *)
+  test "((a 1) (b (2 3)) (d))";
+  [%expect {| (Ok ((a (1)) (b (2 3)) (c ()) (d true))) |}];
+  (* missing field b *)
+  test "((a 1) (c (2 3)) (d))";
+  [%expect {| (Ok ((a (1)) (b ()) (c (2 3)) (d true))) |}];
+  (* missing field a *)
+  test "((b (1 2)) (c (3 4)) (d))";
+  [%expect {| (Ok ((a ()) (b (1 2)) (c (3 4)) (d true))) |}];
+  (* extra field *)
+  test "((a 1) (b (2 3)) (c (4 5)) (d) (e (6 7)))";
+  [%expect
+    {|
+    (Error
+     (Of_sexp_error
+      "M.t_of_sexp: extra fields: e"
+      (invalid_sexp ((a 1) (b (2 3)) (c (4 5)) (d) (e (6 7)))))) |}];
+  (* all fields missing *)
+  test "()";
+  [%expect {| (Ok ((a ()) (b ()) (c ()) (d false))) |}];
+  ()
+;;
+
+let%expect_test "record with polymorphic fields" =
+  let module M = struct
+    type t =
+      { a : 'a. 'a list
+      ; b : 'a 'b. ('a, 'b) Result.t option
+      }
+    [@@deriving sexp_of]
+
+    let equal = Poly.equal
+
+    let t_of_sexp =
+      let open struct
+        type a = { a : 'a. 'a list } [@@unboxed]
+        type b = { b : 'a 'b. ('a, 'b) Result.t option } [@@unboxed]
+      end in
+      let caller = "M.t" in
+      Sexp_conv_record.record_of_sexp
+        ~caller
+        ~fields:
+          (Field
+             { name = "a"
+             ; kind = Required
+             ; conv =
+                 (fun sexp ->
+                    { a =
+                        list_of_sexp
+                          (Sexplib.Conv_error.record_poly_field_value caller)
+                          sexp
+                    })
+             ; rest =
+                 Field
+                   { name = "b"
+                   ; kind = Required
+                   ; conv =
+                       (fun sexp ->
+                          { b =
+                              Option.t_of_sexp
+                                (Result.t_of_sexp
+                                   (Sexplib.Conv_error.record_poly_field_value 
caller)
+                                   (Sexplib.Conv_error.record_poly_field_value 
caller))
+                                sexp
+                          })
+                   ; rest = Empty
+                   }
+             })
+        ~index_of_field:(function
+          | "a" -> 0
+          | "b" -> 1
+          | _ -> -1)
+        ~allow_extra_fields:false
+        ~create:(fun ({ a }, ({ b }, ())) -> { a; b })
+    ;;
+  end
+  in
+  let test = test (module M) in
+  (* in order *)
+  test "((a ()) (b ()))";
+  [%expect {| (Ok ((a ()) (b ()))) |}];
+  (* reverse order *)
+  test "((b ()) (a ()))";
+  [%expect {| (Ok ((a ()) (b ()))) |}];
+  (* attempt to deserialize paramter to [a] *)
+  test "((a (_)) (b ()))";
+  [%expect
+    {|
+    (Error
+     (Of_sexp_error
+      "M.t_of_sexp: cannot convert values of types resulting from polymorphic 
record fields"
+      (invalid_sexp _))) |}];
+  (* attempt to deserialize first parameter to [b] *)
+  test "((a ()) (b ((Ok _))))";
+  [%expect
+    {|
+    (Error
+     (Of_sexp_error
+      "M.t_of_sexp: cannot convert values of types resulting from polymorphic 
record fields"
+      (invalid_sexp _))) |}];
+  (* attempt to deserialize second parameter to [b] *)
+  test "((a ()) (b ((Error _))))";
+  [%expect
+    {|
+    (Error
+     (Of_sexp_error
+      "M.t_of_sexp: cannot convert values of types resulting from polymorphic 
record fields"
+      (invalid_sexp _))) |}];
+  (* multiple missing fields *)
+  test "()";
+  [%expect
+    {|
+    (Error
+     (Of_sexp_error
+      "M.t_of_sexp: the following record elements were undefined: a b"
+      (invalid_sexp ()))) |}];
+  ()
+;;
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/ocaml-sexplib0-0.15.1/test/sexplib0_test.mli 
new/ocaml-sexplib0-0.16.0/test/sexplib0_test.mli
--- old/ocaml-sexplib0-0.15.1/test/sexplib0_test.mli    1970-01-01 
01:00:00.000000000 +0100
+++ new/ocaml-sexplib0-0.16.0/test/sexplib0_test.mli    2023-04-25 
15:12:25.000000000 +0200
@@ -0,0 +1 @@
+(*_ This signature is deliberately empty. *)

Reply via email to