RFC: multifile ATD definition support via import statements

Hello,

We’re adding support for splitting ATD type definitions into smaller files. It wasn’t possible to conveniently reference types defined from other ATD files until now, resulting in large interface files (example).

I would like a pair of eyes to help me check the sanity of the spec which is copy-pasted below from the pull request before this goes live in atd 4.0.0.


Import declarations

An ATD file may import other ATD modules using import declarations.
Import declarations must appear after any top-level annotations and before
any type definitions.

Syntax:

import module.path as alias

The as clause is optional. Without it, the local name of the imported
module is the last component of the dotted path (e.g. import foo.bar
binds the local name bar).

Type names from an imported module are referenced using dot notation:
alias.typename (or lastcomponent.typename when no alias is given).
For example, if a module types is imported, the type date from
that module is written types.date in type expressions.

Annotations on the path or alias allow language-specific backends to
override the module name used in generated code. The annotation
<ocaml name="..."> (or the equivalent for another target language)
on the path controls how the module is referenced in generated output,
while the same annotation on the as clause controls the local alias
name used in the generated code.

Examples:

(* Simple import: local name is "common" *)
import mylib.common

(* Import with an alias *)
import mylib.common as c

(* Using an imported type in a definition *)
type event = {
  id : string;
  timestamp : common.date;
}
(* Language-specific name annotation on the path *)
import mylib.common <ocaml name="Mylib_common">

(* Language-specific name annotation on the alias *)
import mylib.common as c <ocaml name="Common">

Warning:

Dotted module paths (e.g. import foo.bar.baz) are an experimental
feature. Each code generator maps them to file paths in its own way and
there is currently no guarantee of consistent behavior across backends.
When possible, prefer single-component module names (e.g. import baz
or import foo as bar). Support for dotted module paths may be removed
in a future release.

1 Like

Nice. Is it intended to replace things like this:

type t_error <ocaml from="Utils"> = abstract

Yes. Now, you’d have a Utils.atd file that you’d use as:

import utils
type errors = utils.t_error list

And if Utils is an OCaml module but there’s no ATD file Utils.atd or utils.atd, that’s fine too as long as it exposes a compatible interface.

The OCaml translation (mli) is

type errors = Utils.t_error list

val errors_of_yojson : Yojson.Safe.t -> errors
val yojson_of_errors : errors -> Yojson.Safe.t
val errors_of_json : string -> errors
val json_of_errors : errors -> string

module Errors : sig
  type nonrec t = errors
  val of_yojson : Yojson.Safe.t -> t
  val to_yojson : t -> Yojson.Safe.t
  val of_json : string -> t
  val to_json : t -> string
end