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