Skip to content

Commit 94bc7e6

Browse files
committed
atdml: support <ocaml attr=...> on record fields, variant constructors, and payloads
1 parent c56b7d7 commit 94bc7e6

4 files changed

Lines changed: 125 additions & 18 deletions

File tree

CHANGES.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@
1212
package-private constructor from `JSONArray`, `toJsonBuffer`, `toJson`,
1313
and a public `value` field of type `ArrayList<T>`. List aliases used as
1414
record fields or sum-variant payloads are also handled correctly.
15+
* atdml: Add support for `<ocaml attr="...">` on record fields, variant constructors,
16+
and variant payload types to attach ppx attributes (e.g. `[@deriving.ord.ignore]`)
17+
1518

1619
4.1.0 (2026-04-11)
1720
------------------

atdgen/src/ocaml.ml

Lines changed: 71 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -167,11 +167,14 @@ let annot_schema_ocaml : Atd.Annot.schema_section =
167167
Type_expr, "validator";
168168
Type_expr, "wrap";
169169
Variant, "name";
170+
Variant, "attr";
171+
Type_expr, "attr";
170172
Cell, "default";
171173
Field, "default";
172174
Field, "mutable";
173175
Field, "name";
174176
Field, "repr";
177+
Field, "attr";
175178
]
176179
}
177180

@@ -398,6 +401,27 @@ let get_type_attrs an =
398401
~field:"attr"
399402
an
400403

404+
let get_field_attrs an =
405+
Atd.Annot.get_fields
406+
~parse:(fun s -> Some s)
407+
~sections:["ocaml"]
408+
~field:"attr"
409+
an
410+
411+
let get_variant_attrs an =
412+
Atd.Annot.get_fields
413+
~parse:(fun s -> Some s)
414+
~sections:["ocaml"]
415+
~field:"attr"
416+
an
417+
418+
let get_payload_attrs e =
419+
Atd.Annot.get_fields
420+
~parse:(fun s -> Some s)
421+
~sections:["ocaml"]
422+
~field:"attr"
423+
(Atd.Ast.annot_of_type_expr e)
424+
401425
(*
402426
OCaml syntax tree
403427
*)
@@ -413,9 +437,11 @@ type ocaml_expr =
413437

414438
and ocaml_variant =
415439
string * ocaml_expr option * Atd.Doc.doc option
440+
* string list (* cons attrs *) * string list (* payload attrs *)
416441

417442
and ocaml_field =
418443
(string * bool (* is mutable? *)) * ocaml_expr * Atd.Doc.doc option
444+
* string list (* attrs *)
419445

420446
(*
421447
OCaml type definition:
@@ -499,7 +525,13 @@ and map_variant ~kind target (x : variant) : ocaml_variant =
499525
"Inline records are not allowed in polymorphic variants (not valid in OCaml)"
500526
| _, Variant (loc, (s, an), o) ->
501527
let s = get_ocaml_cons target s an in
502-
(s, Option.map (map_expr target []) o, Atd.Doc.get_doc loc an)
528+
let cons_attrs = get_variant_attrs an in
529+
let payload_attrs = match o with
530+
| None -> []
531+
| Some e -> get_payload_attrs e
532+
in
533+
(s, Option.map (map_expr target []) o, Atd.Doc.get_doc loc an,
534+
cons_attrs, payload_attrs)
503535

504536
and map_field target ocaml_field_prefix (x : field) : ocaml_field =
505537
match x with
@@ -516,7 +548,9 @@ and map_field target ocaml_field_prefix (x : field) : ocaml_field =
516548
else sprintf "%s (*atd %s *)" ocaml_fname atd_fname
517549
in
518550
let is_mutable = get_ocaml_mutable target an in
519-
((fname, is_mutable), map_expr target [] x, Atd.Doc.get_doc loc an)
551+
let field_attrs = get_field_attrs an in
552+
((fname, is_mutable), map_expr target [] x, Atd.Doc.get_doc loc an,
553+
field_attrs)
520554

521555

522556
(* hack to deal with legacy behavior *)
@@ -683,7 +717,7 @@ and ocaml_of_variant_mapping x =
683717
Variant o -> o
684718
| _ -> assert false
685719
in
686-
(o.ocaml_cons, Option.map ocaml_of_expr_mapping x.var_arg, o.ocaml_vdoc)
720+
(o.ocaml_cons, Option.map ocaml_of_expr_mapping x.var_arg, o.ocaml_vdoc, [], [])
687721

688722
and ocaml_of_field_mapping x =
689723
let o =
@@ -692,7 +726,7 @@ and ocaml_of_field_mapping x =
692726
| _ -> assert false
693727
in
694728
let v = ocaml_of_expr_mapping x.f_value in
695-
((o.ocaml_fname, o.ocaml_mutable), v, o.ocaml_fdoc)
729+
((o.ocaml_fname, o.ocaml_mutable), v, o.ocaml_fdoc, [])
696730

697731

698732
(*
@@ -960,7 +994,7 @@ and format_type_expr x =
960994
and format_type_name name args =
961995
horizontal_sequence (prepend_type_args args [ make_atom name ])
962996

963-
and format_field ((s, is_mutable), t, doc) =
997+
and format_field ((s, is_mutable), t, doc, attrs) =
964998
let l =
965999
let l = [make_atom (s ^ ":")] in
9661000
if is_mutable then
@@ -973,22 +1007,49 @@ and format_field ((s, is_mutable), t, doc) =
9731007
format_type_expr t
9741008
)
9751009
in
976-
append_ocamldoc_comment field doc
1010+
let field_with_attrs =
1011+
match attrs with
1012+
| [] -> field
1013+
| _ ->
1014+
let attrs_str =
1015+
List.map (fun a -> sprintf "[@%s]" a) attrs |> String.concat ""
1016+
in
1017+
Label ((field, label), make_atom attrs_str)
1018+
in
1019+
append_ocamldoc_comment field_with_attrs doc
9771020

978-
and format_variant kind (s, o, doc) =
1021+
and format_variant kind (s, o, doc, cons_attrs, payload_attrs) =
9791022
let s = tick kind ^ s in
9801023
let cons = make_atom s in
1024+
let attrs_str attrs =
1025+
List.map (fun a -> sprintf "[@%s]" a) attrs |> String.concat ""
1026+
in
1027+
let format_payload t =
1028+
match payload_attrs with
1029+
| [] -> format_type_expr t
1030+
| _ ->
1031+
Easy_format.List (
1032+
("(", "", ")", shlist),
1033+
[format_type_expr t; make_atom (attrs_str payload_attrs)]
1034+
)
1035+
in
9811036
let variant =
9821037
match o with
983-
None -> cons
984-
| Some t ->
1038+
| None ->
1039+
if cons_attrs = [] then cons
1040+
else Label ((cons, label), make_atom (attrs_str cons_attrs))
1041+
| Some t ->
1042+
let with_payload =
9851043
Label (
9861044
(cons, label),
9871045
Label (
9881046
(make_atom "of", label),
989-
format_type_expr t
1047+
format_payload t
9901048
)
9911049
)
1050+
in
1051+
if cons_attrs = [] then with_payload
1052+
else Label ((with_payload, label), make_atom (attrs_str cons_attrs))
9921053
in
9931054
append_ocamldoc_comment variant doc
9941055

atdgen/test/test_ppx.atd

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,16 @@
11
type t <ocaml attr="deriving show,eq" attr="ocamlformat \"disable\""> = {
22
v: int;
3+
w: string <ocaml attr="compare.ignore">;
34
}
5+
6+
type status = [
7+
| Active <ocaml attr="deriving.ord.ignore">
8+
| Pending of int <ocaml attr="deriving.ord.ignore">
9+
| Inactive
10+
]
11+
12+
type poly_status = [
13+
| Active <ocaml attr="deriving.ord.ignore">
14+
| Pending of int <ocaml attr="deriving.ord.ignore">
15+
| Inactive
16+
] <ocaml repr="poly">

doc/atdgen-reference.rst

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -973,28 +973,58 @@ Section ``ocaml``
973973
Field ``attr``
974974
""""""""""""""
975975

976-
Position: on a type definition, i.e. on the left-handside just before
977-
the equal sign ``=``
976+
Position: on a type definition, a record field, a variant constructor,
977+
or a variant payload type expression
978978

979-
Semantics: specifies custom ppx attributes for the type
980-
definition. Overrides any default attributes set globally via
981-
the command line option ``-type-attr``.
979+
Semantics:
982980

983-
Values: the contents of a ppx annotation without the enclosing
984-
``[@@`` and ``]``
981+
- On a **type definition** (left-hand side just before ``=``), specifies
982+
custom ppx attributes for the type definition. Overrides any default
983+
attributes set globally via the command line option ``-type-attr``.
984+
Multiple ``attr`` annotations are allowed and combined.
985+
- On a **record field** (after the field type), appends a ``[@attr]``
986+
attribute to that field.
987+
- On a **variant constructor** (after the constructor name), appends a
988+
``[@attr]`` attribute to that constructor.
989+
- On a **variant payload type** (after the ``of <type>``), wraps the
990+
payload in ``(<type> [@attr])``.
985991

986-
Example:
992+
Values: the contents of a ppx annotation without the enclosing brackets
993+
and ``@`` sigils
994+
995+
Examples:
987996

988997
.. code:: ocaml
989998
990999
type foo <ocaml attr="deriving show,eq"> = int list
9911000
1001+
type point = {
1002+
x: float;
1003+
y: float <ocaml attr="compare.ignore">;
1004+
}
1005+
1006+
type status = [
1007+
| Active <ocaml attr="deriving.ord.ignore">
1008+
| Pending of int <ocaml attr="deriving.ord.ignore">
1009+
| Inactive
1010+
]
1011+
9921012
translates to
9931013

9941014
.. code:: ocaml
9951015
9961016
type foo = int list [@@deriving show,eq]
9971017
1018+
type point = {
1019+
x: float;
1020+
y: float [@compare.ignore];
1021+
}
1022+
1023+
type status =
1024+
| Active [@deriving.ord.ignore]
1025+
| Pending of (int [@deriving.ord.ignore])
1026+
| Inactive
1027+
9981028
9991029
Field ``predef``
10001030
""""""""""""""""

0 commit comments

Comments
 (0)