Skip to main content

canton_codegen/
ir.rs

1//! A decoder-agnostic intermediate representation (IR) of Daml types.
2//!
3//! The IR is the seam between "decode Daml-LF" (`canton-lf`) and "emit Rust"
4//! (this crate's generator). Neither side knows about the other: a decoder
5//! produces this IR, the generator consumes it — so the LF-decoder choice stays
6//! isolated to lowering, the one step that reads the LF AST
7//! ([`lower_dar`](crate::lower_dar)).
8
9/// A Daml type — a primitive, a container, or a reference to a named data type.
10///
11/// This is the type a record field, choice argument, or contract key can take.
12#[derive(Clone, Debug, PartialEq, Eq)]
13#[non_exhaustive]
14pub enum DamlType {
15    /// The `Unit` type `()`.
16    Unit,
17    /// `Bool`.
18    Bool,
19    /// `Int64`.
20    Int64,
21    /// `Numeric n` — a fixed-scale decimal; the value is the scale (decimals).
22    Numeric(u8),
23    /// `Text`.
24    Text,
25    /// `Timestamp` (microseconds since the Unix epoch, UTC).
26    Timestamp,
27    /// `Date` (days since the Unix epoch).
28    Date,
29    /// `Party`.
30    Party,
31    /// `ContractId t` — a handle to a contract of the referenced payload type.
32    ContractId(Box<DamlType>),
33    /// `List t` / `[t]`.
34    List(Box<DamlType>),
35    /// `Optional t`.
36    Optional(Box<DamlType>),
37    /// `TextMap t` — a map keyed by `Text`.
38    TextMap(Box<DamlType>),
39    /// `GenMap k v` — a map with arbitrary key type.
40    GenMap(Box<DamlType>, Box<DamlType>),
41    /// A reference to a named data type (record / variant / enum).
42    Ref(TypeRef),
43    /// A type parameter (`a`, `b`, …) inside a generic data type.
44    Var(String),
45    /// A type behind a `Box`, used to give recursive types the indirection Rust
46    /// requires (Daml allows a type to contain itself directly; Rust does not).
47    Boxed(Box<DamlType>),
48}
49
50/// A reference to a named Daml data type, with any applied type arguments.
51#[derive(Clone, Debug, PartialEq, Eq)]
52#[non_exhaustive]
53pub struct TypeRef {
54    /// The Rust path to the referenced type, as segments. A local reference is a
55    /// single segment (`["Foo"]`); a qualified one carries its full path
56    /// (`["crate", "splice_amulet", "Amulet"]`), which is how cross-module and
57    /// cross-package references are disambiguated.
58    pub path: Vec<String>,
59    /// Applied type arguments, if the referenced type is generic.
60    pub args: Vec<DamlType>,
61}
62
63impl TypeRef {
64    /// A local (single-segment) reference to `name`, no path qualification.
65    #[must_use]
66    pub fn local(name: impl Into<String>, args: Vec<DamlType>) -> Self {
67        Self {
68            path: vec![name.into()],
69            args,
70        }
71    }
72}
73
74/// One field of a record.
75#[derive(Clone, Debug, PartialEq, Eq)]
76#[non_exhaustive]
77pub struct Field {
78    /// The Daml field label, in its source casing (usually camelCase).
79    pub label: String,
80    /// The field's type.
81    pub ty: DamlType,
82}
83
84/// A record data type. Template payloads are records too, so this is reused for
85/// both a plain `data … = … with` record and a `template … with` payload.
86#[derive(Clone, Debug, PartialEq, Eq)]
87#[non_exhaustive]
88pub struct Record {
89    /// The type name (PascalCase, as in Daml).
90    pub name: String,
91    /// Type parameters, in order, if the record is generic.
92    pub type_params: Vec<String>,
93    /// The fields, in declaration order.
94    pub fields: Vec<Field>,
95}
96
97/// A named data type declared in a module: a record, a variant, or an enum.
98#[derive(Clone, Debug, PartialEq, Eq)]
99#[non_exhaustive]
100pub enum DataType {
101    /// A record (product) type.
102    Record(Record),
103    /// A variant (sum) type.
104    Variant(Variant),
105    /// An enumeration (constructors carrying no payload).
106    Enum(Enum),
107    /// An interface **marker**: a phantom tag emitted so references to the
108    /// interface (always `ContractId<I>`) resolve. The interface itself is not
109    /// serializable and carries no data of its own — its view and choices are
110    /// emitted separately, from [`Interface`]. The `String` is the name.
111    InterfaceMarker(String),
112}
113
114/// A variant (sum) type: named constructors, each optionally carrying a payload.
115#[derive(Clone, Debug, PartialEq, Eq)]
116#[non_exhaustive]
117pub struct Variant {
118    /// The type name (PascalCase).
119    pub name: String,
120    /// Type parameters, in order, if generic.
121    pub type_params: Vec<String>,
122    /// The constructors, in declaration order.
123    pub constructors: Vec<VariantConstructor>,
124}
125
126/// One constructor of a [`Variant`].
127#[derive(Clone, Debug, PartialEq, Eq)]
128#[non_exhaustive]
129pub struct VariantConstructor {
130    /// The constructor name (PascalCase).
131    pub name: String,
132    /// The payload type, or `None` for a constructor that carries no data.
133    pub payload: Option<DamlType>,
134}
135
136/// An enumeration: named constructors that carry no payload.
137#[derive(Clone, Debug, PartialEq, Eq)]
138#[non_exhaustive]
139pub struct Enum {
140    /// The type name (PascalCase).
141    pub name: String,
142    /// The constructor names, in declaration order.
143    pub constructors: Vec<String>,
144}
145
146/// A template: its payload fields, its choices, and an optional contract key.
147#[derive(Clone, Debug, PartialEq, Eq)]
148#[non_exhaustive]
149pub struct Template {
150    /// The template name (PascalCase) — also the Rust payload struct name.
151    pub name: String,
152    /// The Daml module the template is defined in, dotted (e.g. `Splice.Amulet`).
153    /// Part of the on-ledger template id.
154    pub module_name: String,
155    /// The id (hash) of the package the template is defined in. Pins the exact
156    /// template version in an on-ledger template id.
157    pub package_id: String,
158    /// The Daml package **name** (e.g. `splice-amulet`), used for the
159    /// upgrade-friendly `#<package-name>` template-id form so the participant
160    /// resolves the vetted version under Smart Contract Upgrade.
161    pub package_name: String,
162    /// The payload fields, in declaration order.
163    pub fields: Vec<Field>,
164    /// The choices exercisable on a contract of this template.
165    pub choices: Vec<Choice>,
166    /// The contract key type, if the template declares a key.
167    pub key: Option<DamlType>,
168}
169
170/// A choice on a [`Template`].
171#[derive(Clone, Debug, PartialEq, Eq)]
172#[non_exhaustive]
173pub struct Choice {
174    /// The choice name (PascalCase).
175    pub name: String,
176    /// Whether exercising the choice archives the contract.
177    pub consuming: bool,
178    /// The choice argument type (usually a reference to a record).
179    pub argument: DamlType,
180    /// The type the choice returns.
181    pub returns: DamlType,
182}
183
184/// A module's worth of generated declarations: its data types, templates, and
185/// interfaces.
186#[derive(Clone, Debug, PartialEq, Eq, Default)]
187#[non_exhaustive]
188pub struct Module {
189    /// The named data types (records, variants, enums).
190    pub data_types: Vec<DataType>,
191    /// The templates.
192    pub templates: Vec<Template>,
193    /// The interfaces: their view type and choices, emitted as impls on the
194    /// marker struct that the corresponding [`DataType::InterfaceMarker`]
195    /// declares.
196    pub interfaces: Vec<Interface>,
197}
198
199/// A Daml interface: its view type and choices, plus the on-ledger identity a
200/// choice exercised through the interface needs. The marker struct itself is
201/// emitted from the interface's `DataCons::Interface` data type; this adds the
202/// `Interface`/`Choice` impls on that marker.
203#[derive(Clone, Debug, PartialEq, Eq)]
204#[non_exhaustive]
205pub struct Interface {
206    /// The interface name (PascalCase) — also the marker struct's name.
207    pub name: String,
208    /// The Daml module the interface is defined in, dotted (e.g. `Splice.Api…`).
209    pub module_name: String,
210    /// The id (hash) of the package the interface is defined in.
211    pub package_id: String,
212    /// The Daml package **name**, for the `#<package-name>` id form.
213    pub package_name: String,
214    /// The interface's view type (its `viewtype`), if known.
215    pub view: Option<DamlType>,
216    /// The choices the interface declares.
217    pub choices: Vec<Choice>,
218}
219
220/// A whole generated crate: every package in a DAR, each as its own Rust module
221/// (so cross-module and cross-package references resolve, and names from
222/// different modules cannot collide).
223#[derive(Clone, Debug, PartialEq, Eq, Default)]
224#[non_exhaustive]
225pub struct Crate {
226    /// The packages, each a top-level module in the generated crate.
227    pub packages: Vec<PackageModule>,
228}
229
230/// One package rendered as a Rust module (`pub mod <name> { … }`), containing a
231/// submodule per Daml module.
232#[derive(Clone, Debug, PartialEq, Eq)]
233#[non_exhaustive]
234pub struct PackageModule {
235    /// The Rust module name for this package (derived from its name + version).
236    pub name: String,
237    /// The Daml modules of this package.
238    pub modules: Vec<NamedModule>,
239}
240
241/// A Daml module rendered as a Rust submodule.
242#[derive(Clone, Debug, PartialEq, Eq)]
243#[non_exhaustive]
244pub struct NamedModule {
245    /// The Rust module name for this Daml module (dotted name, `.` → `_`).
246    pub name: String,
247    /// The module's declarations.
248    pub module: Module,
249}
250
251// ---- constructors ------------------------------------------------------------
252//
253// The types above are `#[non_exhaustive]`, so a struct literal only works inside
254// this crate. That is deliberate: this IR gains fields as Daml-LF does — it took
255// forty of them during Milestone 2 alone — and once the crate is published, one
256// more would be a breaking change for everyone who wrote a literal.
257//
258// Fields stay public, so the documented use (lower a DAR, then post-process the
259// IR) still reads and mutates them freely. Only construction goes through these,
260// which take what a value cannot exist without and leave the rest empty.
261
262impl TypeRef {
263    /// A reference to the type at `path`, with no type arguments.
264    #[must_use]
265    pub fn new(path: Vec<String>) -> Self {
266        Self {
267            path,
268            args: Vec::new(),
269        }
270    }
271}
272
273impl Field {
274    /// A field named `label` of type `ty`.
275    #[must_use]
276    pub fn new(label: impl Into<String>, ty: DamlType) -> Self {
277        Self {
278            label: label.into(),
279            ty,
280        }
281    }
282}
283
284impl Record {
285    /// An empty, non-generic record named `name`.
286    #[must_use]
287    pub fn new(name: impl Into<String>) -> Self {
288        Self {
289            name: name.into(),
290            type_params: Vec::new(),
291            fields: Vec::new(),
292        }
293    }
294}
295
296impl Variant {
297    /// A variant named `name` with no constructors yet.
298    #[must_use]
299    pub fn new(name: impl Into<String>) -> Self {
300        Self {
301            name: name.into(),
302            type_params: Vec::new(),
303            constructors: Vec::new(),
304        }
305    }
306}
307
308impl VariantConstructor {
309    /// A constructor carrying no payload.
310    #[must_use]
311    pub fn new(name: impl Into<String>) -> Self {
312        Self {
313            name: name.into(),
314            payload: None,
315        }
316    }
317
318    /// A constructor carrying `payload`.
319    #[must_use]
320    pub fn with_payload(name: impl Into<String>, payload: DamlType) -> Self {
321        Self {
322            name: name.into(),
323            payload: Some(payload),
324        }
325    }
326}
327
328impl Enum {
329    /// An enumeration of `constructors`.
330    #[must_use]
331    pub fn new(name: impl Into<String>, constructors: Vec<String>) -> Self {
332        Self {
333            name: name.into(),
334            constructors,
335        }
336    }
337}
338
339impl Template {
340    /// A template with its on-ledger identity, and no fields, choices or key
341    /// yet.
342    #[must_use]
343    pub fn new(
344        name: impl Into<String>,
345        module_name: impl Into<String>,
346        package_id: impl Into<String>,
347        package_name: impl Into<String>,
348    ) -> Self {
349        Self {
350            name: name.into(),
351            module_name: module_name.into(),
352            package_id: package_id.into(),
353            package_name: package_name.into(),
354            fields: Vec::new(),
355            choices: Vec::new(),
356            key: None,
357        }
358    }
359}
360
361impl Choice {
362    /// A non-consuming choice. Set `consuming` on the value to change that —
363    /// it is a plain field, and a bool argument here would read as
364    /// `Choice::new(name, arg, ret, true)` at every call site.
365    #[must_use]
366    pub fn new(name: impl Into<String>, argument: DamlType, returns: DamlType) -> Self {
367        Self {
368            name: name.into(),
369            consuming: false,
370            argument,
371            returns,
372        }
373    }
374}
375
376impl Interface {
377    /// An interface with its on-ledger identity, no view and no choices yet.
378    #[must_use]
379    pub fn new(
380        name: impl Into<String>,
381        module_name: impl Into<String>,
382        package_id: impl Into<String>,
383        package_name: impl Into<String>,
384    ) -> Self {
385        Self {
386            name: name.into(),
387            module_name: module_name.into(),
388            package_id: package_id.into(),
389            package_name: package_name.into(),
390            view: None,
391            choices: Vec::new(),
392        }
393    }
394}
395
396impl PackageModule {
397    /// A package with no modules yet.
398    #[must_use]
399    pub fn new(name: impl Into<String>) -> Self {
400        Self {
401            name: name.into(),
402            modules: Vec::new(),
403        }
404    }
405}
406
407impl NamedModule {
408    /// `module`, to be emitted as the Rust module `name`.
409    #[must_use]
410    pub fn new(name: impl Into<String>, module: Module) -> Self {
411        Self {
412            name: name.into(),
413            module,
414        }
415    }
416}