Skip to content

[new release] atd (11 packages) (4.0.0)#29552

Merged
mseri merged 5 commits intoocaml:masterfrom
mjambon:release-atd-4.0.0
Mar 18, 2026
Merged

[new release] atd (11 packages) (4.0.0)#29552
mseri merged 5 commits intoocaml:masterfrom
mjambon:release-atd-4.0.0

Conversation

@mjambon
Copy link
Contributor

@mjambon mjambon commented Mar 17, 2026

Project page: https://github.com/ahrefs/atd

CHANGES:
  • Build: Dune >= 3.18 (April 2025) is now required to build the ATD tools
    from source. (Require dune >= 3.18 (April 2025) ahrefs/atd#445)

  • atdcat: read from stdin when no input file is given, like cat.

  • ATD: Experimental. Module support via new from ... import ... syntax.
    Types to be used must be listed explicitly; the full syntax is:

    from module.path <annots> [as alias] import type1 <annots>, type2, ...
    

    Key properties:

    • Imported types must be listed explicitly
      (e.g. from mylib import date, status).
    • Supported languages: OCaml (atdml, not atdgen), Python (atdpy),
      TypeScript (atdts)
    • Parameterized types declare their arity: from mylib import 'a list_ne.
    • The arity of each imported type is enforced at all use-sites within the
      importing file.
    • Aliases are plain names without annotations: from foo as f import t.
    • Language-specific name annotations go on the module path:
      <python name="..."> for atdpy, <ts name="..."> for atdts,
      <ocaml name="..."> for atdml.
    • atdpy: alias names that are Python keywords are automatically suffixed
      with _ (e.g. alias classclass_ in generated code).
    • atdts: Dotted module paths in import declarations are now mapped to
      path-separator form in the generated TypeScript import path.
      For example, import long.module.path generates
      import * as path from "./long/module/path" rather than "./path".
    • A warning is printed to stderr when an imported type name is never
      referenced in any type expression.

    Examples:

    (* Import specific types from a module *)
    from mylib.common import date, status
    
    (* Import with alias *)
    from long.module.path as ext import tag
    
    (* Override OCaml module name *)
    from mylib.common <ocaml name="Mylib_common"> import date
    
  • atdml: Experimental. New tool. Generates a single self-contained OCaml module (.ml +
    .mli) from a single .atd file, with JSON support via Yojson.Safe.t.
    No separate runtime library is required; the runtime helpers are inlined
    into each generated .ml. Atdml is the recommended successor to atdgen
    for OCaml JSON support.
    Supported features:

    • All primitive ATD types: unit, bool, int, float, string
    • abstract type, represented as Yojson.Safe.t
    • 'a list, 'a option, 'a nullable
    • Tuples: ('a * 'b * ...)
    • Records, including required fields, optional fields (?foo),
      and with-default fields (~foo) with implicit or explicit defaults
      (<ocaml default="...">)
    • <ocaml field_prefix="pre_"> on record types: prepends the given prefix
      to all OCaml record field names while keeping the labeled arguments of
      create_ unprefixed (e.g. create_point ~x ~y () : point). JSON field
      names are unaffected.
    • Classic sum types (regular OCaml variants) and polymorphic variants
      (<ocaml repr="poly">)
    • Parametric types
    • Mutually recursive types (detected automatically via topological sort;
      and is used only when strictly necessary)
    • wrap construct with <ocaml module="M"> or explicit
      <ocaml t="..." wrap="..." unwrap="...">
    • JSON adapters via <json adapter.ocaml="M"> (module providing normalize
      and restore) or <json adapter.to_ocaml="..." adapter.from_ocaml="...">
      (inline expressions); supported on sum types and records
    • <json name="..."> to override JSON field or constructor names
    • <ocaml name="..."> to rename variant constructors in OCaml
    • <ocaml attr="..."> to attach ppx attributes (e.g. [@@deriving show])
      to generated type definitions
    • <ocaml private> on any type definition forces private in the generated
      .mli; <ocaml public> on a primitive alias suppresses the default
      private, making the alias transparent to callers
    • Unparameterized aliases of primitive types (unit, bool, int,
      float, string) are generated as private in the .mli, preventing
      direct construction outside the module. A constructor function
      create_email : string -> email is generated (also exposed as
      val create in submodule Email). Coerce back with :>
      (e.g. (x :> string)).
    • <doc text="..."> documentation annotations, translated into ocamldoc
      (** ... *) comments in the generated code; supported on type definitions,
      record fields, variant constructors, and module-level head annotations
    • Automatic renaming of ATD type names that conflict with OCaml keywords
      or with the foo_of_yojson/yojson_of_foo naming scheme
    • Automatic renaming of record fields and variant constructors that conflict
      with OCaml keywords (e.g. endend_)
    • (string * 'a) list <json repr="object">: encode association lists as
      JSON objects {"key": value, ...} rather than arrays
    • Graceful handling of atdgen annotations not supported by atdml:
      <ocaml module="..."> on type definitions and <ocaml name="..."> on
      record fields emit a warning and are otherwise ignored
    • ~field: user_type with no OCaml default: warns and treats the field as
      required in JSON (no create_ function generated for that type)
    • Record creation functions are named create_foo (not make_foo),
      matching atdgen's convention
    • Stdin mode: reads ATD from stdin, writes a copy-pasteable
      module type Types = sig ... end / module Types : Types = struct ... end
      snippet to stdout
    • Each generated type gets a submodule (module Foo) bundling the type
      and its conversion functions

CHANGES:

* Build: Dune >= 3.18 (April 2025) is now required to build the ATD tools
  from source. (ahrefs/atd#445)

* atdcat: read from stdin when no input file is given, like `cat`.

* ATD: **Experimental.** Module support via new `from ... import ...` syntax.
  Types to be used must be listed explicitly; the full syntax is:

      from module.path <annots> [as alias] import type1 <annots>, type2, ...

  Key properties:
  - Imported types must be listed explicitly
    (e.g. `from mylib import date, status`).
  - Supported languages: OCaml (atdml, not atdgen), Python (atdpy),
    TypeScript (atdts)
  - Parameterized types declare their arity: `from mylib import 'a list_ne`.
  - The arity of each imported type is enforced at all use-sites within the
    importing file.
  - Aliases are plain names without annotations: `from foo as f import t`.
  - Language-specific name annotations go on the module path:
    `<python name="...">` for atdpy, `<ts name="...">` for atdts,
    `<ocaml name="...">` for atdml.
  - atdpy: alias names that are Python keywords are automatically suffixed
    with `_` (e.g. alias `class` → `class_` in generated code).
  - atdts: Dotted module paths in `import` declarations are now mapped to
    path-separator form in the generated TypeScript import path.
    For example, `import long.module.path` generates
    `import * as path from "./long/module/path"` rather than `"./path"`.
  - A warning is printed to stderr when an imported type name is never
    referenced in any type expression.

  Examples:

      (* Import specific types from a module *)
      from mylib.common import date, status

      (* Import with alias *)
      from long.module.path as ext import tag

      (* Override OCaml module name *)
      from mylib.common <ocaml name="Mylib_common"> import date

* atdml: **Experimental.** New tool. Generates a single self-contained OCaml module (`.ml` +
  `.mli`) from a single `.atd` file, with JSON support via `Yojson.Safe.t`.
  No separate runtime library is required; the runtime helpers are inlined
  into each generated `.ml`. Atdml is the recommended successor to atdgen
  for OCaml JSON support.
  Supported features:
  - All primitive ATD types: `unit`, `bool`, `int`, `float`, `string`
  - `abstract` type, represented as `Yojson.Safe.t`
  - `'a list`, `'a option`, `'a nullable`
  - Tuples: `('a * 'b * ...)`
  - Records, including required fields, optional fields (`?foo`),
    and with-default fields (`~foo`) with implicit or explicit defaults
    (`<ocaml default="...">`)
  - `<ocaml field_prefix="pre_">` on record types: prepends the given prefix
    to all OCaml record field names while keeping the labeled arguments of
    `create_` unprefixed (e.g. `create_point ~x ~y () : point`). JSON field
    names are unaffected.
  - Classic sum types (regular OCaml variants) and polymorphic variants
    (`<ocaml repr="poly">`)
  - Parametric types
  - Mutually recursive types (detected automatically via topological sort;
    `and` is used only when strictly necessary)
  - `wrap` construct with `<ocaml module="M">` or explicit
    `<ocaml t="..." wrap="..." unwrap="...">`
  - JSON adapters via `<json adapter.ocaml="M">` (module providing `normalize`
    and `restore`) or `<json adapter.to_ocaml="..." adapter.from_ocaml="...">`
    (inline expressions); supported on sum types and records
  - `<json name="...">` to override JSON field or constructor names
  - `<ocaml name="...">` to rename variant constructors in OCaml
  - `<ocaml attr="...">` to attach ppx attributes (e.g. `[@@deriving show]`)
    to generated type definitions
  - `<ocaml private>` on any type definition forces `private` in the generated
    `.mli`; `<ocaml public>` on a primitive alias suppresses the default
    `private`, making the alias transparent to callers
  - Unparameterized aliases of primitive types (`unit`, `bool`, `int`,
    `float`, `string`) are generated as `private` in the `.mli`, preventing
    direct construction outside the module. A constructor function
    `create_email : string -> email` is generated (also exposed as
    `val create` in submodule `Email`). Coerce back with `:>`
    (e.g. `(x :> string)`).
  - `<doc text="...">` documentation annotations, translated into ocamldoc
    `(** ... *)` comments in the generated code; supported on type definitions,
    record fields, variant constructors, and module-level head annotations
  - Automatic renaming of ATD type names that conflict with OCaml keywords
    or with the `foo_of_yojson`/`yojson_of_foo` naming scheme
  - Automatic renaming of record fields and variant constructors that conflict
    with OCaml keywords (e.g. `end` → `end_`)
  - `(string * 'a) list <json repr="object">`: encode association lists as
    JSON objects `{"key": value, ...}` rather than arrays
  - Graceful handling of atdgen annotations not supported by atdml:
    `<ocaml module="...">` on type definitions and `<ocaml name="...">` on
    record fields emit a warning and are otherwise ignored
  - `~field: user_type` with no OCaml default: warns and treats the field as
    required in JSON (no `create_` function generated for that type)
  - Record creation functions are named `create_foo` (not `make_foo`),
    matching atdgen's convention
  - Stdin mode: reads ATD from stdin, writes a copy-pasteable
    `module type Types = sig ... end / module Types : Types = struct ... end`
    snippet to stdout
  - Each generated type gets a submodule (`module Foo`) bundling the type
    and its conversion functions
@mjambon
Copy link
Contributor Author

mjambon commented Mar 17, 2026

I'm trying to figure out why the new generated file atdml.opam has a build command of the form dune -p atdml ... @runtest causing an attempt to run all the project's tests (failing due missing dependencies needed by the irrelevant tests).

Solution: added atdml.opam.template with the desired build command.

@raphael-proust
Copy link
Contributor

raphael-proust commented Mar 17, 2026

esgg.20190322

# File "derive.mli", line 7, characters 18-37:
# 7 | val output : init:Atd.Ast.full_module -> mapping -> Tjson.t -> Atd.Ast.full_module
#                       ^^^^^^^^^^^^^^^^^^^
# Error: Unbound type constructor Atd.Ast.full_module

with `atd.4.0.0

@mjambon
Copy link
Contributor Author

mjambon commented Mar 17, 2026

I fixed the version constraint for esgg. The errors that remain seem independent of this release.

@mseri
Copy link
Member

mseri commented Mar 18, 2026

Some CI machines are stuck, but it is independent of the PR. The rest looks good, thanks

@mseri mseri merged commit dfabc62 into ocaml:master Mar 18, 2026
4 of 5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants