Skip to main content

canton_codegen/
lower.rs

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