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}