Skip to main content

canton_codegen/
lower.rs

1//! The AST→IR bridge: lower decoded Daml-LF [`Package`](lf::Package)s into the
2//! codegen [`ir`](crate::ir).
3//!
4//! This is the one module that touches the LF AST; everything else stays
5//! decoder-agnostic. Serializable records, variants, and enums lower fully, as
6//! do **templates** (payload record, choices, contract key, on-ledger id) and
7//! **interfaces** (view type, choices, and the identity choices are exercised
8//! through — on the marker struct emitted from the interface's data type). Field
9//! types cover the LF builtins, references to named types, and type variables;
10//! unsupported LF type shapes yield a [`SkippedType`] rather than silently-wrong
11//! output.
12//!
13//! # Qualified references (PackageMap)
14//!
15//! A DAR bundles a package and its whole dependency closure. A type reference
16//! (`Con`) carries the target module *and* package (self, or an imported
17//! package identified by its id-hash). Lowering a **whole DAR**
18//! resolves every reference to a fully-qualified Rust path —
19//! `crate::<package>::<module>::<Type>` — so cross-module and cross-package
20//! references resolve and names from different modules can never collide.
21
22use std::collections::{HashMap, HashSet};
23
24use heck::ToUpperCamelCase;
25
26use canton_lf::pb::daml_lf_2 as lf;
27use canton_lf::{
28    Dar, DecodeError, decode_all, imported_package_id, interned_dotted_name, interned_str,
29    interned_type, package_name, package_version,
30};
31
32use crate::ir::{
33    Choice, Crate, DamlType, DataType, Enum, Field, Interface, Module, NamedModule, PackageModule,
34    Record, Template, TypeRef, Variant, VariantConstructor,
35};
36
37/// A declaration that could not be lowered into the IR, and why.
38///
39/// Lowering is best-effort: an LF shape the codegen cannot represent (a type
40/// name that is not a Rust identifier, a non-serializable builtin, two fields
41/// that would collide after snake-casing) is skipped and reported here rather
42/// than failing the whole DAR or emitting something wrong. Callers surface
43/// these as warnings — see [`crate::lower_dar`].
44#[derive(Debug, Clone, PartialEq, Eq)]
45pub struct SkippedType {
46    module: String,
47    reason: String,
48}
49
50impl SkippedType {
51    fn new(reason: impl Into<String>) -> Self {
52        Self {
53            module: String::new(),
54            reason: reason.into(),
55        }
56    }
57
58    /// Attribute the skip to the Daml module it arose in, so the warning tells
59    /// the user *where* to look.
60    fn in_module(mut self, module: &str) -> Self {
61        self.module = module.to_string();
62        self
63    }
64
65    /// The dotted Daml module the skipped declaration lives in, or `""` when
66    /// the skip could not be attributed to one.
67    #[must_use]
68    pub fn module(&self) -> &str {
69        &self.module
70    }
71
72    /// Why the declaration was skipped.
73    #[must_use]
74    pub fn reason(&self) -> &str {
75        &self.reason
76    }
77}
78
79impl std::fmt::Display for SkippedType {
80    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
81        if self.module.is_empty() {
82            f.write_str(&self.reason)
83        } else {
84            write!(f, "{}: {}", self.module, self.reason)
85        }
86    }
87}
88
89impl std::error::Error for SkippedType {}
90
91/// Lower a whole DAR (its main package and its full dependency closure) into a
92/// [`Crate`] of qualified modules, plus the list of types that could not be
93/// lowered (best-effort: see [`SkippedType`]).
94///
95/// # Errors
96/// Returns [`DecodeError`] if any package's bytes are malformed or not LF 2.x.
97pub fn lower_dar(dar: &Dar) -> Result<(Crate, Vec<SkippedType>), DecodeError> {
98    lower_dar_with(dar, &ExternalPackages::default())
99}
100
101/// The same, with packages that are **already published as their own crates**
102/// referenced instead of re-emitted.
103///
104/// A DAR's dependency closure is shared: `splice-api-token-holding-v1` sits
105/// under amulet, wallet and wallet-payments alike. Emitting it into each crate
106/// gives every crate its own `Holding`, and Rust treats those as unrelated
107/// types — a `ContractId<Holding>` obtained from one crate does not typecheck
108/// against the other, though both name the same interface in the same package.
109///
110/// Naming such a package here makes references to it point at the published
111/// crate, and leaves its modules out of the output entirely.
112///
113/// # Errors
114/// As [`lower_dar`].
115pub fn lower_dar_with(
116    dar: &Dar,
117    external: &ExternalPackages,
118) -> Result<(Crate, Vec<SkippedType>), DecodeError> {
119    lower_dar_selecting(dar, &Selection::everything(), external)
120}
121
122/// The same, emitting only the packages a [`Selection`] names.
123///
124/// A DAR is generated whole because that is what a DAR describes. Some packages
125/// have no DAR of their own, though — `daml-stdlib` and `daml-prim` arrive only
126/// as somebody else's dependency — and publishing *those* as a crate means
127/// choosing a subset of a DAR that happens to contain them.
128///
129/// A reference out of the selection that is not covered by `external` is
130/// reported as a skipped type rather than emitted, because the path would name
131/// a module this crate does not have.
132///
133/// # Errors
134/// As [`lower_dar`].
135pub fn lower_dar_selecting(
136    dar: &Dar,
137    selection: &Selection,
138    external: &ExternalPackages,
139) -> Result<(Crate, Vec<SkippedType>), DecodeError> {
140    Ok(lower_crate_selecting(
141        &decode_all(dar)?,
142        selection,
143        external,
144    ))
145}
146
147/// The same, from packages already decoded rather than from a DAR.
148///
149/// A DAR is one way to get packages; a participant is another, and often the
150/// better one — `AdminClient::get_package` returns exactly what a participant
151/// has vetted, whereas a DAR file has to be found somewhere and may not exist
152/// for a package that is nonetheless live on the network. A package id is the
153/// hash of its own bytes, so a set assembled this way is pinned by its ids.
154///
155/// # Errors
156/// This cannot fail: the packages are already decoded. It returns the same
157/// pair as [`lower_dar_selecting`] so the two read alike at a call site.
158#[must_use]
159pub fn lower_packages_selecting(
160    packages: &[(String, lf::Package)],
161    selection: &Selection,
162    external: &ExternalPackages,
163) -> (Crate, Vec<SkippedType>) {
164    lower_crate_selecting(packages, selection, external)
165}
166
167/// Which of a DAR's packages a crate emits.
168///
169/// Keys are package **names** or package **ids**, as for [`ExternalPackages`],
170/// and the name form is the one to prefer for the same reason.
171#[derive(Clone, Debug, Default)]
172pub struct Selection {
173    /// `None` — every package in the DAR, which is the ordinary case.
174    keys: Option<Vec<String>>,
175    /// Name prefixes, for a family of packages nobody wants to enumerate.
176    prefixes: Vec<String>,
177}
178
179impl Selection {
180    /// Emit every package in the DAR.
181    #[must_use]
182    pub fn everything() -> Self {
183        Self {
184            keys: None,
185            prefixes: Vec::new(),
186        }
187    }
188
189    /// Emit only the packages named here.
190    #[must_use]
191    pub fn only<K: Into<String>>(keys: impl IntoIterator<Item = K>) -> Self {
192        Self {
193            keys: Some(keys.into_iter().map(Into::into).collect()),
194            prefixes: Vec::new(),
195        }
196    }
197
198    /// Also emit every package whose name is `prefix`, or begins with
199    /// `prefix-`.
200    ///
201    /// The standard library arrives as some thirty packages — `daml-stdlib`,
202    /// `daml-stdlib-DA-Time-Types`, `daml-prim-GHC-Types`, … — and which ones a
203    /// DAR carries depends on what its Daml source happened to touch. Listing
204    /// them by hand produces a list that is wrong for the next DAR; the family
205    /// is what is meant, so the family is what is written.
206    ///
207    /// The `-` is required so that `daml-prim` does not also select a package
208    /// called `daml-primary`.
209    ///
210    /// This *narrows*: applied to [`Selection::everything`] it stops meaning
211    /// "the whole DAR" and starts meaning "these families".
212    #[must_use]
213    pub fn and_prefixed<K: Into<String>>(mut self, prefixes: impl IntoIterator<Item = K>) -> Self {
214        self.keys.get_or_insert_with(Vec::new);
215        self.prefixes.extend(prefixes.into_iter().map(Into::into));
216        self
217    }
218
219    /// Resolve to the ids actually present, or `None` for "everything".
220    fn resolve(&self, packages: &[(String, lf::Package)]) -> Option<HashSet<String>> {
221        let keys = self.keys.as_ref()?;
222        let mut ids = HashSet::new();
223        for (id, package) in packages {
224            let name = canton_lf::package_name(package);
225            let named = keys
226                .iter()
227                .any(|k| k == id || name.is_some_and(|n| n == k.as_str()))
228                || self.prefixes.iter().any(|p| {
229                    name.is_some_and(|n| n == p.as_str() || n.starts_with(&format!("{p}-")))
230                });
231            if named {
232                ids.insert(id.clone());
233            }
234        }
235        Some(ids)
236    }
237}
238
239/// Packages to reference rather than generate, keyed by package **id** or
240/// package **name**.
241///
242/// The name form is the one to prefer: a package id is the hash of one build,
243/// so pinning by id stops matching the moment the dependency is rebuilt, while
244/// the name survives a version bump — which is the whole point of addressing
245/// packages by name under Smart Contract Upgrade.
246#[derive(Clone, Debug, Default)]
247pub struct ExternalPackages {
248    by_key: HashMap<String, String>,
249    /// `(name prefix, crate)`, for a family of packages nobody wants to list.
250    by_prefix: Vec<(String, String)>,
251}
252
253impl ExternalPackages {
254    /// An empty map — everything in the DAR is generated.
255    #[must_use]
256    pub fn new() -> Self {
257        Self::default()
258    }
259
260    /// Reference the package identified by `key` (its name, or its id hash) at
261    /// the crate `crate_name`, instead of generating it.
262    #[must_use]
263    pub fn with(mut self, key: impl Into<String>, crate_name: impl Into<String>) -> Self {
264        self.by_key.insert(key.into(), crate_name.into());
265        self
266    }
267
268    /// Reference every package whose name is `prefix`, or begins with
269    /// `prefix-`, at `crate_name`.
270    ///
271    /// For a family that arrives as many packages — the standard library is
272    /// some thirty, and which of them a DAR carries depends on what its Daml
273    /// source touched. An exact list is right for one DAR and wrong for the
274    /// next; the family is what is meant.
275    #[must_use]
276    pub fn with_prefixed(
277        mut self,
278        prefix: impl Into<String>,
279        crate_name: impl Into<String>,
280    ) -> Self {
281        self.by_prefix.push((prefix.into(), crate_name.into()));
282        self
283    }
284
285    /// The crate names named here, sorted — what a generated manifest must
286    /// depend on.
287    #[must_use]
288    pub fn crate_names(&self) -> Vec<String> {
289        let mut names: Vec<String> = self
290            .by_key
291            .values()
292            .chain(self.by_prefix.iter().map(|(_, c)| c))
293            .cloned()
294            .collect();
295        names.sort_unstable();
296        names.dedup();
297        names
298    }
299
300    /// Resolve to `package id → crate name` for the packages actually present,
301    /// matching a key against each package's id and its name.
302    fn resolve(&self, packages: &[(String, lf::Package)]) -> HashMap<String, String> {
303        let mut resolved = HashMap::new();
304        for (id, package) in packages {
305            let name = canton_lf::package_name(package);
306            let by_id = self.by_key.get(id.as_str());
307            let by_name = name.and_then(|n| self.by_key.get(n));
308            let by_prefix = self.by_prefix.iter().find_map(|(prefix, krate)| {
309                name.filter(|n| *n == prefix || n.starts_with(&format!("{prefix}-")))
310                    .map(|_| krate)
311            });
312            // An exact key wins over a prefix: naming one package of a family
313            // is how a caller says "that one lives somewhere else".
314            if let Some(crate_name) = by_id.or(by_name).or(by_prefix) {
315                resolved.insert(id.clone(), crate_name.clone());
316            }
317        }
318        resolved
319    }
320}
321
322/// Lower a set of decoded packages (a DAR's closure, each with its package id)
323/// into a [`Crate`]: one Rust module per package, one submodule per Daml module,
324/// with every type reference resolved to a fully-qualified path.
325///
326/// Best-effort: a type that cannot be lowered is skipped and its error recorded,
327/// so a package with a few unsupported types still produces output.
328#[must_use]
329/// The no-externals form, which is what the tests exercise.
330#[cfg(test)]
331pub(crate) fn lower_crate(packages: &[(String, lf::Package)]) -> (Crate, Vec<SkippedType>) {
332    lower_crate_selecting(
333        packages,
334        &Selection::everything(),
335        &ExternalPackages::default(),
336    )
337}
338
339pub(crate) fn lower_crate_selecting(
340    packages: &[(String, lf::Package)],
341    selection: &Selection,
342    external: &ExternalPackages,
343) -> (Crate, Vec<SkippedType>) {
344    let external = external.resolve(packages);
345    let selected = selection.resolve(packages);
346    let module_names = package_module_names(packages);
347
348    let mut krate = Crate::default();
349    let mut errors = Vec::new();
350
351    // `package_module_names` is keyed by package id and so can only hold one
352    // name per id, but the input is a slice: a DAR carrying the same id twice
353    // would get one name emitted twice and a crate that fails with E0428. The
354    // second copy is the same package by definition — an id is a hash of its
355    // content — so dropping it loses nothing.
356    let mut lowered: std::collections::HashSet<&str> = std::collections::HashSet::new();
357
358    for (id, package) in packages {
359        if !lowered.insert(id.as_str()) {
360            continue;
361        }
362        // Published elsewhere: referenced, never emitted. Emitting it too would
363        // put a second copy of every type in this crate, which is the thing
364        // being fixed.
365        if external.contains_key(id.as_str()) {
366            continue;
367        }
368        // Outside this crate's selection: another crate emits it.
369        if selected.as_ref().is_some_and(|ids| !ids.contains(id)) {
370            continue;
371        }
372        let lowering = Lowering {
373            package,
374            qualify: Some(Qualify {
375                module_names: &module_names,
376                current_id: id,
377                external: &external,
378                selected: selected.as_ref(),
379            }),
380            depth: std::cell::Cell::new(0),
381        };
382        let mut package_module = PackageModule {
383            name: module_names[id.as_str()].clone(),
384            modules: Vec::new(),
385        };
386        // The same flattening applies to module names: `A.B` and `A_B` would
387        // both become `pub mod A_B`. Detect it here rather than emitting a
388        // crate that fails to compile.
389        let colliding_modules = colliding_module_names(package);
390        // The same guard the package-id loop above carries, one level down. Two
391        // modules declaring the identical dotted name emit `pub mod M` twice
392        // (E0428) — and the flattening collision check cannot see it, because
393        // it groups *distinct* dotted names that flatten together and a
394        // duplicate is not distinct.
395        let mut seen_modules: std::collections::HashSet<String> = std::collections::HashSet::new();
396
397        for lf_module in &package.modules {
398            let Some(dotted) = interned_dotted_name(package, lf_module.name_interned_dname) else {
399                errors.push(SkippedType::new("unresolved module name"));
400                continue;
401            };
402            if !seen_modules.insert(dotted.clone()) {
403                errors.push(SkippedType::new(format!(
404                    "module `{dotted}` is declared more than once in this package"
405                )));
406                continue;
407            }
408            let module_ident = dotted.replace('.', "_");
409            if !is_rust_ident(&module_ident) {
410                errors.push(SkippedType::new(format!(
411                    "module `{dotted}` is not representable as a Rust module name"
412                )));
413                continue;
414            }
415            if let Some(sources) = colliding_modules.get(&module_ident) {
416                errors.push(SkippedType::new(format!(
417                    "modules `{}` both map to the Rust module `{}`; \
418                     rename one in Daml to generate either",
419                    sources.join("` and `"),
420                    dotted.replace('.', "_"),
421                )));
422                continue;
423            }
424            let ir_module = lowering.module(lf_module, &dotted, &mut errors);
425            if !ir_module.data_types.is_empty()
426                || !ir_module.templates.is_empty()
427                || !ir_module.interfaces.is_empty()
428            {
429                package_module.modules.push(NamedModule {
430                    name: dotted.replace('.', "_"),
431                    module: ir_module,
432                });
433            }
434        }
435        if !package_module.modules.is_empty() {
436            krate.packages.push(package_module);
437        }
438    }
439
440    // An `Optional` handed to a generic that nests it under its own `Optional`
441    // has no correct JSON form here — reject those before they ship.
442    drop_ambiguous_nested_optionals(&mut krate, &mut errors);
443
444    // A skipped declaration leaves its name unresolvable, so anything that
445    // referenced it cannot be emitted either. Drop those too, transitively.
446    drop_dangling_references(&mut krate, &mut errors);
447
448    // With every package lowered, break containment cycles crate-wide (direct,
449    // Optional-wrapped, mutual, cross-module, and generic-instantiation ones).
450    box_recursion(&mut krate);
451
452    (krate, errors)
453}
454
455/// For each generic data type, which of its declared parameters land **directly
456/// under an `Optional`** once instantiated — the question that decides whether
457/// an `Optional` type argument is a nested optional on the wire.
458///
459/// Two ways a parameter gets there, and the analysis needs both:
460///
461/// - **Directly**, as `Optional a` in the type's own fields.
462/// - **Through another generic**, as the k-th argument of a reference to a type
463///   that nests *its* k-th parameter: with `Wrap a = { value : Optional a }`,
464///   the parameter of `Outer a = { inner : Wrap a }` nests too, even though
465///   `Outer` has no `Optional` of its own. Answering that needs a fixpoint —
466///   one pass cannot, since the two may be declared in either order, or be
467///   mutually recursive.
468///
469/// The relation is deliberately *not* propagated through `List`, `TextMap`,
470/// `GenMap` or `ContractId`. LF-JSON's list form disambiguates an `Optional`
471/// whose argument is *itself* an `Optional`; a container boundary already
472/// disambiguates, so `Optional [a]` at `a = Optional Text` is the ordinary
473/// `Option<Vec<Option<String>>>` and encodes correctly. `map.rs` draws the line
474/// in exactly the same place (see `nested_optional_inner`), and the two must
475/// agree or this pass either refuses code the emitter handles or lets through
476/// code it does not.
477fn parameters_used_under_optional(krate: &Crate) -> HashMap<String, Vec<bool>> {
478    struct Generic<'a> {
479        params: &'a [String],
480        /// `Optional a` occurrences: the parameter is nested outright.
481        direct: std::collections::BTreeSet<&'a str>,
482        /// `Ref(target, args)` occurrences where `args[k]` is a bare parameter:
483        /// it nests if the target nests its own k-th parameter.
484        forwards: Vec<(String, usize, &'a str)>,
485    }
486
487    let mut generics: HashMap<String, Generic<'_>> = HashMap::new();
488    for package in &krate.packages {
489        for module in &package.modules {
490            for data_type in &module.module.data_types {
491                let (name, params, types) = match data_type {
492                    DataType::Record(record) => (
493                        &record.name,
494                        &record.type_params,
495                        record
496                            .fields
497                            .iter()
498                            .map(|field| &field.ty)
499                            .collect::<Vec<_>>(),
500                    ),
501                    DataType::Variant(variant) => (
502                        &variant.name,
503                        &variant.type_params,
504                        variant
505                            .constructors
506                            .iter()
507                            .filter_map(|constructor| constructor.payload.as_ref())
508                            .collect(),
509                    ),
510                    DataType::Enum(_) | DataType::InterfaceMarker(_) => continue,
511                };
512                if params.is_empty() {
513                    continue;
514                }
515                let mut generic = Generic {
516                    params,
517                    direct: std::collections::BTreeSet::new(),
518                    forwards: Vec::new(),
519                };
520                for ty in types {
521                    collect_nesting_positions(ty, &mut generic.direct, &mut generic.forwards);
522                }
523                generics.insert(path_key_for(&package.name, &module.name, name), generic);
524            }
525        }
526    }
527
528    // Seed with the direct occurrences, then propagate along the forward edges
529    // until nothing changes. Monotone (a flag is only ever set) over a finite
530    // lattice, so this terminates even on mutually recursive generics.
531    let mut nests_parameter: HashMap<String, Vec<bool>> = generics
532        .iter()
533        .map(|(key, generic)| {
534            let flags = generic
535                .params
536                .iter()
537                .map(|param| generic.direct.contains(param.as_str()))
538                .collect();
539            (key.clone(), flags)
540        })
541        .collect();
542
543    loop {
544        let mut changed = false;
545        for (key, generic) in &generics {
546            for (target, index, param) in &generic.forwards {
547                let target_nests = nests_parameter
548                    .get(target)
549                    .and_then(|flags| flags.get(*index))
550                    .copied()
551                    .unwrap_or(false);
552                if !target_nests {
553                    continue;
554                }
555                let Some(position) = generic.params.iter().position(|p| p == param) else {
556                    continue;
557                };
558                let Some(flag) = nests_parameter
559                    .get_mut(key)
560                    .and_then(|flags| flags.get_mut(position))
561                else {
562                    continue;
563                };
564                if !*flag {
565                    *flag = true;
566                    changed = true;
567                }
568            }
569        }
570        if !changed {
571            break;
572        }
573    }
574
575    nests_parameter
576}
577
578/// The two ways a type mentions a parameter in a position that decides nesting:
579/// as the immediate argument of an `Optional`, or as a bare argument to another
580/// generic.
581fn collect_nesting_positions<'a>(
582    ty: &'a DamlType,
583    direct: &mut std::collections::BTreeSet<&'a str>,
584    forwards: &mut Vec<(String, usize, &'a str)>,
585) {
586    match ty {
587        DamlType::Optional(inner) => {
588            if let Some(param) = bare_parameter(inner) {
589                direct.insert(param);
590            }
591            collect_nesting_positions(inner, direct, forwards);
592        }
593        DamlType::Ref(reference) => {
594            let target = path_key_of(reference);
595            for (index, arg) in reference.args.iter().enumerate() {
596                if let Some(param) = bare_parameter(arg) {
597                    forwards.push((target.clone(), index, param));
598                }
599                collect_nesting_positions(arg, direct, forwards);
600            }
601        }
602        // Not `ContractId`: its type argument is a phantom tag. `ContractId<T>`
603        // is a string on both wires whatever `T` is, so nothing under one can be
604        // a nested optional, and descending would refuse code that encodes fine.
605        DamlType::List(inner) | DamlType::TextMap(inner) | DamlType::Boxed(inner) => {
606            collect_nesting_positions(inner, direct, forwards);
607        }
608        DamlType::GenMap(key, value) => {
609            collect_nesting_positions(key, direct, forwards);
610            collect_nesting_positions(value, direct, forwards);
611        }
612        _ => {}
613    }
614}
615
616/// The parameter a type *is*, looking through `Box` (a codegen artifact with no
617/// wire form). `None` for anything else, including a parameter buried inside a
618/// container — that occurrence is a different position.
619fn bare_parameter(ty: &DamlType) -> Option<&str> {
620    match ty {
621        DamlType::Var(name) => Some(name),
622        DamlType::Boxed(inner) => bare_parameter(inner),
623        _ => None,
624    }
625}
626
627/// Reject a generic instantiation whose `Optional` argument lands under the
628/// target's own `Optional`.
629///
630/// LF-JSON encodes an `Optional` as `null`/value unless its *immediate*
631/// argument is itself an `Optional`, in which case that one takes the list form
632/// (`[]` / `[x]`) — which is why the mapping turns `Optional (Optional t)` into
633/// `Option<NestedOpt<T>>`. That rewrite is structural, so it cannot see through
634/// a type parameter: for `data Wrap a = Wrap { value : Optional a }`
635/// instantiated at `Wrap (Optional Text)`, the nesting exists on the wire but
636/// not in the spelled-out type, and the emitted `Option<Option<String>>` would
637/// serialize the inner layer as `null` instead of `[]`.
638///
639/// Encoding that correctly needs the instantiation site to know how the target
640/// uses its parameter, and a distinct IR node for "this `Optional` is nested".
641/// Until then the case is refused rather than emitted wrong — it does not occur
642/// anywhere in the Splice corpus, so nothing real is lost.
643///
644/// Which parameters land under an `Optional` is
645/// [`parameters_used_under_optional`]'s answer.
646fn drop_ambiguous_nested_optionals(krate: &mut Crate, errors: &mut Vec<SkippedType>) {
647    let nests_parameter = parameters_used_under_optional(krate);
648
649    // Every declaration kind is an instantiation site: a template's payload is
650    // folded into `Template.fields` and never appears in `data_types` at all,
651    // so scanning only data types missed exactly the case that matters most.
652    let mut ambiguous: Vec<(String, String, String, String)> = Vec::new();
653    for package in &krate.packages {
654        for module in &package.modules {
655            let mut check = |name: &str, types: Vec<&TypeRef>| {
656                if let Some(target) = types.iter().find_map(|reference| {
657                    let nests = nests_parameter.get(&path_key_of(reference))?;
658                    reference
659                        .args
660                        .iter()
661                        .zip(nests)
662                        .any(|(arg, &nested)| nested && matches!(arg, DamlType::Optional(_)))
663                        .then(|| path_key_of(reference))
664                }) {
665                    ambiguous.push((
666                        package.name.clone(),
667                        module.name.clone(),
668                        name.to_string(),
669                        target,
670                    ));
671                }
672            };
673
674            for data_type in &module.module.data_types {
675                let mut types = Vec::new();
676                collect_type_refs_of_data_type(data_type, &mut types);
677                check(data_type_name(data_type), types);
678            }
679            for template in &module.module.templates {
680                let mut types = Vec::new();
681                each_type_of_template(template, &mut |ty| {
682                    collect_type_refs(ty, &mut types);
683                });
684                check(&template.name, types);
685            }
686            for interface in &module.module.interfaces {
687                let mut types = Vec::new();
688                each_type_of_interface(interface, &mut |ty| {
689                    collect_type_refs(ty, &mut types);
690                });
691                check(&interface.name, types);
692            }
693        }
694    }
695
696    for (package_name, module_name, name, target) in ambiguous {
697        let key = path_key_for(&package_name, &module_name, &name);
698        for package in &mut krate.packages {
699            for module in &mut package.modules {
700                let at = |name: &str| path_key_for(&package.name, &module.name, name);
701                module
702                    .module
703                    .data_types
704                    .retain(|data_type| at(data_type_name(data_type)) != key);
705                module
706                    .module
707                    .templates
708                    .retain(|template| at(&template.name) != key);
709                module
710                    .module
711                    .interfaces
712                    .retain(|interface| at(&interface.name) != key);
713            }
714        }
715        errors.push(
716            SkippedType::new(format!(
717                "`{name}` instantiates `{}` with an Optional that the target nests \
718                 under its own Optional; the LF-JSON form of that is not yet representable",
719                target.rsplit("::").next().unwrap_or(&target),
720            ))
721            .in_module(&module_name),
722        );
723    }
724}
725
726/// Every `TypeRef` reachable from a data type (the references themselves, so
727/// their type arguments can be inspected).
728fn collect_type_refs_of_data_type<'a>(data_type: &'a DataType, out: &mut Vec<&'a TypeRef>) {
729    let mut walk = |ty: &'a DamlType| collect_type_refs(ty, out);
730    match data_type {
731        DataType::Record(record) => record.fields.iter().for_each(|field| walk(&field.ty)),
732        DataType::Variant(variant) => variant
733            .constructors
734            .iter()
735            .filter_map(|constructor| constructor.payload.as_ref())
736            .for_each(walk),
737        DataType::Enum(_) | DataType::InterfaceMarker(_) => {}
738    }
739}
740
741/// [`collect_type_refs_of_data_type`] for one type.
742fn collect_type_refs<'a>(ty: &'a DamlType, out: &mut Vec<&'a TypeRef>) {
743    match ty {
744        DamlType::Ref(reference) => {
745            out.push(reference);
746            reference
747                .args
748                .iter()
749                .for_each(|arg| collect_type_refs(arg, out));
750        }
751        DamlType::List(inner)
752        | DamlType::Optional(inner)
753        | DamlType::TextMap(inner)
754        | DamlType::Boxed(inner) => collect_type_refs(inner, out),
755        DamlType::GenMap(key, value) => {
756            collect_type_refs(key, out);
757            collect_type_refs(value, out);
758        }
759        // `ContractId` lands here on purpose: its type argument is a phantom
760        // tag, so an instantiation under one has no wire form to get wrong.
761        // The dangling-reference pass wants the opposite and walks its own
762        // `collect_all_refs_*`, since a `ContractId<Iface>` must still name a
763        // type the crate emits.
764        _ => {}
765    }
766}
767
768/// The path key of every declaration the crate emits under its own name. An
769/// interface is absent on purpose: its key belongs to the marker `struct` in
770/// `data_types`, which is what a `ContractId<Iface>` resolves to.
771fn declared_keys(krate: &Crate) -> std::collections::HashSet<String> {
772    krate
773        .packages
774        .iter()
775        .flat_map(|package| {
776            package.modules.iter().flat_map(move |module| {
777                let at = |name: &str| path_key_for(&package.name, &module.name, name);
778                module
779                    .module
780                    .data_types
781                    .iter()
782                    .map(move |data_type| at(data_type_name(data_type)))
783                    .chain(
784                        module
785                            .module
786                            .templates
787                            .iter()
788                            .map(move |template| at(&template.name)),
789                    )
790            })
791        })
792        .collect()
793}
794
795/// Whether a surviving declaration in `module` still owns `key`. An interface
796/// and its marker `struct` share one, and the marker outlives the interface.
797fn still_declares(module: &Module, key: &str, at: &impl Fn(&str) -> String) -> bool {
798    module
799        .data_types
800        .iter()
801        .any(|data_type| at(data_type_name(data_type)) == key)
802        || module.templates.iter().any(|t| at(&t.name) == key)
803}
804
805/// Remove declarations that reference a type which was skipped, repeating until
806/// nothing changes.
807///
808/// Lowering is best-effort, so an unrepresentable type is skipped with a
809/// warning — but a *surviving* type that mentioned it still emits
810/// `crate::pkg::Mod::Gone` in a field, and the crate handed to the user fails
811/// to compile (E0425) with nothing tying the error back to the warning. A
812/// binding that cannot compile is worth no more than the one that was skipped,
813/// so its dependents go the same way, each reported with the name it was
814/// waiting on.
815fn drop_dangling_references(krate: &mut Crate, errors: &mut Vec<SkippedType>) {
816    let mut declared = declared_keys(krate);
817
818    loop {
819        let mut dropped = false;
820        for package in &mut krate.packages {
821            let package_name = package.name.clone();
822            for module in &mut package.modules {
823                let module_name = module.name.clone();
824                let at = |name: &str| path_key_for(&package_name, &module_name, name);
825
826                let missing = |referenced: Vec<String>| -> Option<String> {
827                    referenced
828                        .into_iter()
829                        .find(|key| key.starts_with("crate::") && !declared.contains(key))
830                };
831
832                let mut report = Vec::new();
833                module.module.data_types.retain(|data_type| {
834                    let mut referenced = Vec::new();
835                    collect_all_refs_of_data_type(data_type, &mut referenced);
836                    match missing(referenced) {
837                        Some(gone) => {
838                            report.push((
839                                at(data_type_name(data_type)),
840                                data_type_name(data_type).to_string(),
841                                gone,
842                            ));
843                            false
844                        }
845                        None => true,
846                    }
847                });
848                module.module.templates.retain(|template| {
849                    let mut referenced = Vec::new();
850                    collect_all_refs_of_template(template, &mut referenced);
851                    match missing(referenced) {
852                        Some(gone) => {
853                            report.push((at(&template.name), template.name.clone(), gone));
854                            false
855                        }
856                        None => true,
857                    }
858                });
859                // An interface's impls name its view and choice types, so it
860                // dangles the same way a record does — and its marker must
861                // still exist.
862                module.module.interfaces.retain(|interface| {
863                    if !declared.contains(&at(&interface.name)) {
864                        return false;
865                    }
866                    let mut referenced = Vec::new();
867                    collect_all_refs_of_interface(interface, &mut referenced);
868                    match missing(referenced) {
869                        Some(gone) => {
870                            report.push((at(&interface.name), interface.name.clone(), gone));
871                            false
872                        }
873                        None => true,
874                    }
875                });
876
877                for (key, name, gone) in report {
878                    // An interface and its marker `struct` share one path key,
879                    // and the marker is a separate `data_types` entry that
880                    // survives. Unregistering the key would make every holder
881                    // of a `ContractId<Iface>` look dangling and cascade-drop
882                    // it, naming a type the crate does emit.
883                    if !still_declares(&module.module, &key, &at) {
884                        declared.remove(&key);
885                    }
886                    errors.push(
887                        SkippedType::new(format!(
888                            "`{name}` references `{}`, which was skipped",
889                            gone.rsplit("::").next().unwrap_or(&gone),
890                        ))
891                        .in_module(&module_name),
892                    );
893                    dropped = true;
894                }
895            }
896        }
897        if !dropped {
898            break;
899        }
900    }
901
902    krate.packages.iter_mut().for_each(|package| {
903        package.modules.retain(|module| {
904            !module.module.data_types.is_empty()
905                || !module.module.templates.is_empty()
906                || !module.module.interfaces.is_empty()
907        });
908    });
909    krate.packages.retain(|package| !package.modules.is_empty());
910}
911
912/// Every named-type reference reachable from a data type.
913fn collect_all_refs_of_data_type(data_type: &DataType, out: &mut Vec<String>) {
914    match data_type {
915        DataType::Record(record) => record
916            .fields
917            .iter()
918            .for_each(|field| collect_all_refs(&field.ty, out)),
919        DataType::Variant(variant) => variant
920            .constructors
921            .iter()
922            .filter_map(|constructor| constructor.payload.as_ref())
923            .for_each(|payload| collect_all_refs(payload, out)),
924        DataType::Enum(_) | DataType::InterfaceMarker(_) => {}
925    }
926}
927
928/// Every named-type reference reachable from a template: its payload, its
929/// choices' argument and return types, and its contract key.
930fn collect_all_refs_of_template(template: &Template, out: &mut Vec<String>) {
931    each_type_of_template(template, &mut |ty| collect_all_refs(ty, out));
932}
933
934/// Every named-type reference reachable from an interface: its view type and
935/// its choices' argument and return types.
936fn collect_all_refs_of_interface(interface: &Interface, out: &mut Vec<String>) {
937    each_type_of_interface(interface, &mut |ty| collect_all_refs(ty, out));
938}
939
940/// Visit every type a template mentions. Both post-lowering passes go through
941/// this rather than each walking the fields it happens to remember.
942fn each_type_of_template<'a>(template: &'a Template, visit: &mut impl FnMut(&'a DamlType)) {
943    for field in &template.fields {
944        visit(&field.ty);
945    }
946    for choice in &template.choices {
947        visit(&choice.argument);
948        visit(&choice.returns);
949    }
950    if let Some(key) = &template.key {
951        visit(key);
952    }
953}
954
955/// Visit every type an interface mentions.
956fn each_type_of_interface<'a>(interface: &'a Interface, visit: &mut impl FnMut(&'a DamlType)) {
957    if let Some(view) = &interface.view {
958        visit(view);
959    }
960    for choice in &interface.choices {
961        visit(&choice.argument);
962        visit(&choice.returns);
963    }
964}
965
966/// Every named-type reference inside a type, at any depth (unlike
967/// `collect_inline_refs`, which stops at heap indirection).
968fn collect_all_refs(ty: &DamlType, out: &mut Vec<String>) {
969    match ty {
970        DamlType::Ref(reference) => {
971            out.push(path_key_of(reference));
972            reference
973                .args
974                .iter()
975                .for_each(|arg| collect_all_refs(arg, out));
976        }
977        DamlType::ContractId(inner)
978        | DamlType::List(inner)
979        | DamlType::Optional(inner)
980        | DamlType::TextMap(inner)
981        | DamlType::Boxed(inner) => collect_all_refs(inner, out),
982        DamlType::GenMap(key, value) => {
983            collect_all_refs(key, out);
984            collect_all_refs(value, out);
985        }
986        _ => {}
987    }
988}
989
990/// The Rust module name each package's types live under, keyed by package id.
991///
992/// The name alone (`splice_amulet`), **not** name-and-version: under Smart
993/// Contract Upgrade the participant resolves which vetted version a command
994/// targets, so a routine DAR bump should not rename every path a caller
995/// imports. The version is appended only where it is doing work — when one DAR
996/// genuinely bundles two versions of the same package — and the package-id
997/// prefix only if even that is ambiguous.
998fn package_module_names(packages: &[(String, lf::Package)]) -> HashMap<&str, String> {
999    let mut names: HashMap<&str, String> = packages
1000        .iter()
1001        .map(|(id, package)| {
1002            let base =
1003                package_name(package).map_or_else(|| format!("p_{}", ident_prefix(id)), sanitize);
1004            (id.as_str(), base)
1005        })
1006        .collect();
1007
1008    // One pass per disambiguation step: add the version where the bare name
1009    // repeats, then the id prefix where even that repeats (a rebuilt package
1010    // keeps its metadata but gets a new id).
1011    // An empty or metadata-less version is no disambiguator — several packages
1012    // carry `version: ""`, and using it would collapse them all onto one name.
1013    let versioned = |id: &str| {
1014        packages
1015            .iter()
1016            .find(|(candidate, _)| candidate == id)
1017            .and_then(|(_, package)| package_version(package))
1018            .map(sanitize)
1019            .filter(|version| !version.is_empty())
1020    };
1021    // The last step appends the whole package id, which is the map's key and so
1022    // is unique by construction: the loop always terminates with distinct names
1023    // rather than leaving a duplicate for rustc to report as E0428. Two ids
1024    // agreeing on their first 8 hex characters is not going to happen, but
1025    // "not going to happen" is a poor reason to emit a crate that will not
1026    // compile.
1027    for step in 0..3 {
1028        let mut counts: HashMap<String, usize> = HashMap::new();
1029        for name in names.values() {
1030            *counts.entry(name.clone()).or_default() += 1;
1031        }
1032        let ambiguous: std::collections::HashSet<String> = counts
1033            .into_iter()
1034            .filter(|(_, count)| *count > 1)
1035            .map(|(name, _)| name)
1036            .collect();
1037        if ambiguous.is_empty() {
1038            break;
1039        }
1040        for (id, name) in &mut names {
1041            if !ambiguous.contains(name.as_str()) {
1042                continue;
1043            }
1044            let suffix = match step {
1045                0 => versioned(id).unwrap_or_else(|| ident_prefix(id)),
1046                1 => ident_prefix(id),
1047                _ => sanitize(id),
1048            };
1049            *name = format!("{name}_{suffix}");
1050        }
1051    }
1052    names
1053}
1054
1055/// The first 8 identifier-safe characters of a package id, for `p_<hash8>` /
1056/// collision-suffix module names. Character-based (a byte slice could panic on
1057/// a hostile non-ASCII id) and filtered to alphanumerics. An id with no
1058/// alphanumerics at all would yield an *empty* suffix, defeating the
1059/// de-collision this exists for, so it falls back to a hash of the id.
1060fn ident_prefix(package_id: &str) -> String {
1061    let prefix: String = package_id
1062        .chars()
1063        .filter(char::is_ascii_alphanumeric)
1064        .take(8)
1065        .collect();
1066    if prefix.is_empty() {
1067        use std::hash::{Hash as _, Hasher as _};
1068        let mut hasher = std::collections::hash_map::DefaultHasher::new();
1069        package_id.hash(&mut hasher);
1070        format!("h{:x}", hasher.finish())
1071    } else {
1072        prefix
1073    }
1074}
1075
1076/// Rust module names claimed by more than one distinct Daml module name in a
1077/// package, mapped to the Daml names that claim them.
1078fn colliding_module_names(
1079    package: &lf::Package,
1080) -> std::collections::BTreeMap<String, Vec<String>> {
1081    let mut claims: std::collections::BTreeMap<String, Vec<String>> =
1082        std::collections::BTreeMap::new();
1083    for lf_module in package.modules.iter().filter(|m| emits_anything(m)) {
1084        let Some(dotted) = interned_dotted_name(package, lf_module.name_interned_dname) else {
1085            continue;
1086        };
1087        let sources = claims.entry(dotted.replace('.', "_")).or_default();
1088        if !sources.contains(&dotted) {
1089            sources.push(dotted);
1090        }
1091    }
1092    claims.retain(|_, sources| sources.len() > 1);
1093    claims
1094}
1095
1096/// Whether a data type reaches the generated crate: an interface contributes
1097/// its marker `struct`, anything else has to be serializable.
1098fn emits_a_type(def: &lf::DefDataType) -> bool {
1099    matches!(
1100        def.data_cons,
1101        Some(lf::def_data_type::DataCons::Interface(_))
1102    ) || def.serializable
1103}
1104
1105/// Whether a module reaches the generated crate at all. A Daml module of pure
1106/// functions lowers to nothing and is never pushed as a `pub mod`, so it cannot
1107/// collide with one that is — and must not be counted as if it could.
1108fn emits_anything(lf_module: &lf::Module) -> bool {
1109    !lf_module.templates.is_empty()
1110        || !lf_module.interfaces.is_empty()
1111        || lf_module.data_types.iter().any(emits_a_type)
1112}
1113
1114/// Map each non-alphanumeric character to `_` so the result is a valid Rust
1115/// identifier fragment (`splice-amulet` → `splice_amulet`, `0.1.14` → `0_1_14`).
1116fn sanitize(input: &str) -> String {
1117    input
1118        .chars()
1119        .map(|c| if c.is_ascii_alphanumeric() { c } else { '_' })
1120        .collect()
1121}
1122
1123/// How a lowering resolves references, given the current package.
1124struct Lowering<'a> {
1125    package: &'a lf::Package,
1126    /// `None` → local references (bare type name). `Some` → fully-qualified
1127    /// paths resolved through the DAR's package map.
1128    qualify: Option<Qualify<'a>>,
1129    /// How many frames of [`Lowering::apply`] are currently on the stack.
1130    ///
1131    /// Held here rather than passed as an argument so the eleven call sites of
1132    /// `type_`/`apply` keep their signatures; `Lowering` is used from one thread
1133    /// for the length of one package.
1134    depth: std::cell::Cell<u32>,
1135}
1136
1137/// How deep type resolution may go before the DAR is refused.
1138///
1139/// Every other axis of this recursion is already bounded: prost caps nested
1140/// messages, so a type nested inside a type inside a type runs out long before
1141/// the stack does. Interned types are the exception, and they are not nested at
1142/// all — they are a flat table of indices, so `interned_types[0]` may be
1143/// `Interned(0)`, or two entries may point at each other, and prost sees
1144/// nothing to object to. Resolving that recurses until the stack ends, and a
1145/// stack overflow **aborts the process** rather than failing the DAR: the one
1146/// place in this reader where hostile input still killed the caller after the
1147/// zip-bomb, entry-size, archive-size, package-id and LF-version guards.
1148///
1149/// Far above anything real: the deepest type across the 648-package corpus
1150/// resolves in 15 levels, so this leaves seventeen times the headroom and only
1151/// ever fires on an archive built to fire it.
1152const MAX_TYPE_DEPTH: u32 = 256;
1153
1154/// Decrements [`Lowering::depth`] however the frame it guards is left.
1155struct DepthGuard<'a>(&'a std::cell::Cell<u32>);
1156
1157impl Drop for DepthGuard<'_> {
1158    fn drop(&mut self) {
1159        self.0.set(self.0.get() - 1);
1160    }
1161}
1162
1163/// The first segment of a path into another crate. Emitted as a leading `::`,
1164/// which no Daml name can produce, so it cannot collide with one.
1165pub(crate) const EXTERNAL_CRATE_ROOT: &str = "::";
1166
1167/// The context for qualifying a reference to a fully-qualified Rust path.
1168struct Qualify<'a> {
1169    /// package id (archive hash) → the package's Rust module name. Resolves all
1170    /// three reference forms, since each ultimately names a package by its id.
1171    module_names: &'a HashMap<&'a str, String>,
1172    /// The id of the package currently being lowered (resolves `SelfPackageId`).
1173    current_id: &'a str,
1174    /// package id → the crate that already publishes it. A reference to one of
1175    /// these points at that crate; its modules are not emitted here.
1176    external: &'a HashMap<String, String>,
1177    /// The package ids this crate emits, when it emits only some of them.
1178    /// `None` means the whole DAR, which is the ordinary case.
1179    ///
1180    /// A reference to a package that is neither emitted here nor external has
1181    /// nowhere to resolve, so it is reported rather than written out as a path
1182    /// into a module that does not exist.
1183    selected: Option<&'a HashSet<String>>,
1184}
1185
1186impl Lowering<'_> {
1187    /// Lower a whole Daml module: its data types and its templates. A template's
1188    /// payload is a same-named record; it is folded into the [`Template`] (as its
1189    /// `fields`) and *not* emitted as a standalone record, so the payload struct
1190    /// is generated exactly once.
1191    fn module(
1192        &self,
1193        lf_module: &lf::Module,
1194        module_dotted: &str,
1195        errors: &mut Vec<SkippedType>,
1196    ) -> Module {
1197        // Daml names are dotted and Rust identifiers are not, so `A.B` and `A_B`
1198        // both flatten to `A_B`. Emitting both would either overwrite one
1199        // silently (a template taking another type's payload — wrong commands,
1200        // no compile error) or produce a duplicate definition. Detect the clash
1201        // up front and skip the whole colliding set, naming both Daml sources.
1202        let colliding = self.colliding_flattened_names(lf_module);
1203
1204        // The names that are templates — their payload record is folded in below.
1205        let template_names: std::collections::HashSet<String> = lf_module
1206            .templates
1207            .iter()
1208            .filter_map(|def| rust_name(self.package, def.tycon_interned_dname).ok())
1209            .filter(|name| !colliding.contains_key(name))
1210            .collect();
1211
1212        for (flattened, sources) in &colliding {
1213            errors.push(
1214                SkippedType::new(format!(
1215                    "`{}` both map to the Rust name `{flattened}`; \
1216                     rename one in Daml to generate either",
1217                    sources.join("` and `"),
1218                ))
1219                .in_module(module_dotted),
1220            );
1221        }
1222        let skip = |name: &str| colliding.contains_key(name);
1223
1224        let mut module = Module::default();
1225        // Payload records held aside for their template (name → fields).
1226        let mut payloads: HashMap<String, Vec<Field>> = HashMap::new();
1227        // Two declarations under one name is the shape the flattening check
1228        // cannot catch: it groups *distinct* dotted names that collide after
1229        // flattening, and a duplicate is the same name, not a distinct one. The
1230        // consequence is worse than a redefinition, because when the name is
1231        // also a template's, the payload map takes the last writer and the
1232        // template is emitted with another type's fields — wrong commands, and
1233        // it compiles.
1234        let mut seen_types: std::collections::HashSet<String> = std::collections::HashSet::new();
1235        for data_type in &lf_module.data_types {
1236            match self.data_type(data_type) {
1237                Ok(Some(lowered)) if skip(data_type_name(&lowered)) => {}
1238                Ok(Some(lowered)) if !seen_types.insert(data_type_name(&lowered).to_string()) => {
1239                    errors.push(
1240                        SkippedType::new(format!(
1241                            "`{}` is declared more than once in this module",
1242                            data_type_name(&lowered)
1243                        ))
1244                        .in_module(module_dotted),
1245                    );
1246                }
1247                Ok(Some(DataType::Record(record))) if template_names.contains(&record.name) => {
1248                    payloads.insert(record.name, record.fields);
1249                }
1250                Ok(Some(lowered)) => module.data_types.push(lowered),
1251                Ok(None) => {}
1252                Err(error) => errors.push(error.in_module(module_dotted)),
1253            }
1254        }
1255
1256        for def in &lf_module.templates {
1257            match self.template(def, module_dotted, &mut payloads) {
1258                Ok(template) if skip(&template.name) => {}
1259                Ok(template) => module.templates.push(template),
1260                Err(error) => errors.push(error.in_module(module_dotted)),
1261            }
1262        }
1263
1264        for def in &lf_module.interfaces {
1265            match self.interface(def, module_dotted) {
1266                Ok(interface) if skip(&interface.name) => {}
1267                Ok(interface) => module.interfaces.push(interface),
1268                Err(error) => errors.push(error.in_module(module_dotted)),
1269            }
1270        }
1271        module
1272    }
1273
1274    /// Rust names claimed by more than one *distinct* Daml name in this module,
1275    /// mapped to the Daml names that claim them. A template and its payload
1276    /// record share one Daml name, so they never collide with each other.
1277    fn colliding_flattened_names(
1278        &self,
1279        lf_module: &lf::Module,
1280    ) -> std::collections::BTreeMap<String, Vec<String>> {
1281        // Only what the crate emits can collide. A non-serializable data type
1282        // is dropped by `data_type`, so counting its name here took a real type
1283        // down with a claimant that never appears.
1284        let declared = lf_module
1285            .data_types
1286            .iter()
1287            .filter(|def| emits_a_type(def))
1288            .map(|def| def.name_interned_dname)
1289            .chain(
1290                lf_module
1291                    .templates
1292                    .iter()
1293                    .map(|def| def.tycon_interned_dname),
1294            )
1295            .chain(
1296                lf_module
1297                    .interfaces
1298                    .iter()
1299                    .map(|def| def.tycon_interned_dname),
1300            );
1301
1302        let mut claims: std::collections::BTreeMap<String, Vec<String>> =
1303            std::collections::BTreeMap::new();
1304        for index in declared {
1305            let Some(dotted) = interned_dotted_name(self.package, index) else {
1306                continue;
1307            };
1308            let flattened = dotted.replace('.', "_");
1309            let sources = claims.entry(flattened).or_default();
1310            if !sources.contains(&dotted) {
1311                sources.push(dotted);
1312            }
1313        }
1314        claims.retain(|_, sources| sources.len() > 1);
1315        claims
1316    }
1317
1318    /// Lower one `DefInterface`: its view type and choices, plus the on-ledger
1319    /// identity choices are exercised through. The marker struct is emitted from
1320    /// the interface's data type; this carries the impls on it.
1321    fn interface(
1322        &self,
1323        def: &lf::DefInterface,
1324        module_dotted: &str,
1325    ) -> Result<Interface, SkippedType> {
1326        let name = rust_name(self.package, def.tycon_interned_dname)?;
1327        let choices = def
1328            .choices
1329            .iter()
1330            .map(|choice| self.choice(choice))
1331            .collect::<Result<Vec<_>, _>>()?;
1332        let view = def
1333            .view
1334            .as_ref()
1335            .map(|ty| self.type_(ty))
1336            .transpose()?
1337            // An interface always has a view; `Unit` means an empty view record.
1338            .filter(|view| !matches!(view, DamlType::Unit));
1339        Ok(Interface {
1340            name,
1341            module_name: module_dotted.to_string(),
1342            package_id: self
1343                .qualify
1344                .as_ref()
1345                .map_or_else(String::new, |q| q.current_id.to_string()),
1346            package_name: package_name(self.package).unwrap_or_default().to_string(),
1347            view,
1348            choices,
1349        })
1350    }
1351
1352    /// Lower one `DefTemplate`: its payload fields (taken from the folded-in
1353    /// record), choices, and optional contract key.
1354    fn template(
1355        &self,
1356        def: &lf::DefTemplate,
1357        module_dotted: &str,
1358        payloads: &mut HashMap<String, Vec<Field>>,
1359    ) -> Result<Template, SkippedType> {
1360        let name = rust_name(self.package, def.tycon_interned_dname)?;
1361        let fields = payloads.remove(&name).ok_or_else(|| {
1362            SkippedType::new(format!("template {name}: payload record not found"))
1363        })?;
1364        let choices = def
1365            .choices
1366            .iter()
1367            .map(|choice| self.choice(choice))
1368            .collect::<Result<Vec<_>, _>>()?;
1369        let key = match def.key.as_ref() {
1370            // A declared key with no type is malformed LF: lowering it to a
1371            // keyless template would silently drop `exercise_by_key`.
1372            Some(key) => Some(self.type_(key.r#type.as_ref().ok_or_else(|| {
1373                SkippedType::new(format!("template {name}: contract key has no type"))
1374            })?)?),
1375            None => None,
1376        };
1377        Ok(Template {
1378            name,
1379            module_name: module_dotted.to_string(),
1380            package_id: self
1381                .qualify
1382                .as_ref()
1383                .map_or_else(String::new, |q| q.current_id.to_string()),
1384            package_name: package_name(self.package).unwrap_or_default().to_string(),
1385            fields,
1386            choices,
1387            key,
1388        })
1389    }
1390
1391    /// Lower one `TemplateChoice`: its name, consuming flag, argument type, and
1392    /// return type. The controller/observer/body expressions are term-level and
1393    /// intentionally not modelled (see the decoder note on `Expr`).
1394    fn choice(&self, choice: &lf::TemplateChoice) -> Result<Choice, SkippedType> {
1395        let name = interned_str(self.package, choice.name_interned_str)
1396            .ok_or_else(|| SkippedType::new("unresolved choice name"))?
1397            .to_string();
1398        let argument = choice
1399            .arg_binder
1400            .as_ref()
1401            .and_then(|binder| binder.r#type.as_ref())
1402            .ok_or_else(|| SkippedType::new(format!("choice {name}: no argument type")))?;
1403        let returns = choice
1404            .ret_type
1405            .as_ref()
1406            .ok_or_else(|| SkippedType::new(format!("choice {name}: no return type")))?;
1407        Ok(Choice {
1408            name,
1409            consuming: choice.consuming,
1410            argument: self.type_(argument)?,
1411            returns: self.type_(returns)?,
1412        })
1413    }
1414
1415    /// Lower one `DefDataType`. `Ok(None)` when intentionally skipped
1416    /// (non-serializable, or an interface view marker).
1417    fn data_type(&self, data_type: &lf::DefDataType) -> Result<Option<DataType>, SkippedType> {
1418        let name = rust_name(self.package, data_type.name_interned_dname)?;
1419
1420        // An interface is not serializable, but references to it (always
1421        // `ContractId<I>`) must resolve — emit a phantom marker. Handle it
1422        // before the serializable gate, which would otherwise skip it.
1423        if let Some(lf::def_data_type::DataCons::Interface(_)) = &data_type.data_cons {
1424            return Ok(Some(DataType::InterfaceMarker(name)));
1425        }
1426
1427        // Codegen otherwise only wants serializable types (skip functions, and
1428        // internal non-serializable records like the interface `Any` wrappers).
1429        if !data_type.serializable {
1430            return Ok(None);
1431        }
1432        let type_params = data_type
1433            .params
1434            .iter()
1435            .map(|param| {
1436                interned_str(self.package, param.var_interned_str)
1437                    .map(str::to_string)
1438                    .ok_or_else(|| SkippedType::new("unresolved type parameter"))
1439            })
1440            .collect::<Result<Vec<_>, _>>()?;
1441        check_type_params(&name, &type_params)?;
1442
1443        let Some(data_cons) = &data_type.data_cons else {
1444            return Err(SkippedType::new(format!("{name}: no data constructor")));
1445        };
1446
1447        match data_cons {
1448            lf::def_data_type::DataCons::Record(fields) => {
1449                let fields = self.fields(&fields.fields)?;
1450                Ok(Some(DataType::Record(Record {
1451                    name,
1452                    type_params,
1453                    fields,
1454                })))
1455            }
1456            lf::def_data_type::DataCons::Variant(fields) => {
1457                let constructors = fields
1458                    .fields
1459                    .iter()
1460                    .map(|field| self.variant_constructor(field))
1461                    .collect::<Result<Vec<_>, _>>()?;
1462                Ok(Some(DataType::Variant(Variant {
1463                    name,
1464                    type_params,
1465                    constructors,
1466                })))
1467            }
1468            lf::def_data_type::DataCons::Enum(constructors) => {
1469                let constructors = constructors
1470                    .constructors_interned_str
1471                    .iter()
1472                    .map(|&index| {
1473                        let name = interned_str(self.package, index)
1474                            .ok_or_else(|| SkippedType::new("unresolved enum constructor"))?;
1475                        if is_rust_ident(name) {
1476                            Ok(name.to_string())
1477                        } else {
1478                            Err(SkippedType::new(format!(
1479                                "enum constructor `{name}` is not representable as a Rust identifier"
1480                            )))
1481                        }
1482                    })
1483                    .collect::<Result<Vec<_>, _>>()?;
1484                Ok(Some(DataType::Enum(Enum { name, constructors })))
1485            }
1486            // An interface's view marker — reserved, lowered separately later.
1487            lf::def_data_type::DataCons::Interface(_) => Ok(None),
1488        }
1489    }
1490
1491    fn fields(&self, fields: &[lf::FieldWithType]) -> Result<Vec<Field>, SkippedType> {
1492        let lowered = fields
1493            .iter()
1494            .map(|field| {
1495                let label = interned_str(self.package, field.field_interned_str)
1496                    .ok_or_else(|| SkippedType::new("unresolved field label"))?
1497                    .to_string();
1498                let ty = self.type_(field_type(field)?)?;
1499                Ok(Field { label, ty })
1500            })
1501            .collect::<Result<Vec<Field>, SkippedType>>()?;
1502
1503        // Two Daml labels can collapse onto one Rust field name (`fooBar` and
1504        // `foo_bar` both snake_case to `foo_bar`); the emitted struct would not
1505        // compile. Fail the type with a clear message instead.
1506        let mut by_rust_name: HashMap<String, &str> = HashMap::new();
1507        for field in &lowered {
1508            let rust = heck::ToSnakeCase::to_snake_case(field.label.as_str());
1509            if let Some(previous) = by_rust_name.insert(rust.clone(), &field.label) {
1510                return Err(SkippedType::new(format!(
1511                    "fields `{previous}` and `{}` both map to the Rust field `{rust}`",
1512                    field.label
1513                )));
1514            }
1515        }
1516        Ok(lowered)
1517    }
1518
1519    fn variant_constructor(
1520        &self,
1521        field: &lf::FieldWithType,
1522    ) -> Result<VariantConstructor, SkippedType> {
1523        let name = interned_str(self.package, field.field_interned_str)
1524            .ok_or_else(|| SkippedType::new("unresolved variant constructor"))?
1525            .to_string();
1526        if !is_rust_ident(&name) {
1527            return Err(SkippedType::new(format!(
1528                "variant constructor `{name}` is not representable as a Rust identifier"
1529            )));
1530        }
1531        // A `Unit` payload is a nullary constructor.
1532        let payload = match self.type_(field_type(field)?)? {
1533            DamlType::Unit => None,
1534            other => Some(other),
1535        };
1536        Ok(VariantConstructor { name, payload })
1537    }
1538
1539    /// Lower an LF [`Type`](lf::Type) into a [`DamlType`], resolving interned
1540    /// types and (when qualifying) references to fully-qualified paths.
1541    fn type_(&self, ty: &lf::Type) -> Result<DamlType, SkippedType> {
1542        self.apply(ty, &[])
1543    }
1544
1545    /// Claim one frame of type-resolution depth, or refuse the type.
1546    ///
1547    /// Refusing is a `SkippedType`, which is how every other unrepresentable
1548    /// type is reported: the DAR still generates, minus the type that could not
1549    /// be resolved and — through the existing dependent-skip pass — anything
1550    /// that referred to it. The alternative on this path is not a worse error
1551    /// message, it is `SIGABRT`.
1552    fn enter(&self) -> Result<DepthGuard<'_>, SkippedType> {
1553        let depth = self.depth.get();
1554        if depth >= MAX_TYPE_DEPTH {
1555            return Err(SkippedType::new(format!(
1556                "type nests deeper than {MAX_TYPE_DEPTH} levels, or its interned \
1557                 types refer to each other in a cycle"
1558            )));
1559        }
1560        self.depth.set(depth + 1);
1561        Ok(DepthGuard(&self.depth))
1562    }
1563
1564    /// Lower `ty` applied to `extra` type arguments. This unifies the flattened
1565    /// form (`Con`/`Builtin` with an `args` list) and the curried `TApp` form
1566    /// (LF 2.dev): `TApp(lhs, rhs)` applies `lhs` to `rhs` prepended to `extra`,
1567    /// so `((f a) b)` collapses to `f` applied to `[a, b]` regardless of shape.
1568    fn apply(&self, ty: &lf::Type, extra: &[&lf::Type]) -> Result<DamlType, SkippedType> {
1569        // Held for the whole frame, so the count unwinds with every early
1570        // return below as well as the successful one.
1571        let _guard = self.enter()?;
1572        let Some(sum) = &ty.sum else {
1573            return Err(SkippedType::new("empty type"));
1574        };
1575        match sum {
1576            lf::r#type::Sum::Tapp(app) => {
1577                let lhs = app
1578                    .lhs
1579                    .as_deref()
1580                    .ok_or_else(|| SkippedType::new("type application without a function"))?;
1581                let rhs = app
1582                    .rhs
1583                    .as_deref()
1584                    .ok_or_else(|| SkippedType::new("type application without an argument"))?;
1585                let mut args = Vec::with_capacity(extra.len() + 1);
1586                args.push(rhs);
1587                args.extend_from_slice(extra);
1588                self.apply(lhs, &args)
1589            }
1590            lf::r#type::Sum::Var(var) => {
1591                // A higher-kinded application (`f a`) has no Rust equivalent.
1592                // `Var` carries its own `args` as well as any from a curried
1593                // `TApp`; dropping either would silently emit the bare
1594                // parameter in place of the applied type.
1595                if !extra.is_empty() || !var.args.is_empty() {
1596                    return Err(SkippedType::new("type-variable application is unsupported"));
1597                }
1598                let name = interned_str(self.package, var.var_interned_str)
1599                    .ok_or_else(|| SkippedType::new("unresolved type variable"))?
1600                    .to_string();
1601                Ok(DamlType::Var(name))
1602            }
1603            lf::r#type::Sum::Con(con) => {
1604                let tycon = con
1605                    .tycon
1606                    .as_ref()
1607                    .ok_or_else(|| SkippedType::new("type constructor without a name"))?;
1608                let path = self.con_path(tycon)?;
1609                let args = con
1610                    .args
1611                    .iter()
1612                    .chain(extra.iter().copied())
1613                    .map(|arg| self.type_(arg))
1614                    .collect::<Result<Vec<_>, _>>()?;
1615                Ok(DamlType::Ref(TypeRef { path, args }))
1616            }
1617            lf::r#type::Sum::Builtin(builtin) => {
1618                let args: Vec<&lf::Type> =
1619                    builtin.args.iter().chain(extra.iter().copied()).collect();
1620                self.builtin(builtin.builtin, &args)
1621            }
1622            lf::r#type::Sum::Interned(index) => {
1623                let resolved = interned_type(self.package, *index)
1624                    .ok_or_else(|| SkippedType::new("unresolved interned type"))?;
1625                self.apply(resolved, extra)
1626            }
1627            lf::r#type::Sum::Nat(_) => Err(SkippedType::new("unexpected bare type-level Nat")),
1628            lf::r#type::Sum::Forall(_) | lf::r#type::Sum::Struct(_) | lf::r#type::Sum::Syn(_) => {
1629                Err(SkippedType::new(
1630                    "unsupported LF type (forall / struct / syn)",
1631                ))
1632            }
1633        }
1634    }
1635
1636    /// The Rust path segments for a type constructor reference. Local when not
1637    /// qualifying; otherwise `["crate", <package>, <module>, <Type>]`.
1638    ///
1639    /// The target package is resolved from the reference's package identity,
1640    /// which LF spells three ways — **self**, an **imported** package by its
1641    /// interned id-hash, and (newer LF) an index into the explicit import table
1642    /// — each of which ends at an id hash, and from there at the package's Rust
1643    /// module name.
1644    ///
1645    /// Not a fourth way: the `#<package-name>` form of Smart Contract Upgrade is
1646    /// a Ledger API spelling for a template id, not an LF type reference, and
1647    /// never reaches here.
1648    fn con_path(&self, tycon: &lf::TypeConId) -> Result<Vec<String>, SkippedType> {
1649        use lf::self_or_imported_package_id::Sum;
1650
1651        let type_name = rust_name(self.package, tycon.name_interned_dname)?;
1652        let Some(qualify) = &self.qualify else {
1653            return Ok(vec![type_name]);
1654        };
1655
1656        let module_id = tycon
1657            .module
1658            .as_ref()
1659            .ok_or_else(|| SkippedType::new("type constructor without a module"))?;
1660        let module_dotted =
1661            interned_dotted_name(self.package, module_id.module_name_interned_dname)
1662                .ok_or_else(|| SkippedType::new("unresolved referenced module name"))?;
1663
1664        // Every reference form ultimately names a package by its id hash;
1665        // resolve to that hash, then to the package's Rust module name.
1666        let target_id = match module_id.package_id.as_ref().and_then(|p| p.sum.as_ref()) {
1667            // A self reference, or an absent package id, targets this package.
1668            None | Some(Sum::SelfPackageId(_)) => qualify.current_id.to_string(),
1669            // An imported package identified by its id-hash, interned here.
1670            Some(Sum::ImportedPackageIdInternedStr(index)) => interned_str(self.package, *index)
1671                .ok_or_else(|| SkippedType::new("unresolved imported package id"))?
1672                .to_string(),
1673            // Newer LF: an index into the package's explicit import table, whose
1674            // entry is the target package's id hash.
1675            Some(Sum::PackageImportId(index)) => imported_package_id(self.package, *index)
1676                .ok_or_else(|| SkippedType::new("unresolved package import id"))?
1677                .to_string(),
1678        };
1679        let package_module = qualify
1680            .module_names
1681            .get(target_id.as_str())
1682            .ok_or_else(|| {
1683                SkippedType::new(format!("reference to package {target_id} not in the DAR"))
1684            })?;
1685
1686        // A package published as its own crate is referenced there, so both
1687        // crates name one type rather than each declaring its own. The leading
1688        // `::` marker makes the path absolute — without it a generated module
1689        // sharing the crate's name would shadow it.
1690        if let Some(crate_name) = qualify.external.get(&target_id) {
1691            // The package module segment stays: the other crate wraps its
1692            // packages exactly as this one does, so the path there is
1693            // `::<crate>::<package>::<module>::<Type>`. Dropping it produced
1694            // `::<crate>::<module>::<Type>`, which does not resolve.
1695            return Ok(vec![
1696                EXTERNAL_CRATE_ROOT.to_string(),
1697                crate_name.clone(),
1698                package_module.clone(),
1699                module_dotted.replace('.', "_"),
1700                type_name,
1701            ]);
1702        }
1703
1704        // In the DAR, but not in this crate and not published as another one:
1705        // the path below would name a module this crate never emits. Report it
1706        // instead of emitting code that does not compile.
1707        if let Some(selected) = qualify.selected
1708            && !selected.contains(&target_id)
1709        {
1710            return Err(SkippedType::new(format!(
1711                "reference to {package_module}, which this crate neither \
1712                 generates nor references as an external package"
1713            )));
1714        }
1715
1716        Ok(vec![
1717            "crate".to_string(),
1718            package_module.clone(),
1719            module_dotted.replace('.', "_"),
1720            type_name,
1721        ])
1722    }
1723
1724    fn builtin(&self, builtin: i32, args: &[&lf::Type]) -> Result<DamlType, SkippedType> {
1725        use lf::BuiltinType;
1726
1727        let kind = BuiltinType::try_from(builtin)
1728            .map_err(|_| SkippedType::new(format!("unknown builtin type {builtin}")))?;
1729        let arg = |index: usize| -> Result<DamlType, SkippedType> {
1730            let ty = args.get(index).copied().ok_or_else(|| {
1731                SkippedType::new(format!("{kind:?} missing type argument {index}"))
1732            })?;
1733            self.type_(ty)
1734        };
1735
1736        let ty = match kind {
1737            BuiltinType::Unit => DamlType::Unit,
1738            BuiltinType::Bool => DamlType::Bool,
1739            BuiltinType::Int64 => DamlType::Int64,
1740            BuiltinType::Date => DamlType::Date,
1741            BuiltinType::Timestamp => DamlType::Timestamp,
1742            BuiltinType::Party => DamlType::Party,
1743            BuiltinType::Text => DamlType::Text,
1744            BuiltinType::Numeric => DamlType::Numeric(self.numeric_scale(args)?),
1745            BuiltinType::ContractId => DamlType::ContractId(Box::new(arg(0)?)),
1746            BuiltinType::Optional => DamlType::Optional(Box::new(arg(0)?)),
1747            BuiltinType::List => DamlType::List(Box::new(arg(0)?)),
1748            BuiltinType::Textmap => DamlType::TextMap(Box::new(arg(0)?)),
1749            BuiltinType::Genmap => DamlType::GenMap(Box::new(arg(0)?), Box::new(arg(1)?)),
1750            other => {
1751                return Err(SkippedType::new(format!(
1752                    "builtin type {other:?} is not representable in codegen"
1753                )));
1754            }
1755        };
1756        Ok(ty)
1757    }
1758
1759    /// The scale of a `Numeric n` from its type-level `Nat` argument.
1760    fn numeric_scale(&self, args: &[&lf::Type]) -> Result<u8, SkippedType> {
1761        let mut ty = *args
1762            .first()
1763            .ok_or_else(|| SkippedType::new("Numeric without a scale argument"))?;
1764        // Follow one level of interning if the scale is stored in the type table.
1765        if let Some(lf::r#type::Sum::Interned(index)) = &ty.sum {
1766            ty = interned_type(self.package, *index)
1767                .ok_or_else(|| SkippedType::new("unresolved Numeric scale"))?;
1768        }
1769        match &ty.sum {
1770            Some(lf::r#type::Sum::Nat(scale)) => u8::try_from(*scale)
1771                .map_err(|_| SkippedType::new(format!("Numeric scale {scale} out of range"))),
1772            _ => Err(SkippedType::new("Numeric scale is not a type-level Nat")),
1773        }
1774    }
1775}
1776
1777// ---- recursion breaking -----------------------------------------------------
1778//
1779// Daml permits arbitrarily recursive data types; Rust requires indirection on
1780// every cycle. `Vec` / `BTreeMap` / `GenMap` heap-allocate and so break cycles,
1781// but `Option<T>` stores `T` **inline** — `Optional` recursion (`data Tree =
1782// Node { left : Optional Tree }`), mutual recursion (`A` ↔ `B`, including
1783// across modules), and recursion through a generic instantiation (`Wrap T`
1784// where `Wrap a` stores `a` inline) all need a `Box`, or the generated crate
1785// fails to compile (E0072). `Box` is transparent to both codecs, so boxing is
1786// always safe; the pass below is therefore deliberately conservative — it may
1787// box an occurrence that a finer analysis could leave bare, but it can never
1788// produce an infinitely-sized type.
1789
1790/// Break every containment cycle in the crate by boxing the reference
1791/// occurrences that close one.
1792///
1793/// A type "inline-contains" the types its fields reach without crossing heap
1794/// indirection: through `Optional`, and through the *arguments* of a named-type
1795/// reference (a generic target may store its parameter inline — assumed
1796/// conservatively). `List` / `TextMap` / `GenMap` / `Boxed` stop containment
1797/// (heap), as does `ContractId` (a phantom-typed id, it does not contain its
1798/// payload). Every reference occurrence whose target can inline-reach back to
1799/// the type that holds the field is wrapped in [`DamlType::Boxed`].
1800fn box_recursion(krate: &mut Crate) {
1801    // Pass 1: the inline-containment graph, node = fully-qualified path key.
1802    let mut edges: HashMap<String, Vec<String>> = HashMap::new();
1803    for package in &krate.packages {
1804        for module in &package.modules {
1805            let at = |name: &str| path_key_for(&package.name, &module.name, name);
1806            module_containment_edges(&module.module, &at, &mut edges);
1807        }
1808    }
1809
1810    // Pass 2: box every reference occurrence that closes a cycle back to the
1811    // type holding it.
1812    for package in &mut krate.packages {
1813        let package_name = package.name.clone();
1814        for module in &mut package.modules {
1815            let module_name = module.name.clone();
1816            let at = |name: &str| path_key_for(&package_name, &module_name, name);
1817            module_box_cycles(&mut module.module, &at, &edges);
1818        }
1819    }
1820}
1821
1822/// Pass 1 for one module: record which reference keys each declared type
1823/// inline-contains. `at` maps a declared type name to its graph key.
1824fn module_containment_edges(
1825    module: &Module,
1826    at: &impl Fn(&str) -> String,
1827    edges: &mut HashMap<String, Vec<String>>,
1828) {
1829    for data_type in &module.data_types {
1830        match data_type {
1831            DataType::Record(record) => {
1832                let node = edges.entry(at(&record.name)).or_default();
1833                for field in &record.fields {
1834                    collect_inline_refs(&field.ty, node);
1835                }
1836            }
1837            DataType::Variant(variant) => {
1838                let node = edges.entry(at(&variant.name)).or_default();
1839                for constructor in &variant.constructors {
1840                    if let Some(payload) = &constructor.payload {
1841                        collect_inline_refs(payload, node);
1842                    }
1843                }
1844            }
1845            DataType::Enum(_) | DataType::InterfaceMarker(_) => {}
1846        }
1847    }
1848    // Template payloads are types too (other types may reference them).
1849    for template in &module.templates {
1850        let node = edges.entry(at(&template.name)).or_default();
1851        for field in &template.fields {
1852            collect_inline_refs(&field.ty, node);
1853        }
1854    }
1855}
1856
1857/// Pass 2 for one module: box every cycle-closing reference occurrence.
1858fn module_box_cycles(
1859    module: &mut Module,
1860    at: &impl Fn(&str) -> String,
1861    edges: &HashMap<String, Vec<String>>,
1862) {
1863    for data_type in &mut module.data_types {
1864        match data_type {
1865            DataType::Record(record) => {
1866                let holder = at(&record.name);
1867                for field in &mut record.fields {
1868                    box_cycle_closers(&mut field.ty, &holder, edges);
1869                }
1870            }
1871            DataType::Variant(variant) => {
1872                let holder = at(&variant.name);
1873                for constructor in &mut variant.constructors {
1874                    if let Some(payload) = &mut constructor.payload {
1875                        box_cycle_closers(payload, &holder, edges);
1876                    }
1877                }
1878            }
1879            DataType::Enum(_) | DataType::InterfaceMarker(_) => {}
1880        }
1881    }
1882    for template in &mut module.templates {
1883        let holder = at(&template.name);
1884        for field in &mut template.fields {
1885            box_cycle_closers(&mut field.ty, &holder, edges);
1886        }
1887    }
1888}
1889
1890/// The graph key of a type declared in (`package`, `module`) — the same shape
1891/// [`Lowering::con_path`] resolves references to, joined.
1892fn path_key_for(package_module: &str, module_name: &str, type_name: &str) -> String {
1893    format!("crate::{package_module}::{module_name}::{type_name}")
1894}
1895
1896/// The graph key of a reference occurrence. Local (unqualified) references have
1897/// a single segment; qualified ones are `crate::pkg::module::Type`.
1898fn path_key_of(reference: &TypeRef) -> String {
1899    reference.path.join("::")
1900}
1901
1902/// Collect into `out` the reference keys `ty` reaches inline (without crossing
1903/// heap indirection).
1904fn collect_inline_refs(ty: &DamlType, out: &mut Vec<String>) {
1905    match ty {
1906        DamlType::Optional(inner) => collect_inline_refs(inner, out),
1907        DamlType::Ref(reference) => {
1908            out.push(path_key_of(reference));
1909            // A generic target may store its arguments inline (conservative).
1910            for arg in &reference.args {
1911                collect_inline_refs(arg, out);
1912            }
1913        }
1914        // Heap containers / phantom ids stop inline containment; the rest of
1915        // the leaf types contain no references.
1916        _ => {}
1917    }
1918}
1919
1920/// True if `from` can reach `to` through the inline-containment graph.
1921fn inline_reaches(edges: &HashMap<String, Vec<String>>, from: &str, to: &str) -> bool {
1922    let mut stack = vec![from];
1923    let mut seen = std::collections::HashSet::new();
1924    while let Some(node) = stack.pop() {
1925        if node == to {
1926            return true;
1927        }
1928        if seen.insert(node)
1929            && let Some(next) = edges.get(node)
1930        {
1931            stack.extend(next.iter().map(String::as_str));
1932        }
1933    }
1934    false
1935}
1936
1937/// Box, in place, every reference occurrence inside `ty` (at an inline
1938/// position) whose target inline-reaches `holder` — i.e. every occurrence that
1939/// closes a containment cycle.
1940fn box_cycle_closers(ty: &mut DamlType, holder: &str, edges: &HashMap<String, Vec<String>>) {
1941    match ty {
1942        DamlType::Optional(inner) => box_cycle_closers(inner, holder, edges),
1943        DamlType::Ref(reference) => {
1944            for arg in &mut reference.args {
1945                box_cycle_closers(arg, holder, edges);
1946            }
1947            let key = path_key_of(reference);
1948            if key == holder || inline_reaches(edges, &key, holder) {
1949                let inner = std::mem::replace(ty, DamlType::Unit);
1950                *ty = DamlType::Boxed(Box::new(inner));
1951            }
1952        }
1953        _ => {}
1954    }
1955}
1956
1957/// The `Type` of a field, or an error if absent.
1958fn field_type(field: &lf::FieldWithType) -> Result<&lf::Type, SkippedType> {
1959    field
1960        .r#type
1961        .as_ref()
1962        .ok_or_else(|| SkippedType::new("field without a type"))
1963}
1964
1965/// The Rust name a lowered declaration will be emitted under.
1966fn data_type_name(data_type: &DataType) -> &str {
1967    match data_type {
1968        DataType::Record(record) => &record.name,
1969        DataType::Variant(variant) => &variant.name,
1970        DataType::Enum(enumeration) => &enumeration.name,
1971        DataType::InterfaceMarker(name) => name,
1972    }
1973}
1974
1975/// Resolve an interned dotted type name into a Rust-usable identifier: the Daml
1976/// qualified name with `.` replaced by `_` (Daml type names are otherwise valid
1977/// Rust identifiers). Definitions and references use this same mapping, so they
1978/// agree. A name that is not a valid Rust identifier (LF permits characters
1979/// like `$` in compiler-internal names) is a [`SkippedType`], not a panic in
1980/// the emitter.
1981fn rust_name(package: &lf::Package, name_interned_dname: i32) -> Result<String, SkippedType> {
1982    let dotted = interned_dotted_name(package, name_interned_dname)
1983        .ok_or_else(|| SkippedType::new("unresolved dotted name"))?;
1984    let name = dotted.replace('.', "_");
1985    if is_rust_ident(&name) {
1986        Ok(name)
1987    } else {
1988        Err(SkippedType::new(format!(
1989            "`{dotted}` is not representable as a Rust identifier"
1990        )))
1991    }
1992}
1993
1994/// Reject a generic whose parameters cannot become distinct Rust generic
1995/// parameters.
1996///
1997/// Every other naming boundary is guarded; this one was not, and
1998/// `emit::type_var_ident` upper-camel-cases (`a` -> `A`), which is not
1999/// injective. Daml `data Pair a A = …` produced `pub struct Pair<A, A>`, which
2000/// parses — so generation reported success and wrote the file — and then failed
2001/// in the user's build with E0403, having silently merged the two parameters.
2002fn check_type_params(name: &str, params: &[String]) -> Result<(), SkippedType> {
2003    let mut seen: std::collections::BTreeMap<String, &str> = std::collections::BTreeMap::new();
2004    for param in params {
2005        let ident = param.to_upper_camel_case();
2006        if !is_rust_ident(&ident) {
2007            return Err(SkippedType::new(format!(
2008                "{name}: type parameter `{param}` is not representable as a Rust \
2009                 generic parameter"
2010            )));
2011        }
2012        if let Some(previous) = seen.insert(ident.clone(), param) {
2013            return Err(SkippedType::new(format!(
2014                "{name}: type parameters `{previous}` and `{param}` both map to the \
2015                 Rust generic parameter `{ident}`; rename one in Daml"
2016            )));
2017        }
2018    }
2019    Ok(())
2020}
2021
2022/// Whether `name` can be emitted as a Rust identifier.
2023///
2024/// Everything handed to the emitter must pass this: `Ident::new` **panics** on
2025/// anything else, and Daml permits names Rust does not — an apostrophe suffix
2026/// (`Red'`) is ordinary Daml and Haskell. A name that fails is skipped with a
2027/// reason, which is the contract this crate documents; aborting the process
2028/// with a `proc_macro2` backtrace is not.
2029fn is_rust_ident(name: &str) -> bool {
2030    !name.is_empty()
2031        && !name.starts_with(|c: char| c.is_ascii_digit())
2032        && name.chars().all(|c| c.is_ascii_alphanumeric() || c == '_')
2033}
2034
2035#[cfg(test)]
2036#[allow(clippy::unwrap_used, clippy::expect_used)]
2037mod tests {
2038    use super::*;
2039    use canton_lf::Dar;
2040
2041    // ---- name flattening (always-on, synthetic LF) -------------------------
2042    //
2043    // Daml names are dotted, Rust identifiers are not. These build the minimal
2044    // LF packages where that difference used to produce silently-wrong output.
2045
2046    /// An LF package with the given interned strings and dotted names, where
2047    /// dotted name `i` is built from the single string `i` unless it contains
2048    /// a `.`, in which case it is split into segments.
2049    fn package_with(names: &[&str]) -> lf::Package {
2050        let mut strings: Vec<String> = Vec::new();
2051        let mut dotted = Vec::new();
2052        for name in names {
2053            let segments = name
2054                .split('.')
2055                .map(|segment| {
2056                    strings.push(segment.to_string());
2057                    i32::try_from(strings.len() - 1).unwrap()
2058                })
2059                .collect();
2060            dotted.push(lf::InternedDottedName {
2061                segments_interned_str: segments,
2062            });
2063        }
2064        lf::Package {
2065            interned_strings: strings,
2066            interned_dotted_names: dotted,
2067            ..lf::Package::default()
2068        }
2069    }
2070
2071    /// A serializable record data type named by dotted-name index `name`, with
2072    /// one `Party` field labelled by interned-string index `label`.
2073    fn record_def(name: i32, label: i32) -> lf::DefDataType {
2074        lf::DefDataType {
2075            name_interned_dname: name,
2076            serializable: true,
2077            data_cons: Some(lf::def_data_type::DataCons::Record(
2078                lf::def_data_type::Fields {
2079                    fields: vec![lf::FieldWithType {
2080                        field_interned_str: label,
2081                        r#type: Some(lf::Type {
2082                            sum: Some(lf::r#type::Sum::Builtin(lf::r#type::Builtin {
2083                                builtin: lf::BuiltinType::Party as i32,
2084                                args: vec![],
2085                            })),
2086                        }),
2087                    }],
2088                },
2089            )),
2090            ..lf::DefDataType::default()
2091        }
2092    }
2093
2094    #[test]
2095    fn two_daml_names_that_flatten_alike_are_skipped_not_silently_merged() {
2096        // `Foo.Bar` (a data type) and `Foo_Bar` (a template) both flatten to the
2097        // Rust name `Foo_Bar`. The payload fold was keyed on the flattened name,
2098        // so the data type's fields silently replaced the template's — a create
2099        // command with the wrong shape, and no compile error anywhere.
2100        // strings: 0=Foo 1=Bar 2=Foo_Bar 3=owner 4=note ; dnames: 0=Foo.Bar 1=Foo_Bar
2101        let mut package = package_with(&["Foo.Bar", "Foo_Bar"]);
2102        package.interned_strings.push("owner".to_string());
2103        package.interned_strings.push("note".to_string());
2104        let (owner, note) = (3, 4);
2105
2106        package.modules.push(lf::Module {
2107            name_interned_dname: 1, // reuse `Foo_Bar` as the module name
2108            data_types: vec![record_def(0, note), record_def(1, owner)],
2109            templates: vec![lf::DefTemplate {
2110                tycon_interned_dname: 1,
2111                ..lf::DefTemplate::default()
2112            }],
2113            ..lf::Module::default()
2114        });
2115
2116        let (krate, skipped) = lower_crate(&[("aa".to_string(), package)]);
2117        let reasons: Vec<String> = skipped.iter().map(ToString::to_string).collect();
2118        assert!(
2119            reasons.iter().any(|r| r.contains("Foo.Bar")
2120                && r.contains("Foo_Bar")
2121                && r.contains("map to the Rust name")),
2122            "the clash must be reported naming both Daml sources: {reasons:?}"
2123        );
2124        // Nothing under that name is emitted — better no bindings than bindings
2125        // that submit the wrong payload.
2126        let emitted: Vec<&str> = krate
2127            .packages
2128            .iter()
2129            .flat_map(|p| &p.modules)
2130            .flat_map(|m| m.module.templates.iter().map(|t| t.name.as_str()))
2131            .collect();
2132        assert!(!emitted.contains(&"Foo_Bar"), "emitted: {emitted:?}");
2133    }
2134
2135    /// A package carrying just the metadata `package_module_names` reads.
2136    fn named_package(name: &str, version: Option<&str>) -> lf::Package {
2137        lf::Package {
2138            interned_strings: vec![name.to_string(), version.unwrap_or_default().to_string()],
2139            metadata: Some(lf::PackageMetadata {
2140                name_interned_str: 0,
2141                version_interned_str: 1,
2142                upgraded_package_id: None,
2143            }),
2144            ..lf::Package::default()
2145        }
2146    }
2147
2148    fn module_names(packages: &[(&str, lf::Package)]) -> Vec<String> {
2149        let owned: Vec<(String, lf::Package)> = packages
2150            .iter()
2151            .map(|(id, package)| ((*id).to_string(), package.clone()))
2152            .collect();
2153        let names = package_module_names(&owned);
2154        // Report in the caller's order, not the map's.
2155        owned
2156            .iter()
2157            .map(|(id, _)| names[id.as_str()].clone())
2158            .collect()
2159    }
2160
2161    #[test]
2162    fn a_package_module_is_named_after_the_package_alone() {
2163        // The DAR's own version is not in the path: bumping the package must not
2164        // rename every type a consumer imports. See docs/scu-regeneration.md.
2165        assert_eq!(
2166            module_names(&[
2167                ("aaa", named_package("quickstart-licensing", Some("0.0.1"))),
2168                ("bbb", named_package("splice-amulet", Some("0.1.14"))),
2169            ]),
2170            ["quickstart_licensing", "splice_amulet"],
2171        );
2172    }
2173
2174    #[test]
2175    fn two_versions_of_one_package_are_separated_by_version() {
2176        // The SCU shape: one DAR bundling v1 and v2 of the same package. Both
2177        // must be reachable, so here — and only here — the version is the
2178        // disambiguator.
2179        assert_eq!(
2180            module_names(&[
2181                ("aaa", named_package("my-app", Some("1.0.0"))),
2182                ("bbb", named_package("my-app", Some("2.0.0"))),
2183                ("ccc", named_package("other", Some("1.0.0"))),
2184            ]),
2185            ["my_app_1_0_0", "my_app_2_0_0", "other"],
2186        );
2187    }
2188
2189    #[test]
2190    fn packages_alike_down_to_the_version_fall_back_to_the_id() {
2191        // A rebuilt package keeps its metadata but gets a new id; and several
2192        // real packages (daml-prim, …) carry `version: ""`, which would collapse
2193        // them all onto one name if it were used as a suffix.
2194        assert_eq!(
2195            module_names(&[
2196                ("aaaaaaaaaaaa", named_package("my-app", Some("1.0.0"))),
2197                ("bbbbbbbbbbbb", named_package("my-app", Some("1.0.0"))),
2198                ("cccccccccccc", named_package("no-version", Some(""))),
2199                ("dddddddddddd", named_package("no-version", None)),
2200                ("eeeeeeeeeeee", lf::Package::default()),
2201            ]),
2202            [
2203                "my_app_1_0_0_aaaaaaaa",
2204                "my_app_1_0_0_bbbbbbbb",
2205                "no_version_cccccccc",
2206                "no_version_dddddddd",
2207                "p_eeeeeeee",
2208            ],
2209        );
2210    }
2211
2212    #[test]
2213    fn ids_sharing_a_prefix_still_get_distinct_module_names() {
2214        // Two package ids agreeing on their first 8 identifier-safe characters
2215        // would have collided after the last disambiguation step, and the crate
2216        // would have failed to compile with E0428 instead of naming the cause.
2217        let names = module_names(&[
2218            ("aaaaaaaa1111", named_package("my-app", Some("1.0.0"))),
2219            ("aaaaaaaa2222", named_package("my-app", Some("1.0.0"))),
2220        ]);
2221        assert_eq!(names.len(), 2);
2222        assert_ne!(names[0], names[1], "{names:?}");
2223        assert!(
2224            names.iter().all(|n| n.starts_with("my_app_1_0_0_")),
2225            "{names:?}"
2226        );
2227    }
2228
2229    #[test]
2230    fn a_non_serializable_claimant_does_not_take_a_real_type_down() {
2231        // The Rust-name clash was computed over the raw LF, before lowering
2232        // decides what is emitted. A non-serializable data type is dropped, so
2233        // counting its flattened name refused the serializable type it "clashed"
2234        // with — and, through the dangling-reference pass, everything using it.
2235        let mut package = package_with(&["Mod", "Handler.OnEvent", "Handler_OnEvent"]);
2236        package.interned_strings.push("owner".to_string());
2237        let owner = i32::try_from(package.interned_strings.len() - 1).unwrap();
2238        let mut ghost = record_def(1, owner);
2239        ghost.serializable = false;
2240        package.modules.push(lf::Module {
2241            name_interned_dname: 0,
2242            data_types: vec![ghost, record_def(2, owner)],
2243            ..lf::Module::default()
2244        });
2245
2246        let (krate, skipped) = lower_crate(&[("aa".to_string(), package)]);
2247        assert!(skipped.is_empty(), "nothing collides: {skipped:?}");
2248        let emitted: Vec<&str> = krate.packages[0].modules[0]
2249            .module
2250            .data_types
2251            .iter()
2252            .map(data_type_name)
2253            .collect();
2254        assert_eq!(emitted, ["Handler_OnEvent"]);
2255    }
2256
2257    #[test]
2258    fn a_module_that_emits_nothing_does_not_block_one_that_does() {
2259        // Same shape one level up: a Daml module of pure functions lowers to no
2260        // items and is never pushed as a `pub mod`, so it cannot collide — but
2261        // it was counted, and both modules were refused.
2262        let mut package = package_with(&["Util.Text", "Util_Text"]);
2263        package.interned_strings.push("owner".to_string());
2264        let owner = i32::try_from(package.interned_strings.len() - 1).unwrap();
2265        package.modules.push(lf::Module {
2266            name_interned_dname: 0, // Util.Text — functions only
2267            ..lf::Module::default()
2268        });
2269        package.modules.push(lf::Module {
2270            name_interned_dname: 1, // Util_Text — a real record
2271            data_types: vec![record_def(1, owner)],
2272            ..lf::Module::default()
2273        });
2274
2275        let (krate, skipped) = lower_crate(&[("aa".to_string(), package)]);
2276        assert!(skipped.is_empty(), "nothing collides: {skipped:?}");
2277        let modules: Vec<&str> = krate.packages[0]
2278            .modules
2279            .iter()
2280            .map(|m| m.name.as_str())
2281            .collect();
2282        assert_eq!(modules, ["Util_Text"]);
2283    }
2284
2285    #[test]
2286    fn two_type_parameters_that_upper_camel_alike_are_skipped() {
2287        // `emit::type_var_ident` upper-camel-cases, which is not injective:
2288        // `data Pair a A` emitted `pub struct Pair<A, A>`. That parses, so
2289        // generation reported success and the user's build failed with E0403.
2290        let mut package = package_with(&["Mod", "Pair"]);
2291        let base = i32::try_from(package.interned_strings.len()).unwrap();
2292        package.interned_strings.push("a".to_string());
2293        package.interned_strings.push("A".to_string());
2294        let mut pair = record_def(1, base);
2295        pair.params = vec![
2296            lf::TypeVarWithKind {
2297                var_interned_str: base,
2298                kind: None,
2299            },
2300            lf::TypeVarWithKind {
2301                var_interned_str: base + 1,
2302                kind: None,
2303            },
2304        ];
2305        package.modules.push(lf::Module {
2306            name_interned_dname: 0,
2307            data_types: vec![pair],
2308            ..lf::Module::default()
2309        });
2310
2311        let (krate, skipped) = lower_crate(&[("aa".to_string(), package)]);
2312        assert!(
2313            skipped
2314                .iter()
2315                .any(|s| s.to_string().contains("Rust generic parameter `A`")),
2316            "{skipped:?}"
2317        );
2318        assert!(krate.packages.is_empty(), "nothing emitted");
2319    }
2320
2321    #[test]
2322    fn the_same_package_id_twice_yields_one_module() {
2323        // The name map is keyed by id, so it cannot see a duplicate in the input
2324        // slice: both entries got the same name and the crate failed with E0428.
2325        let mut package = package_with(&["Mod", "T"]);
2326        package.interned_strings.push("owner".to_string());
2327        let owner = i32::try_from(package.interned_strings.len() - 1).unwrap();
2328        package.modules.push(lf::Module {
2329            name_interned_dname: 0,
2330            data_types: vec![record_def(1, owner)],
2331            ..lf::Module::default()
2332        });
2333
2334        let (krate, _) = lower_crate(&[
2335            ("aa".to_string(), package.clone()),
2336            ("aa".to_string(), package),
2337        ]);
2338        let names: Vec<&str> = krate.packages.iter().map(|p| p.name.as_str()).collect();
2339        assert_eq!(names.len(), 1, "{names:?}");
2340    }
2341
2342    #[test]
2343    fn two_module_names_that_flatten_alike_are_skipped() {
2344        // `A.B` and `A_B` would both become `pub mod A_B` — E0428 in the crate
2345        // we hand the user, reported as a successful generation.
2346        let mut package = package_with(&["A.B", "A_B", "T"]);
2347        package.interned_strings.push("owner".to_string());
2348        let owner = i32::try_from(package.interned_strings.len() - 1).unwrap();
2349        for name in [0, 1] {
2350            package.modules.push(lf::Module {
2351                name_interned_dname: name,
2352                data_types: vec![record_def(2, owner)],
2353                ..lf::Module::default()
2354            });
2355        }
2356
2357        let (krate, skipped) = lower_crate(&[("aa".to_string(), package)]);
2358        assert!(
2359            skipped
2360                .iter()
2361                .any(|s| s.to_string().contains("map to the Rust module")),
2362            "{skipped:?}"
2363        );
2364        let modules: Vec<&str> = krate
2365            .packages
2366            .iter()
2367            .flat_map(|p| p.modules.iter().map(|m| m.name.as_str()))
2368            .collect();
2369        assert!(modules.is_empty(), "no module may be emitted: {modules:?}");
2370    }
2371
2372    #[test]
2373    fn an_optional_passed_to_a_generic_that_nests_it_is_refused_not_mis_encoded() {
2374        // `Wrap a = { value : Optional a }` puts its parameter under an
2375        // Optional, so `Wrap (Optional Text)` is a nested optional on the wire
2376        // and needs the LF-JSON list form — which the structural mapping cannot
2377        // see through a type parameter. Refuse it rather than emit
2378        // `Option<Option<String>>`, which serializes the inner layer as `null`.
2379        let reference = |name: &str, args: Vec<DamlType>| {
2380            DamlType::Ref(TypeRef {
2381                path: vec![
2382                    "crate".to_string(),
2383                    "pkg_1_0_0".to_string(),
2384                    "Mod".to_string(),
2385                    name.to_string(),
2386                ],
2387                args,
2388            })
2389        };
2390        let field = |label: &str, ty: DamlType| Field {
2391            label: label.to_string(),
2392            ty,
2393        };
2394        let module = |data_types: Vec<DataType>| Crate {
2395            packages: vec![PackageModule {
2396                name: "pkg_1_0_0".to_string(),
2397                modules: vec![NamedModule {
2398                    name: "Mod".to_string(),
2399                    module: Module {
2400                        data_types,
2401                        ..Module::default()
2402                    },
2403                }],
2404            }],
2405        };
2406
2407        // The generic that nests its parameter, plus a user of it.
2408        let wrap_nesting = DataType::Record(Record {
2409            name: "Wrap".to_string(),
2410            type_params: vec!["a".to_string()],
2411            fields: vec![field(
2412                "value",
2413                DamlType::Optional(Box::new(DamlType::Var("a".to_string()))),
2414            )],
2415        });
2416        let user = |arg: DamlType| {
2417            DataType::Record(Record {
2418                name: "User".to_string(),
2419                type_params: vec![],
2420                fields: vec![field("w", reference("Wrap", vec![arg]))],
2421            })
2422        };
2423
2424        let mut krate = module(vec![
2425            wrap_nesting.clone(),
2426            user(DamlType::Optional(Box::new(DamlType::Text))),
2427        ]);
2428        let mut errors = Vec::new();
2429        drop_ambiguous_nested_optionals(&mut krate, &mut errors);
2430        assert!(
2431            errors
2432                .iter()
2433                .any(|e| e.to_string().contains("not yet representable")),
2434            "{errors:?}"
2435        );
2436        let names: Vec<&str> = krate.packages[0].modules[0]
2437            .module
2438            .data_types
2439            .iter()
2440            .map(data_type_name)
2441            .collect();
2442        assert_eq!(names, ["Wrap"], "only the ambiguous user is dropped");
2443
2444        // A non-Optional argument is unaffected…
2445        let mut fine = module(vec![wrap_nesting, user(DamlType::Text)]);
2446        let mut errors = Vec::new();
2447        drop_ambiguous_nested_optionals(&mut fine, &mut errors);
2448        assert!(errors.is_empty(), "{errors:?}");
2449
2450        // …and so is an Optional argument to a generic that does *not* nest it
2451        // (there the top-level `null`/value form is the correct one).
2452        let wrap_plain = DataType::Record(Record {
2453            name: "Wrap".to_string(),
2454            type_params: vec!["a".to_string()],
2455            fields: vec![field("value", DamlType::Var("a".to_string()))],
2456        });
2457        let mut plain = module(vec![
2458            wrap_plain,
2459            user(DamlType::Optional(Box::new(DamlType::Text))),
2460        ]);
2461        let mut errors = Vec::new();
2462        drop_ambiguous_nested_optionals(&mut plain, &mut errors);
2463        assert!(errors.is_empty(), "{errors:?}");
2464        assert_eq!(plain.packages[0].modules[0].module.data_types.len(), 2);
2465    }
2466
2467    #[test]
2468    fn nesting_introduced_through_an_intermediate_generic_is_still_refused() {
2469        // `Outer` has no `Optional` of its own, so a one-level analysis clears
2470        // it — but `Outer (Optional Text)` reaches `Wrap`'s `Optional a` all the
2471        // same, and the emitted `Option<Option<String>>` would put `null` on the
2472        // wire where the ledger expects `[]`.
2473        //
2474        //   Wrap a  = { value : Optional a }
2475        //   Outer a = { inner : Wrap a }
2476        //   User    = { o : Outer (Optional Text) }
2477        let mut krate = generics_module(vec![
2478            generic_record("Wrap", &["a"], vec![("value", optional(var("a")))]),
2479            generic_record(
2480                "Outer",
2481                &["a"],
2482                vec![("inner", reference("Wrap", vec![var("a")]))],
2483            ),
2484            generic_record(
2485                "User",
2486                &[],
2487                vec![("o", reference("Outer", vec![optional(DamlType::Text)]))],
2488            ),
2489        ]);
2490        let mut errors = Vec::new();
2491        drop_ambiguous_nested_optionals(&mut krate, &mut errors);
2492        assert!(
2493            errors
2494                .iter()
2495                .any(|e| e.to_string().contains("not yet representable")),
2496            "the transitive nesting must be refused, not mis-encoded: {errors:?}"
2497        );
2498        let names: Vec<&str> = krate.packages[0].modules[0]
2499            .module
2500            .data_types
2501            .iter()
2502            .map(data_type_name)
2503            .collect();
2504        assert_eq!(
2505            names,
2506            ["Wrap", "Outer"],
2507            "only the instantiation site is dropped"
2508        );
2509    }
2510
2511    #[test]
2512    fn mutually_recursive_generics_reach_a_fixpoint() {
2513        // Neither declaration alone shows the nesting, and each depends on the
2514        // other, so a worklist that visits declarations once in either order
2515        // gets a different answer than the one that runs to fixpoint. It must
2516        // also terminate.
2517        //
2518        //   Ping a = { p : Optional a }
2519        //   Pong a = { q : Ping a, r : Optional (Pong a) }
2520        let mut krate = generics_module(vec![
2521            generic_record("Ping", &["a"], vec![("p", optional(var("a")))]),
2522            generic_record(
2523                "Pong",
2524                &["a"],
2525                vec![
2526                    ("q", reference("Ping", vec![var("a")])),
2527                    ("r", optional(reference("Pong", vec![var("a")]))),
2528                ],
2529            ),
2530            generic_record(
2531                "User",
2532                &[],
2533                vec![("o", reference("Pong", vec![optional(DamlType::Text)]))],
2534            ),
2535        ]);
2536        let mut errors = Vec::new();
2537        drop_ambiguous_nested_optionals(&mut krate, &mut errors);
2538        assert!(
2539            errors
2540                .iter()
2541                .any(|e| e.to_string().contains("not yet representable")),
2542            "{errors:?}"
2543        );
2544    }
2545
2546    #[test]
2547    fn a_container_between_the_two_optionals_is_not_a_nested_optional() {
2548        // `Optional [a]` at `a = Optional Text` is `Optional (List (Optional
2549        // Text))`. The list form exists to tell an inner `None` from an outer
2550        // one; a list element boundary already does that, so the inner Optional
2551        // keeps the plain `null`/value form and `Option<Vec<Option<String>>>`
2552        // is correct. Refusing it would reject code the emitter handles — and
2553        // `map.rs` agrees, since `nested_optional_inner` stops at the container
2554        // too.
2555        for container in [
2556            DamlType::List(Box::new(var("a"))),
2557            DamlType::TextMap(Box::new(var("a"))),
2558            DamlType::GenMap(Box::new(DamlType::Text), Box::new(var("a"))),
2559        ] {
2560            let mut krate = generics_module(vec![
2561                generic_record("Wrap", &["a"], vec![("value", optional(container))]),
2562                generic_record(
2563                    "User",
2564                    &[],
2565                    vec![("w", reference("Wrap", vec![optional(DamlType::Text)]))],
2566                ),
2567            ]);
2568            let mut errors = Vec::new();
2569            drop_ambiguous_nested_optionals(&mut krate, &mut errors);
2570            assert!(errors.is_empty(), "must not be refused: {errors:?}");
2571            assert_eq!(krate.packages[0].modules[0].module.data_types.len(), 2);
2572        }
2573    }
2574
2575    #[test]
2576    fn dropping_an_interface_keeps_its_marker_reachable() {
2577        // An interface and its marker `struct` share a path key. When the
2578        // interface goes (its view type was unrepresentable) the marker stays
2579        // and is still emitted, so a template holding a `ContractId<Iface>`
2580        // must survive too — dropping it would blame a type the crate emits.
2581        let mut krate = Crate {
2582            packages: vec![PackageModule {
2583                name: "pkg".to_string(),
2584                modules: vec![NamedModule {
2585                    name: "Mod".to_string(),
2586                    module: Module {
2587                        data_types: vec![DataType::InterfaceMarker("Holding".to_string())],
2588                        templates: vec![Template {
2589                            name: "Vault".to_string(),
2590                            module_name: "Mod".to_string(),
2591                            package_id: "id".to_string(),
2592                            package_name: "pkg".to_string(),
2593                            fields: vec![Field {
2594                                label: "held".to_string(),
2595                                ty: DamlType::ContractId(Box::new(reference("Holding", vec![]))),
2596                            }],
2597                            choices: vec![],
2598                            key: None,
2599                        }],
2600                        interfaces: vec![Interface {
2601                            name: "Holding".to_string(),
2602                            module_name: "Mod".to_string(),
2603                            package_id: "id".to_string(),
2604                            package_name: "pkg".to_string(),
2605                            // The view type was skipped by an earlier pass.
2606                            view: Some(reference("HoldingView", vec![])),
2607                            choices: vec![],
2608                        }],
2609                    },
2610                }],
2611            }],
2612        };
2613
2614        let mut errors = Vec::new();
2615        drop_dangling_references(&mut krate, &mut errors);
2616
2617        let module = &krate.packages[0].modules[0].module;
2618        assert!(module.interfaces.is_empty(), "the interface itself must go");
2619        assert_eq!(
2620            module.data_types.len(),
2621            1,
2622            "the marker is still emitted: {:?}",
2623            module
2624                .data_types
2625                .iter()
2626                .map(data_type_name)
2627                .collect::<Vec<_>>()
2628        );
2629        let templates: Vec<&str> = module.templates.iter().map(|t| t.name.as_str()).collect();
2630        assert_eq!(
2631            templates,
2632            ["Vault"],
2633            "a ContractId of the marker still resolves; reasons: {errors:?}"
2634        );
2635    }
2636
2637    #[test]
2638    fn a_contract_id_type_argument_is_not_a_value_position() {
2639        // `ContractId<T>` is a tagged string: the tag never reaches either
2640        // wire, so nothing under a `ContractId` can be a nested optional.
2641        // Refusing here would drop a declaration that encodes perfectly.
2642        let mut krate = generics_module(vec![
2643            generic_record("Wrap", &["a"], vec![("value", optional(var("a")))]),
2644            generic_record(
2645                "Outer",
2646                &["a"],
2647                vec![(
2648                    "c",
2649                    DamlType::ContractId(Box::new(reference("Wrap", vec![var("a")]))),
2650                )],
2651            ),
2652            generic_record(
2653                "User",
2654                &[],
2655                vec![("o", reference("Outer", vec![optional(DamlType::Text)]))],
2656            ),
2657        ]);
2658        let mut errors = Vec::new();
2659        drop_ambiguous_nested_optionals(&mut krate, &mut errors);
2660        assert!(errors.is_empty(), "must not be refused: {errors:?}");
2661        assert_eq!(krate.packages[0].modules[0].module.data_types.len(), 3);
2662    }
2663
2664    #[test]
2665    fn a_parameter_handed_on_inside_a_container_does_not_propagate() {
2666        // `Outer a = { inner : Wrap [a] }` instantiates `Wrap`'s parameter at
2667        // `[a]`, not at `a`, so `Wrap`'s `Optional b` becomes `Optional [a]` —
2668        // a container between the two Optionals again. Propagating the flag
2669        // through the container would be a false refusal.
2670        let mut krate = generics_module(vec![
2671            generic_record("Wrap", &["b"], vec![("value", optional(var("b")))]),
2672            generic_record(
2673                "Outer",
2674                &["a"],
2675                vec![(
2676                    "inner",
2677                    reference("Wrap", vec![DamlType::List(Box::new(var("a")))]),
2678                )],
2679            ),
2680            generic_record(
2681                "User",
2682                &[],
2683                vec![("o", reference("Outer", vec![optional(DamlType::Text)]))],
2684            ),
2685        ]);
2686        let mut errors = Vec::new();
2687        drop_ambiguous_nested_optionals(&mut krate, &mut errors);
2688        assert!(errors.is_empty(), "must not be refused: {errors:?}");
2689    }
2690
2691    // Builders for the nested-Optional tests above.
2692    fn var(name: &str) -> DamlType {
2693        DamlType::Var(name.to_string())
2694    }
2695
2696    fn optional(ty: DamlType) -> DamlType {
2697        DamlType::Optional(Box::new(ty))
2698    }
2699
2700    fn reference(name: &str, args: Vec<DamlType>) -> DamlType {
2701        DamlType::Ref(TypeRef {
2702            path: vec![
2703                "crate".to_string(),
2704                "pkg".to_string(),
2705                "Mod".to_string(),
2706                name.to_string(),
2707            ],
2708            args,
2709        })
2710    }
2711
2712    fn generic_record(name: &str, params: &[&str], fields: Vec<(&str, DamlType)>) -> DataType {
2713        DataType::Record(Record {
2714            name: name.to_string(),
2715            type_params: params.iter().map(|p| (*p).to_string()).collect(),
2716            fields: fields
2717                .into_iter()
2718                .map(|(label, ty)| Field {
2719                    label: label.to_string(),
2720                    ty,
2721                })
2722                .collect(),
2723        })
2724    }
2725
2726    fn generics_module(data_types: Vec<DataType>) -> Crate {
2727        Crate {
2728            packages: vec![PackageModule {
2729                name: "pkg".to_string(),
2730                modules: vec![NamedModule {
2731                    name: "Mod".to_string(),
2732                    module: Module {
2733                        data_types,
2734                        ..Module::default()
2735                    },
2736                }],
2737            }],
2738        }
2739    }
2740
2741    /// The two post-lowering passes each walk every declaration kind. They were
2742    /// written one kind at a time and both missed one — the dangling-reference
2743    /// pass judged interfaces only by their marker, and the nested-Optional
2744    /// pass never looked at templates, whose payload is folded into
2745    /// `Template.fields` and so never appears in `data_types` at all.
2746    #[test]
2747    fn both_post_lowering_passes_cover_templates_and_interfaces() {
2748        let reference = |name: &str, args: Vec<DamlType>| {
2749            DamlType::Ref(TypeRef {
2750                path: vec![
2751                    "crate".to_string(),
2752                    "pkg_1_0_0".to_string(),
2753                    "Mod".to_string(),
2754                    name.to_string(),
2755                ],
2756                args,
2757            })
2758        };
2759        let module_of = |data_types: Vec<DataType>,
2760                         templates: Vec<Template>,
2761                         interfaces: Vec<Interface>| Crate {
2762            packages: vec![PackageModule {
2763                name: "pkg_1_0_0".to_string(),
2764                modules: vec![NamedModule {
2765                    name: "Mod".to_string(),
2766                    module: Module {
2767                        data_types,
2768                        templates,
2769                        interfaces,
2770                    },
2771                }],
2772            }],
2773        };
2774        let choice = |ty: DamlType| Choice {
2775            name: "Do".to_string(),
2776            consuming: true,
2777            argument: DamlType::Unit,
2778            returns: ty,
2779        };
2780        let template_of = |name: &str, field: DamlType| Template {
2781            name: name.to_string(),
2782            module_name: "Mod".to_string(),
2783            package_id: "aa".to_string(),
2784            package_name: "pkg".to_string(),
2785            fields: vec![Field {
2786                label: "x".to_string(),
2787                ty: field,
2788            }],
2789            choices: vec![],
2790            key: None,
2791        };
2792
2793        // (1) An interface whose choice returns a skipped type used to survive,
2794        // emitting `type Return = crate::…::Bad` and failing the user's build
2795        // with E0425 — the very error the pass exists to prevent.
2796        let mut krate = module_of(
2797            vec![DataType::InterfaceMarker("Iface".to_string())],
2798            vec![],
2799            vec![Interface {
2800                name: "Iface".to_string(),
2801                module_name: "Mod".to_string(),
2802                package_id: "aa".to_string(),
2803                package_name: "pkg".to_string(),
2804                view: None,
2805                choices: vec![choice(reference("Bad", vec![]))], // `Bad` was skipped
2806            }],
2807        );
2808        let mut errors = Vec::new();
2809        drop_dangling_references(&mut krate, &mut errors);
2810        assert!(
2811            errors
2812                .iter()
2813                .any(|e| e.to_string().contains("`Iface` references `Bad`")),
2814            "an interface dangles like anything else: {errors:?}"
2815        );
2816        assert!(
2817            krate.packages.is_empty() || krate.packages[0].modules[0].module.interfaces.is_empty(),
2818            "the interface must not be emitted"
2819        );
2820
2821        // (2) The same ambiguous instantiation inside a *template payload* used
2822        // to be emitted, serialising the inner Optional as `null` where LF-JSON
2823        // requires `[]`.
2824        let wrap = DataType::Record(Record {
2825            name: "Wrap".to_string(),
2826            type_params: vec!["a".to_string()],
2827            fields: vec![Field {
2828                label: "value".to_string(),
2829                ty: DamlType::Optional(Box::new(DamlType::Var("a".to_string()))),
2830            }],
2831        });
2832        let mut krate = module_of(
2833            vec![wrap],
2834            vec![template_of(
2835                "Tpl",
2836                reference("Wrap", vec![DamlType::Optional(Box::new(DamlType::Text))]),
2837            )],
2838            vec![],
2839        );
2840        let mut errors = Vec::new();
2841        drop_ambiguous_nested_optionals(&mut krate, &mut errors);
2842        assert!(
2843            errors
2844                .iter()
2845                .any(|e| e.to_string().contains("not yet representable")),
2846            "a template payload is an instantiation site too: {errors:?}"
2847        );
2848        assert!(
2849            krate.packages[0].modules[0].module.templates.is_empty(),
2850            "the template must not be emitted"
2851        );
2852    }
2853
2854    #[test]
2855    fn a_type_referencing_a_skipped_type_is_dropped_transitively() {
2856        // `Bad` is unrepresentable and skipped; `Good` has a field of type
2857        // `Bad`, and `Worse` a field of type `Good`. Emitting either leaves
2858        // `crate::…::Bad` dangling and the user's crate fails with E0425, with
2859        // nothing tying that to the warning — so both must go too.
2860        let path = |name: &str| {
2861            DamlType::Ref(TypeRef {
2862                path: vec![
2863                    "crate".to_string(),
2864                    "pkg_1_0_0".to_string(),
2865                    "Mod".to_string(),
2866                    name.to_string(),
2867                ],
2868                args: vec![],
2869            })
2870        };
2871        let record = |name: &str, field: DamlType| {
2872            DataType::Record(Record {
2873                name: name.to_string(),
2874                type_params: vec![],
2875                fields: vec![Field {
2876                    label: "x".to_string(),
2877                    ty: field,
2878                }],
2879            })
2880        };
2881        let mut krate = Crate {
2882            packages: vec![PackageModule {
2883                name: "pkg_1_0_0".to_string(),
2884                modules: vec![NamedModule {
2885                    name: "Mod".to_string(),
2886                    module: Module {
2887                        // `Bad` is absent — as if lowering had skipped it.
2888                        data_types: vec![
2889                            record("Good", path("Bad")),
2890                            record("Worse", path("Good")),
2891                            record("Fine", DamlType::Text),
2892                        ],
2893                        ..Module::default()
2894                    },
2895                }],
2896            }],
2897        };
2898
2899        let mut errors = Vec::new();
2900        drop_dangling_references(&mut krate, &mut errors);
2901
2902        let surviving: Vec<&str> = krate.packages[0].modules[0]
2903            .module
2904            .data_types
2905            .iter()
2906            .map(data_type_name)
2907            .collect();
2908        assert_eq!(surviving, ["Fine"], "only the self-contained type survives");
2909
2910        let reasons: Vec<String> = errors.iter().map(ToString::to_string).collect();
2911        assert!(
2912            reasons
2913                .iter()
2914                .any(|r| r.contains("`Good` references `Bad`")),
2915            "{reasons:?}"
2916        );
2917        assert!(
2918            reasons
2919                .iter()
2920                .any(|r| r.contains("`Worse` references `Good`")),
2921            "the drop must cascade: {reasons:?}"
2922        );
2923    }
2924
2925    #[test]
2926    fn a_daml_name_rust_cannot_spell_is_skipped_not_panicked_on() {
2927        // `Red'` is ordinary Daml and Haskell. Only *type* names were checked,
2928        // so a module or constructor carrying an apostrophe reached
2929        // `Ident::new` and aborted the CLI with a proc_macro2 panic instead of
2930        // the skip-with-a-reason this crate promises.
2931        assert!(is_rust_ident("Red"));
2932        assert!(is_rust_ident("_x9"));
2933        for bad in ["Red'", "", "9lives", "naïve", "a-b", "A.B"] {
2934            assert!(!is_rust_ident(bad), "{bad:?} is not a Rust identifier");
2935        }
2936
2937        // An enum constructor Rust cannot spell skips its declaration.
2938        // strings: 0=Colour 1=Red' ; dnames: 0=Colour
2939        let mut package = package_with(&["Colour"]);
2940        package.interned_strings.push("Red'".to_string());
2941        let constructor = i32::try_from(package.interned_strings.len() - 1).unwrap();
2942        package.modules.push(lf::Module {
2943            name_interned_dname: 0,
2944            data_types: vec![lf::DefDataType {
2945                name_interned_dname: 0,
2946                serializable: true,
2947                data_cons: Some(lf::def_data_type::DataCons::Enum(
2948                    lf::def_data_type::EnumConstructors {
2949                        constructors_interned_str: vec![constructor],
2950                    },
2951                )),
2952                ..lf::DefDataType::default()
2953            }],
2954            ..lf::Module::default()
2955        });
2956
2957        let (krate, skipped) = lower_crate(&[("aa".to_string(), package)]);
2958        assert!(
2959            skipped
2960                .iter()
2961                .any(|s| s.to_string().contains("enum constructor `Red'`")),
2962            "{skipped:?}"
2963        );
2964        assert!(krate.packages.is_empty(), "nothing may be emitted");
2965    }
2966
2967    #[test]
2968    fn the_collision_suffix_is_never_empty() {
2969        // Two metadata-less packages whose ids share no ASCII alphanumerics both
2970        // derived `p_`, and the de-collision suffix was empty — two `pub mod p__`.
2971        assert_ne!(ident_prefix("€€€€"), ident_prefix("£££££"));
2972        assert!(!ident_prefix("€€€€").is_empty());
2973        assert_eq!(ident_prefix("aabbccddee"), "aabbccdd");
2974    }
2975
2976    // ---- recursion breaking (always-on, pure IR) ---------------------------
2977
2978    fn qualified(name: &str) -> DamlType {
2979        DamlType::Ref(TypeRef {
2980            path: vec![
2981                "crate".to_string(),
2982                "pkg_1_0_0".to_string(),
2983                "Mod".to_string(),
2984                name.to_string(),
2985            ],
2986            args: vec![],
2987        })
2988    }
2989
2990    fn record(name: &str, fields: Vec<(&str, DamlType)>) -> DataType {
2991        DataType::Record(Record {
2992            name: name.to_string(),
2993            type_params: vec![],
2994            fields: fields
2995                .into_iter()
2996                .map(|(label, ty)| Field {
2997                    label: label.to_string(),
2998                    ty,
2999                })
3000                .collect(),
3001        })
3002    }
3003
3004    fn crate_of(data_types: Vec<DataType>) -> Crate {
3005        Crate {
3006            packages: vec![PackageModule {
3007                name: "pkg_1_0_0".to_string(),
3008                modules: vec![NamedModule {
3009                    name: "Mod".to_string(),
3010                    module: Module {
3011                        data_types,
3012                        ..Module::default()
3013                    },
3014                }],
3015            }],
3016        }
3017    }
3018
3019    fn field_ty(krate: &Crate, type_index: usize, field_index: usize) -> &DamlType {
3020        match &krate.packages[0].modules[0].module.data_types[type_index] {
3021            DataType::Record(record) => &record.fields[field_index].ty,
3022            other => panic!("expected a record, got {other:?}"),
3023        }
3024    }
3025
3026    #[test]
3027    fn optional_self_recursion_is_boxed() {
3028        // data Tree = Node { left : Optional Tree } — `Option<Tree>` stores the
3029        // payload inline, so without a Box the generated struct is E0072.
3030        let mut krate = crate_of(vec![record(
3031            "Tree",
3032            vec![
3033                ("left", DamlType::Optional(Box::new(qualified("Tree")))),
3034                ("size", DamlType::Int64),
3035            ],
3036        )]);
3037        box_recursion(&mut krate);
3038        assert_eq!(
3039            field_ty(&krate, 0, 0),
3040            &DamlType::Optional(Box::new(DamlType::Boxed(Box::new(qualified("Tree"))))),
3041        );
3042        // Non-recursive fields are untouched.
3043        assert_eq!(field_ty(&krate, 0, 1), &DamlType::Int64);
3044    }
3045
3046    #[test]
3047    fn mutual_recursion_is_boxed() {
3048        // data A = A { b : B }; data B = B { a : Optional A } — the cycle is
3049        // A → B → A; every occurrence that closes it must be boxed.
3050        let mut krate = crate_of(vec![
3051            record("A", vec![("b", qualified("B"))]),
3052            record(
3053                "B",
3054                vec![("a", DamlType::Optional(Box::new(qualified("A"))))],
3055            ),
3056        ]);
3057        box_recursion(&mut krate);
3058        assert_eq!(
3059            field_ty(&krate, 0, 0),
3060            &DamlType::Boxed(Box::new(qualified("B")))
3061        );
3062        assert_eq!(
3063            field_ty(&krate, 1, 0),
3064            &DamlType::Optional(Box::new(DamlType::Boxed(Box::new(qualified("A"))))),
3065        );
3066    }
3067
3068    #[test]
3069    fn generic_instantiation_recursion_is_boxed() {
3070        // data T = T { w : Wrap T } — Wrap may store its parameter inline, so
3071        // the argument occurrence is (conservatively) boxed: Wrap<Box<T>>.
3072        let wrap_of_t = DamlType::Ref(TypeRef {
3073            path: vec![
3074                "crate".to_string(),
3075                "pkg_1_0_0".to_string(),
3076                "Mod".to_string(),
3077                "Wrap".to_string(),
3078            ],
3079            args: vec![qualified("T")],
3080        });
3081        let mut krate = crate_of(vec![
3082            DataType::Record(Record {
3083                name: "Wrap".to_string(),
3084                type_params: vec!["a".to_string()],
3085                fields: vec![Field {
3086                    label: "w".to_string(),
3087                    ty: DamlType::Var("a".to_string()),
3088                }],
3089            }),
3090            record("T", vec![("w", wrap_of_t)]),
3091        ]);
3092        box_recursion(&mut krate);
3093        let DamlType::Ref(reference) = field_ty(&krate, 1, 0) else {
3094            panic!("outer Wrap reference must stay a Ref");
3095        };
3096        assert_eq!(
3097            reference.args[0],
3098            DamlType::Boxed(Box::new(qualified("T"))),
3099            "the recursive argument occurrence is boxed"
3100        );
3101    }
3102
3103    #[test]
3104    fn heap_containers_already_break_cycles() {
3105        // Vec/TextMap/GenMap heap-allocate; recursion through them needs no Box.
3106        let mut krate = crate_of(vec![record(
3107            "Tree",
3108            vec![
3109                ("children", DamlType::List(Box::new(qualified("Tree")))),
3110                ("index", DamlType::TextMap(Box::new(qualified("Tree")))),
3111            ],
3112        )]);
3113        let before = krate.clone();
3114        box_recursion(&mut krate);
3115        assert_eq!(krate, before, "heap-indirected recursion is left alone");
3116    }
3117
3118    #[test]
3119    fn lowers_a_real_dar_to_a_qualified_crate() {
3120        // Env-gated: point at a real .dar (e.g. one from cn-quickstart).
3121        let Ok(path) = std::env::var("CANTON_TEST_DAR") else {
3122            eprintln!("skipping lower test: set CANTON_TEST_DAR=/path/to/x.dar");
3123            return;
3124        };
3125
3126        let dar = Dar::open(&path).expect("open DAR");
3127        let (krate, errors) = lower_dar(&dar).expect("decode + lower DAR");
3128
3129        assert!(
3130            !krate.packages.is_empty(),
3131            "a DAR should lower at least one package"
3132        );
3133
3134        // Every reference must resolve to a package present in the DAR closure —
3135        // if the imported-package-id interning were wrong, this would surface as
3136        // a "reference to package … not in the DAR" error.
3137        let unresolved: Vec<&SkippedType> = errors
3138            .iter()
3139            .filter(|error| error.reason().contains("not in the DAR"))
3140            .collect();
3141        assert!(
3142            unresolved.is_empty(),
3143            "every cross-package reference should resolve: {unresolved:?}"
3144        );
3145
3146        // The lowered IR generates syntactically valid Rust end to end.
3147        let src = crate::generate_crate(&krate).expect("generate crate");
3148        syn::parse_file(&src).expect("generated source must be valid Rust");
3149
3150        let modules: usize = krate.packages.iter().map(|p| p.modules.len()).sum();
3151        let types: usize = krate
3152            .packages
3153            .iter()
3154            .flat_map(|p| &p.modules)
3155            .map(|m| m.module.data_types.len())
3156            .sum();
3157        println!(
3158            "lowered {} packages / {modules} modules / {types} data types → {} bytes of Rust ({} skipped)",
3159            krate.packages.len(),
3160            src.len(),
3161            errors.len(),
3162        );
3163    }
3164
3165    /// An interned type that resolves to itself. prost cannot object: the
3166    /// interned table is a flat list of indices, not nested messages, so
3167    /// nothing about the archive is malformed until something follows the
3168    /// index. Following it used to recurse until the stack ended, and a stack
3169    /// overflow aborts the process — the DAR does not fail, the caller dies.
3170    ///
3171    /// A `.dalf` like this is not something `daml build` emits; it is something
3172    /// a person writes. The archive-integrity guards do not help, because the
3173    /// package id is the hash of whatever payload the author chose.
3174    #[test]
3175    fn an_interned_type_cycle_is_refused_instead_of_overflowing_the_stack() {
3176        let mut package = package_with(&["M.T"]);
3177        package.interned_strings.push("f".to_string());
3178        let label = i32::try_from(package.interned_strings.len() - 1).unwrap();
3179        // interned_types[0] = Interned(0)
3180        package.interned_types = vec![lf::Type {
3181            sum: Some(lf::r#type::Sum::Interned(0)),
3182        }];
3183        let mut def = record_def(0, label);
3184        if let Some(lf::def_data_type::DataCons::Record(fields)) = &mut def.data_cons {
3185            fields.fields[0].r#type = Some(lf::Type {
3186                sum: Some(lf::r#type::Sum::Interned(0)),
3187            });
3188        }
3189        package.modules = vec![lf::Module {
3190            name_interned_dname: 0,
3191            data_types: vec![def],
3192            ..lf::Module::default()
3193        }];
3194
3195        // Reaching this line at all is the assertion: before the depth bound
3196        // the process aborted here with SIGABRT.
3197        let (krate, skipped) = lower_crate(&[("aa".to_string(), package)]);
3198        assert!(
3199            skipped.iter().any(|s| s.to_string().contains("cycle")),
3200            "should be reported as a skipped type: {skipped:?}"
3201        );
3202        // And the type it defeated is not emitted half-formed.
3203        let emitted: usize = krate
3204            .packages
3205            .iter()
3206            .flat_map(|p| &p.modules)
3207            .map(|m| m.module.data_types.len())
3208            .sum();
3209        assert_eq!(
3210            emitted, 0,
3211            "a type that could not be resolved is not emitted"
3212        );
3213    }
3214
3215    /// Two interned types pointing at each other — the same defect one step
3216    /// removed, and the shape a counter that only watched the direct
3217    /// `Interned` arm would miss.
3218    #[test]
3219    fn an_indirect_interned_cycle_is_refused_too() {
3220        let mut package = package_with(&["M.T"]);
3221        package.interned_strings.push("f".to_string());
3222        let label = i32::try_from(package.interned_strings.len() - 1).unwrap();
3223        package.interned_types = vec![
3224            lf::Type {
3225                sum: Some(lf::r#type::Sum::Interned(1)),
3226            },
3227            lf::Type {
3228                sum: Some(lf::r#type::Sum::Interned(0)),
3229            },
3230        ];
3231        let mut def = record_def(0, label);
3232        if let Some(lf::def_data_type::DataCons::Record(fields)) = &mut def.data_cons {
3233            fields.fields[0].r#type = Some(lf::Type {
3234                sum: Some(lf::r#type::Sum::Interned(0)),
3235            });
3236        }
3237        package.modules = vec![lf::Module {
3238            name_interned_dname: 0,
3239            data_types: vec![def],
3240            ..lf::Module::default()
3241        }];
3242
3243        let (_krate, skipped) = lower_crate(&[("aa".to_string(), package)]);
3244        assert!(
3245            skipped.iter().any(|s| s.to_string().contains("cycle")),
3246            "{skipped:?}"
3247        );
3248    }
3249    /// Two declarations under one name, where that name is also a template's.
3250    ///
3251    /// The flattening collision check cannot see this: it groups *distinct*
3252    /// dotted names that collide once `.` becomes `_`, and a duplicate is the
3253    /// same name, not a distinct one. So nothing was reported, both records
3254    /// were folded into the payload map, and the last writer won — the template
3255    /// came out carrying another type's fields. That is the exact outcome the
3256    /// collision check's own comment says it exists to prevent: wrong commands,
3257    /// and it compiles.
3258    #[test]
3259    fn a_name_declared_twice_is_refused_rather_than_silently_resolved() {
3260        let mut package = package_with(&["M", "T"]);
3261        package.interned_strings.push("a".to_string());
3262        let a = i32::try_from(package.interned_strings.len() - 1).unwrap();
3263        package.interned_strings.push("b".to_string());
3264        let b = i32::try_from(package.interned_strings.len() - 1).unwrap();
3265
3266        package.modules = vec![lf::Module {
3267            name_interned_dname: 0,
3268            data_types: vec![record_def(1, a), record_def(1, b)],
3269            templates: vec![lf::DefTemplate {
3270                tycon_interned_dname: 1,
3271                ..lf::DefTemplate::default()
3272            }],
3273            ..lf::Module::default()
3274        }];
3275
3276        let (krate, skipped) = lower_crate(&[("aa".to_string(), package)]);
3277        let reasons: Vec<String> = skipped.iter().map(ToString::to_string).collect();
3278        assert!(
3279            reasons
3280                .iter()
3281                .any(|r| r.contains("declared more than once")),
3282            "the duplicate must be reported: {reasons:?}"
3283        );
3284        // And no template is emitted carrying a payload it cannot be sure of.
3285        let templates: Vec<&str> = krate
3286            .packages
3287            .iter()
3288            .flat_map(|p| &p.modules)
3289            .flat_map(|m| &m.module.templates)
3290            .flat_map(|t| t.fields.iter().map(|f| f.label.as_str()))
3291            .collect();
3292        assert!(
3293            templates.is_empty() || templates == ["a"],
3294            "a template must not silently take the second declaration's fields: {templates:?}"
3295        );
3296    }
3297
3298    /// The same one level up: two modules under one dotted name emit `pub mod M`
3299    /// twice, which is E0428 in the crate handed to the user — reported as a
3300    /// successful generation, because `syn::parse_file` accepts duplicate items.
3301    #[test]
3302    fn a_module_declared_twice_is_refused() {
3303        let mut package = package_with(&["M"]);
3304        package.interned_strings.push("f".to_string());
3305        let label = i32::try_from(package.interned_strings.len() - 1).unwrap();
3306        let module = lf::Module {
3307            name_interned_dname: 0,
3308            data_types: vec![record_def(0, label)],
3309            ..lf::Module::default()
3310        };
3311        package.modules = vec![module.clone(), module];
3312
3313        let (krate, skipped) = lower_crate(&[("aa".to_string(), package)]);
3314        let reasons: Vec<String> = skipped.iter().map(ToString::to_string).collect();
3315        assert!(
3316            reasons
3317                .iter()
3318                .any(|r| r.contains("declared more than once")),
3319            "{reasons:?}"
3320        );
3321        let modules: usize = krate.packages.iter().map(|p| p.modules.len()).sum();
3322        assert_eq!(modules, 1, "only one module may be emitted");
3323    }
3324}