1use 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#[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 fn in_module(mut self, module: &str) -> Self {
61 self.module = module.to_string();
62 self
63 }
64
65 #[must_use]
68 pub fn module(&self) -> &str {
69 &self.module
70 }
71
72 #[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
91pub fn lower_dar(dar: &Dar) -> Result<(Crate, Vec<SkippedType>), DecodeError> {
98 lower_dar_with(dar, &ExternalPackages::default())
99}
100
101pub 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
122pub 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#[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#[derive(Clone, Debug, Default)]
172pub struct Selection {
173 keys: Option<Vec<String>>,
175 prefixes: Vec<String>,
177}
178
179impl Selection {
180 #[must_use]
182 pub fn everything() -> Self {
183 Self {
184 keys: None,
185 prefixes: Vec::new(),
186 }
187 }
188
189 #[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 #[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 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#[derive(Clone, Debug, Default)]
247pub struct ExternalPackages {
248 by_key: HashMap<String, String>,
249 by_prefix: Vec<(String, String)>,
251}
252
253impl ExternalPackages {
254 #[must_use]
256 pub fn new() -> Self {
257 Self::default()
258 }
259
260 #[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 #[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 #[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 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 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#[must_use]
329#[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 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 if external.contains_key(id.as_str()) {
366 continue;
367 }
368 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 let colliding_modules = colliding_module_names(package);
390 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 drop_ambiguous_nested_optionals(&mut krate, &mut errors);
443
444 drop_dangling_references(&mut krate, &mut errors);
447
448 box_recursion(&mut krate);
451
452 (krate, errors)
453}
454
455fn parameters_used_under_optional(krate: &Crate) -> HashMap<String, Vec<bool>> {
478 struct Generic<'a> {
479 params: &'a [String],
480 direct: std::collections::BTreeSet<&'a str>,
482 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 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
578fn 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 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
616fn 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
627fn drop_ambiguous_nested_optionals(krate: &mut Crate, errors: &mut Vec<SkippedType>) {
647 let nests_parameter = parameters_used_under_optional(krate);
648
649 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
726fn 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
741fn 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 _ => {}
765 }
766}
767
768fn 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
795fn 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
805fn 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 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 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
912fn 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
928fn 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
934fn 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
940fn 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
955fn 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
966fn 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
990fn 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 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 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
1055fn 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
1076fn 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
1096fn 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
1105fn 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
1114fn sanitize(input: &str) -> String {
1117 input
1118 .chars()
1119 .map(|c| if c.is_ascii_alphanumeric() { c } else { '_' })
1120 .collect()
1121}
1122
1123struct Lowering<'a> {
1125 package: &'a lf::Package,
1126 qualify: Option<Qualify<'a>>,
1129 depth: std::cell::Cell<u32>,
1135}
1136
1137const MAX_TYPE_DEPTH: u32 = 256;
1153
1154struct 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
1163pub(crate) const EXTERNAL_CRATE_ROOT: &str = "::";
1166
1167struct Qualify<'a> {
1169 module_names: &'a HashMap<&'a str, String>,
1172 current_id: &'a str,
1174 external: &'a HashMap<String, String>,
1177 selected: Option<&'a HashSet<String>>,
1184}
1185
1186impl Lowering<'_> {
1187 fn module(
1192 &self,
1193 lf_module: &lf::Module,
1194 module_dotted: &str,
1195 errors: &mut Vec<SkippedType>,
1196 ) -> Module {
1197 let colliding = self.colliding_flattened_names(lf_module);
1203
1204 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 let mut payloads: HashMap<String, Vec<Field>> = HashMap::new();
1227 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 fn colliding_flattened_names(
1278 &self,
1279 lf_module: &lf::Module,
1280 ) -> std::collections::BTreeMap<String, Vec<String>> {
1281 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 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 .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 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 Some(key) => Some(self.type_(key.r#type.as_ref().ok_or_else(|| {
1373 SkippedType::new(format!("template {name}: contract key has no type"))
1374 })?)?),
1375 None => None,
1376 };
1377 Ok(Template {
1378 name,
1379 module_name: module_dotted.to_string(),
1380 package_id: self
1381 .qualify
1382 .as_ref()
1383 .map_or_else(String::new, |q| q.current_id.to_string()),
1384 package_name: package_name(self.package).unwrap_or_default().to_string(),
1385 fields,
1386 choices,
1387 key,
1388 })
1389 }
1390
1391 fn choice(&self, choice: &lf::TemplateChoice) -> Result<Choice, SkippedType> {
1395 let name = interned_str(self.package, choice.name_interned_str)
1396 .ok_or_else(|| SkippedType::new("unresolved choice name"))?
1397 .to_string();
1398 let argument = choice
1399 .arg_binder
1400 .as_ref()
1401 .and_then(|binder| binder.r#type.as_ref())
1402 .ok_or_else(|| SkippedType::new(format!("choice {name}: no argument type")))?;
1403 let returns = choice
1404 .ret_type
1405 .as_ref()
1406 .ok_or_else(|| SkippedType::new(format!("choice {name}: no return type")))?;
1407 Ok(Choice {
1408 name,
1409 consuming: choice.consuming,
1410 argument: self.type_(argument)?,
1411 returns: self.type_(returns)?,
1412 })
1413 }
1414
1415 fn data_type(&self, data_type: &lf::DefDataType) -> Result<Option<DataType>, SkippedType> {
1418 let name = rust_name(self.package, data_type.name_interned_dname)?;
1419
1420 if let Some(lf::def_data_type::DataCons::Interface(_)) = &data_type.data_cons {
1424 return Ok(Some(DataType::InterfaceMarker(name)));
1425 }
1426
1427 if !data_type.serializable {
1430 return Ok(None);
1431 }
1432 let type_params = data_type
1433 .params
1434 .iter()
1435 .map(|param| {
1436 interned_str(self.package, param.var_interned_str)
1437 .map(str::to_string)
1438 .ok_or_else(|| SkippedType::new("unresolved type parameter"))
1439 })
1440 .collect::<Result<Vec<_>, _>>()?;
1441 check_type_params(&name, &type_params)?;
1442
1443 let Some(data_cons) = &data_type.data_cons else {
1444 return Err(SkippedType::new(format!("{name}: no data constructor")));
1445 };
1446
1447 match data_cons {
1448 lf::def_data_type::DataCons::Record(fields) => {
1449 let fields = self.fields(&fields.fields)?;
1450 Ok(Some(DataType::Record(Record {
1451 name,
1452 type_params,
1453 fields,
1454 })))
1455 }
1456 lf::def_data_type::DataCons::Variant(fields) => {
1457 let constructors = fields
1458 .fields
1459 .iter()
1460 .map(|field| self.variant_constructor(field))
1461 .collect::<Result<Vec<_>, _>>()?;
1462 Ok(Some(DataType::Variant(Variant {
1463 name,
1464 type_params,
1465 constructors,
1466 })))
1467 }
1468 lf::def_data_type::DataCons::Enum(constructors) => {
1469 let constructors = constructors
1470 .constructors_interned_str
1471 .iter()
1472 .map(|&index| {
1473 let name = interned_str(self.package, index)
1474 .ok_or_else(|| SkippedType::new("unresolved enum constructor"))?;
1475 if is_rust_ident(name) {
1476 Ok(name.to_string())
1477 } else {
1478 Err(SkippedType::new(format!(
1479 "enum constructor `{name}` is not representable as a Rust identifier"
1480 )))
1481 }
1482 })
1483 .collect::<Result<Vec<_>, _>>()?;
1484 Ok(Some(DataType::Enum(Enum { name, constructors })))
1485 }
1486 lf::def_data_type::DataCons::Interface(_) => Ok(None),
1488 }
1489 }
1490
1491 fn fields(&self, fields: &[lf::FieldWithType]) -> Result<Vec<Field>, SkippedType> {
1492 let lowered = fields
1493 .iter()
1494 .map(|field| {
1495 let label = interned_str(self.package, field.field_interned_str)
1496 .ok_or_else(|| SkippedType::new("unresolved field label"))?
1497 .to_string();
1498 let ty = self.type_(field_type(field)?)?;
1499 Ok(Field { label, ty })
1500 })
1501 .collect::<Result<Vec<Field>, SkippedType>>()?;
1502
1503 let mut by_rust_name: HashMap<String, &str> = HashMap::new();
1507 for field in &lowered {
1508 let rust = heck::ToSnakeCase::to_snake_case(field.label.as_str());
1509 if let Some(previous) = by_rust_name.insert(rust.clone(), &field.label) {
1510 return Err(SkippedType::new(format!(
1511 "fields `{previous}` and `{}` both map to the Rust field `{rust}`",
1512 field.label
1513 )));
1514 }
1515 }
1516 Ok(lowered)
1517 }
1518
1519 fn variant_constructor(
1520 &self,
1521 field: &lf::FieldWithType,
1522 ) -> Result<VariantConstructor, SkippedType> {
1523 let name = interned_str(self.package, field.field_interned_str)
1524 .ok_or_else(|| SkippedType::new("unresolved variant constructor"))?
1525 .to_string();
1526 if !is_rust_ident(&name) {
1527 return Err(SkippedType::new(format!(
1528 "variant constructor `{name}` is not representable as a Rust identifier"
1529 )));
1530 }
1531 let payload = match self.type_(field_type(field)?)? {
1533 DamlType::Unit => None,
1534 other => Some(other),
1535 };
1536 Ok(VariantConstructor { name, payload })
1537 }
1538
1539 fn type_(&self, ty: &lf::Type) -> Result<DamlType, SkippedType> {
1542 self.apply(ty, &[])
1543 }
1544
1545 fn enter(&self) -> Result<DepthGuard<'_>, SkippedType> {
1553 let depth = self.depth.get();
1554 if depth >= MAX_TYPE_DEPTH {
1555 return Err(SkippedType::new(format!(
1556 "type nests deeper than {MAX_TYPE_DEPTH} levels, or its interned \
1557 types refer to each other in a cycle"
1558 )));
1559 }
1560 self.depth.set(depth + 1);
1561 Ok(DepthGuard(&self.depth))
1562 }
1563
1564 fn apply(&self, ty: &lf::Type, extra: &[&lf::Type]) -> Result<DamlType, SkippedType> {
1569 let _guard = self.enter()?;
1572 let Some(sum) = &ty.sum else {
1573 return Err(SkippedType::new("empty type"));
1574 };
1575 match sum {
1576 lf::r#type::Sum::Tapp(app) => {
1577 let lhs = app
1578 .lhs
1579 .as_deref()
1580 .ok_or_else(|| SkippedType::new("type application without a function"))?;
1581 let rhs = app
1582 .rhs
1583 .as_deref()
1584 .ok_or_else(|| SkippedType::new("type application without an argument"))?;
1585 let mut args = Vec::with_capacity(extra.len() + 1);
1586 args.push(rhs);
1587 args.extend_from_slice(extra);
1588 self.apply(lhs, &args)
1589 }
1590 lf::r#type::Sum::Var(var) => {
1591 if !extra.is_empty() || !var.args.is_empty() {
1596 return Err(SkippedType::new("type-variable application is unsupported"));
1597 }
1598 let name = interned_str(self.package, var.var_interned_str)
1599 .ok_or_else(|| SkippedType::new("unresolved type variable"))?
1600 .to_string();
1601 Ok(DamlType::Var(name))
1602 }
1603 lf::r#type::Sum::Con(con) => {
1604 let tycon = con
1605 .tycon
1606 .as_ref()
1607 .ok_or_else(|| SkippedType::new("type constructor without a name"))?;
1608 let path = self.con_path(tycon)?;
1609 let args = con
1610 .args
1611 .iter()
1612 .chain(extra.iter().copied())
1613 .map(|arg| self.type_(arg))
1614 .collect::<Result<Vec<_>, _>>()?;
1615 Ok(DamlType::Ref(TypeRef { path, args }))
1616 }
1617 lf::r#type::Sum::Builtin(builtin) => {
1618 let args: Vec<&lf::Type> =
1619 builtin.args.iter().chain(extra.iter().copied()).collect();
1620 self.builtin(builtin.builtin, &args)
1621 }
1622 lf::r#type::Sum::Interned(index) => {
1623 let resolved = interned_type(self.package, *index)
1624 .ok_or_else(|| SkippedType::new("unresolved interned type"))?;
1625 self.apply(resolved, extra)
1626 }
1627 lf::r#type::Sum::Nat(_) => Err(SkippedType::new("unexpected bare type-level Nat")),
1628 lf::r#type::Sum::Forall(_) | lf::r#type::Sum::Struct(_) | lf::r#type::Sum::Syn(_) => {
1629 Err(SkippedType::new(
1630 "unsupported LF type (forall / struct / syn)",
1631 ))
1632 }
1633 }
1634 }
1635
1636 fn con_path(&self, tycon: &lf::TypeConId) -> Result<Vec<String>, SkippedType> {
1649 use lf::self_or_imported_package_id::Sum;
1650
1651 let type_name = rust_name(self.package, tycon.name_interned_dname)?;
1652 let Some(qualify) = &self.qualify else {
1653 return Ok(vec![type_name]);
1654 };
1655
1656 let module_id = tycon
1657 .module
1658 .as_ref()
1659 .ok_or_else(|| SkippedType::new("type constructor without a module"))?;
1660 let module_dotted =
1661 interned_dotted_name(self.package, module_id.module_name_interned_dname)
1662 .ok_or_else(|| SkippedType::new("unresolved referenced module name"))?;
1663
1664 let target_id = match module_id.package_id.as_ref().and_then(|p| p.sum.as_ref()) {
1667 None | Some(Sum::SelfPackageId(_)) => qualify.current_id.to_string(),
1669 Some(Sum::ImportedPackageIdInternedStr(index)) => interned_str(self.package, *index)
1671 .ok_or_else(|| SkippedType::new("unresolved imported package id"))?
1672 .to_string(),
1673 Some(Sum::PackageImportId(index)) => imported_package_id(self.package, *index)
1676 .ok_or_else(|| SkippedType::new("unresolved package import id"))?
1677 .to_string(),
1678 };
1679 let package_module = qualify
1680 .module_names
1681 .get(target_id.as_str())
1682 .ok_or_else(|| {
1683 SkippedType::new(format!("reference to package {target_id} not in the DAR"))
1684 })?;
1685
1686 if let Some(crate_name) = qualify.external.get(&target_id) {
1691 return Ok(vec![
1696 EXTERNAL_CRATE_ROOT.to_string(),
1697 crate_name.clone(),
1698 package_module.clone(),
1699 module_dotted.replace('.', "_"),
1700 type_name,
1701 ]);
1702 }
1703
1704 if let Some(selected) = qualify.selected
1708 && !selected.contains(&target_id)
1709 {
1710 return Err(SkippedType::new(format!(
1711 "reference to {package_module}, which this crate neither \
1712 generates nor references as an external package"
1713 )));
1714 }
1715
1716 Ok(vec![
1717 "crate".to_string(),
1718 package_module.clone(),
1719 module_dotted.replace('.', "_"),
1720 type_name,
1721 ])
1722 }
1723
1724 fn builtin(&self, builtin: i32, args: &[&lf::Type]) -> Result<DamlType, SkippedType> {
1725 use lf::BuiltinType;
1726
1727 let kind = BuiltinType::try_from(builtin)
1728 .map_err(|_| SkippedType::new(format!("unknown builtin type {builtin}")))?;
1729 let arg = |index: usize| -> Result<DamlType, SkippedType> {
1730 let ty = args.get(index).copied().ok_or_else(|| {
1731 SkippedType::new(format!("{kind:?} missing type argument {index}"))
1732 })?;
1733 self.type_(ty)
1734 };
1735
1736 let ty = match kind {
1737 BuiltinType::Unit => DamlType::Unit,
1738 BuiltinType::Bool => DamlType::Bool,
1739 BuiltinType::Int64 => DamlType::Int64,
1740 BuiltinType::Date => DamlType::Date,
1741 BuiltinType::Timestamp => DamlType::Timestamp,
1742 BuiltinType::Party => DamlType::Party,
1743 BuiltinType::Text => DamlType::Text,
1744 BuiltinType::Numeric => DamlType::Numeric(self.numeric_scale(args)?),
1745 BuiltinType::ContractId => DamlType::ContractId(Box::new(arg(0)?)),
1746 BuiltinType::Optional => DamlType::Optional(Box::new(arg(0)?)),
1747 BuiltinType::List => DamlType::List(Box::new(arg(0)?)),
1748 BuiltinType::Textmap => DamlType::TextMap(Box::new(arg(0)?)),
1749 BuiltinType::Genmap => DamlType::GenMap(Box::new(arg(0)?), Box::new(arg(1)?)),
1750 other => {
1751 return Err(SkippedType::new(format!(
1752 "builtin type {other:?} is not representable in codegen"
1753 )));
1754 }
1755 };
1756 Ok(ty)
1757 }
1758
1759 fn numeric_scale(&self, args: &[&lf::Type]) -> Result<u8, SkippedType> {
1761 let mut ty = *args
1762 .first()
1763 .ok_or_else(|| SkippedType::new("Numeric without a scale argument"))?;
1764 if let Some(lf::r#type::Sum::Interned(index)) = &ty.sum {
1766 ty = interned_type(self.package, *index)
1767 .ok_or_else(|| SkippedType::new("unresolved Numeric scale"))?;
1768 }
1769 match &ty.sum {
1770 Some(lf::r#type::Sum::Nat(scale)) => u8::try_from(*scale)
1771 .map_err(|_| SkippedType::new(format!("Numeric scale {scale} out of range"))),
1772 _ => Err(SkippedType::new("Numeric scale is not a type-level Nat")),
1773 }
1774 }
1775}
1776
1777fn box_recursion(krate: &mut Crate) {
1801 let mut edges: HashMap<String, Vec<String>> = HashMap::new();
1803 for package in &krate.packages {
1804 for module in &package.modules {
1805 let at = |name: &str| path_key_for(&package.name, &module.name, name);
1806 module_containment_edges(&module.module, &at, &mut edges);
1807 }
1808 }
1809
1810 for package in &mut krate.packages {
1813 let package_name = package.name.clone();
1814 for module in &mut package.modules {
1815 let module_name = module.name.clone();
1816 let at = |name: &str| path_key_for(&package_name, &module_name, name);
1817 module_box_cycles(&mut module.module, &at, &edges);
1818 }
1819 }
1820}
1821
1822fn module_containment_edges(
1825 module: &Module,
1826 at: &impl Fn(&str) -> String,
1827 edges: &mut HashMap<String, Vec<String>>,
1828) {
1829 for data_type in &module.data_types {
1830 match data_type {
1831 DataType::Record(record) => {
1832 let node = edges.entry(at(&record.name)).or_default();
1833 for field in &record.fields {
1834 collect_inline_refs(&field.ty, node);
1835 }
1836 }
1837 DataType::Variant(variant) => {
1838 let node = edges.entry(at(&variant.name)).or_default();
1839 for constructor in &variant.constructors {
1840 if let Some(payload) = &constructor.payload {
1841 collect_inline_refs(payload, node);
1842 }
1843 }
1844 }
1845 DataType::Enum(_) | DataType::InterfaceMarker(_) => {}
1846 }
1847 }
1848 for template in &module.templates {
1850 let node = edges.entry(at(&template.name)).or_default();
1851 for field in &template.fields {
1852 collect_inline_refs(&field.ty, node);
1853 }
1854 }
1855}
1856
1857fn module_box_cycles(
1859 module: &mut Module,
1860 at: &impl Fn(&str) -> String,
1861 edges: &HashMap<String, Vec<String>>,
1862) {
1863 for data_type in &mut module.data_types {
1864 match data_type {
1865 DataType::Record(record) => {
1866 let holder = at(&record.name);
1867 for field in &mut record.fields {
1868 box_cycle_closers(&mut field.ty, &holder, edges);
1869 }
1870 }
1871 DataType::Variant(variant) => {
1872 let holder = at(&variant.name);
1873 for constructor in &mut variant.constructors {
1874 if let Some(payload) = &mut constructor.payload {
1875 box_cycle_closers(payload, &holder, edges);
1876 }
1877 }
1878 }
1879 DataType::Enum(_) | DataType::InterfaceMarker(_) => {}
1880 }
1881 }
1882 for template in &mut module.templates {
1883 let holder = at(&template.name);
1884 for field in &mut template.fields {
1885 box_cycle_closers(&mut field.ty, &holder, edges);
1886 }
1887 }
1888}
1889
1890fn path_key_for(package_module: &str, module_name: &str, type_name: &str) -> String {
1893 format!("crate::{package_module}::{module_name}::{type_name}")
1894}
1895
1896fn path_key_of(reference: &TypeRef) -> String {
1899 reference.path.join("::")
1900}
1901
1902fn collect_inline_refs(ty: &DamlType, out: &mut Vec<String>) {
1905 match ty {
1906 DamlType::Optional(inner) => collect_inline_refs(inner, out),
1907 DamlType::Ref(reference) => {
1908 out.push(path_key_of(reference));
1909 for arg in &reference.args {
1911 collect_inline_refs(arg, out);
1912 }
1913 }
1914 _ => {}
1917 }
1918}
1919
1920fn inline_reaches(edges: &HashMap<String, Vec<String>>, from: &str, to: &str) -> bool {
1922 let mut stack = vec![from];
1923 let mut seen = std::collections::HashSet::new();
1924 while let Some(node) = stack.pop() {
1925 if node == to {
1926 return true;
1927 }
1928 if seen.insert(node)
1929 && let Some(next) = edges.get(node)
1930 {
1931 stack.extend(next.iter().map(String::as_str));
1932 }
1933 }
1934 false
1935}
1936
1937fn box_cycle_closers(ty: &mut DamlType, holder: &str, edges: &HashMap<String, Vec<String>>) {
1941 match ty {
1942 DamlType::Optional(inner) => box_cycle_closers(inner, holder, edges),
1943 DamlType::Ref(reference) => {
1944 for arg in &mut reference.args {
1945 box_cycle_closers(arg, holder, edges);
1946 }
1947 let key = path_key_of(reference);
1948 if key == holder || inline_reaches(edges, &key, holder) {
1949 let inner = std::mem::replace(ty, DamlType::Unit);
1950 *ty = DamlType::Boxed(Box::new(inner));
1951 }
1952 }
1953 _ => {}
1954 }
1955}
1956
1957fn field_type(field: &lf::FieldWithType) -> Result<&lf::Type, SkippedType> {
1959 field
1960 .r#type
1961 .as_ref()
1962 .ok_or_else(|| SkippedType::new("field without a type"))
1963}
1964
1965fn data_type_name(data_type: &DataType) -> &str {
1967 match data_type {
1968 DataType::Record(record) => &record.name,
1969 DataType::Variant(variant) => &variant.name,
1970 DataType::Enum(enumeration) => &enumeration.name,
1971 DataType::InterfaceMarker(name) => name,
1972 }
1973}
1974
1975fn rust_name(package: &lf::Package, name_interned_dname: i32) -> Result<String, SkippedType> {
1982 let dotted = interned_dotted_name(package, name_interned_dname)
1983 .ok_or_else(|| SkippedType::new("unresolved dotted name"))?;
1984 let name = dotted.replace('.', "_");
1985 if is_rust_ident(&name) {
1986 Ok(name)
1987 } else {
1988 Err(SkippedType::new(format!(
1989 "`{dotted}` is not representable as a Rust identifier"
1990 )))
1991 }
1992}
1993
1994fn check_type_params(name: &str, params: &[String]) -> Result<(), SkippedType> {
2003 let mut seen: std::collections::BTreeMap<String, &str> = std::collections::BTreeMap::new();
2004 for param in params {
2005 let ident = param.to_upper_camel_case();
2006 if !is_rust_ident(&ident) {
2007 return Err(SkippedType::new(format!(
2008 "{name}: type parameter `{param}` is not representable as a Rust \
2009 generic parameter"
2010 )));
2011 }
2012 if let Some(previous) = seen.insert(ident.clone(), param) {
2013 return Err(SkippedType::new(format!(
2014 "{name}: type parameters `{previous}` and `{param}` both map to the \
2015 Rust generic parameter `{ident}`; rename one in Daml"
2016 )));
2017 }
2018 }
2019 Ok(())
2020}
2021
2022fn is_rust_ident(name: &str) -> bool {
2030 !name.is_empty()
2031 && !name.starts_with(|c: char| c.is_ascii_digit())
2032 && name.chars().all(|c| c.is_ascii_alphanumeric() || c == '_')
2033}
2034
2035#[cfg(test)]
2036#[allow(clippy::unwrap_used, clippy::expect_used)]
2037mod tests {
2038 use super::*;
2039 use canton_lf::Dar;
2040
2041 fn package_with(names: &[&str]) -> lf::Package {
2050 let mut strings: Vec<String> = Vec::new();
2051 let mut dotted = Vec::new();
2052 for name in names {
2053 let segments = name
2054 .split('.')
2055 .map(|segment| {
2056 strings.push(segment.to_string());
2057 i32::try_from(strings.len() - 1).unwrap()
2058 })
2059 .collect();
2060 dotted.push(lf::InternedDottedName {
2061 segments_interned_str: segments,
2062 });
2063 }
2064 lf::Package {
2065 interned_strings: strings,
2066 interned_dotted_names: dotted,
2067 ..lf::Package::default()
2068 }
2069 }
2070
2071 fn record_def(name: i32, label: i32) -> lf::DefDataType {
2074 lf::DefDataType {
2075 name_interned_dname: name,
2076 serializable: true,
2077 data_cons: Some(lf::def_data_type::DataCons::Record(
2078 lf::def_data_type::Fields {
2079 fields: vec![lf::FieldWithType {
2080 field_interned_str: label,
2081 r#type: Some(lf::Type {
2082 sum: Some(lf::r#type::Sum::Builtin(lf::r#type::Builtin {
2083 builtin: lf::BuiltinType::Party as i32,
2084 args: vec![],
2085 })),
2086 }),
2087 }],
2088 },
2089 )),
2090 ..lf::DefDataType::default()
2091 }
2092 }
2093
2094 #[test]
2095 fn two_daml_names_that_flatten_alike_are_skipped_not_silently_merged() {
2096 let mut package = package_with(&["Foo.Bar", "Foo_Bar"]);
2102 package.interned_strings.push("owner".to_string());
2103 package.interned_strings.push("note".to_string());
2104 let (owner, note) = (3, 4);
2105
2106 package.modules.push(lf::Module {
2107 name_interned_dname: 1, data_types: vec![record_def(0, note), record_def(1, owner)],
2109 templates: vec![lf::DefTemplate {
2110 tycon_interned_dname: 1,
2111 ..lf::DefTemplate::default()
2112 }],
2113 ..lf::Module::default()
2114 });
2115
2116 let (krate, skipped) = lower_crate(&[("aa".to_string(), package)]);
2117 let reasons: Vec<String> = skipped.iter().map(ToString::to_string).collect();
2118 assert!(
2119 reasons.iter().any(|r| r.contains("Foo.Bar")
2120 && r.contains("Foo_Bar")
2121 && r.contains("map to the Rust name")),
2122 "the clash must be reported naming both Daml sources: {reasons:?}"
2123 );
2124 let emitted: Vec<&str> = krate
2127 .packages
2128 .iter()
2129 .flat_map(|p| &p.modules)
2130 .flat_map(|m| m.module.templates.iter().map(|t| t.name.as_str()))
2131 .collect();
2132 assert!(!emitted.contains(&"Foo_Bar"), "emitted: {emitted:?}");
2133 }
2134
2135 fn named_package(name: &str, version: Option<&str>) -> lf::Package {
2137 lf::Package {
2138 interned_strings: vec![name.to_string(), version.unwrap_or_default().to_string()],
2139 metadata: Some(lf::PackageMetadata {
2140 name_interned_str: 0,
2141 version_interned_str: 1,
2142 upgraded_package_id: None,
2143 }),
2144 ..lf::Package::default()
2145 }
2146 }
2147
2148 fn module_names(packages: &[(&str, lf::Package)]) -> Vec<String> {
2149 let owned: Vec<(String, lf::Package)> = packages
2150 .iter()
2151 .map(|(id, package)| ((*id).to_string(), package.clone()))
2152 .collect();
2153 let names = package_module_names(&owned);
2154 owned
2156 .iter()
2157 .map(|(id, _)| names[id.as_str()].clone())
2158 .collect()
2159 }
2160
2161 #[test]
2162 fn a_package_module_is_named_after_the_package_alone() {
2163 assert_eq!(
2166 module_names(&[
2167 ("aaa", named_package("quickstart-licensing", Some("0.0.1"))),
2168 ("bbb", named_package("splice-amulet", Some("0.1.14"))),
2169 ]),
2170 ["quickstart_licensing", "splice_amulet"],
2171 );
2172 }
2173
2174 #[test]
2175 fn two_versions_of_one_package_are_separated_by_version() {
2176 assert_eq!(
2180 module_names(&[
2181 ("aaa", named_package("my-app", Some("1.0.0"))),
2182 ("bbb", named_package("my-app", Some("2.0.0"))),
2183 ("ccc", named_package("other", Some("1.0.0"))),
2184 ]),
2185 ["my_app_1_0_0", "my_app_2_0_0", "other"],
2186 );
2187 }
2188
2189 #[test]
2190 fn packages_alike_down_to_the_version_fall_back_to_the_id() {
2191 assert_eq!(
2195 module_names(&[
2196 ("aaaaaaaaaaaa", named_package("my-app", Some("1.0.0"))),
2197 ("bbbbbbbbbbbb", named_package("my-app", Some("1.0.0"))),
2198 ("cccccccccccc", named_package("no-version", Some(""))),
2199 ("dddddddddddd", named_package("no-version", None)),
2200 ("eeeeeeeeeeee", lf::Package::default()),
2201 ]),
2202 [
2203 "my_app_1_0_0_aaaaaaaa",
2204 "my_app_1_0_0_bbbbbbbb",
2205 "no_version_cccccccc",
2206 "no_version_dddddddd",
2207 "p_eeeeeeee",
2208 ],
2209 );
2210 }
2211
2212 #[test]
2213 fn ids_sharing_a_prefix_still_get_distinct_module_names() {
2214 let names = module_names(&[
2218 ("aaaaaaaa1111", named_package("my-app", Some("1.0.0"))),
2219 ("aaaaaaaa2222", named_package("my-app", Some("1.0.0"))),
2220 ]);
2221 assert_eq!(names.len(), 2);
2222 assert_ne!(names[0], names[1], "{names:?}");
2223 assert!(
2224 names.iter().all(|n| n.starts_with("my_app_1_0_0_")),
2225 "{names:?}"
2226 );
2227 }
2228
2229 #[test]
2230 fn a_non_serializable_claimant_does_not_take_a_real_type_down() {
2231 let mut package = package_with(&["Mod", "Handler.OnEvent", "Handler_OnEvent"]);
2236 package.interned_strings.push("owner".to_string());
2237 let owner = i32::try_from(package.interned_strings.len() - 1).unwrap();
2238 let mut ghost = record_def(1, owner);
2239 ghost.serializable = false;
2240 package.modules.push(lf::Module {
2241 name_interned_dname: 0,
2242 data_types: vec![ghost, record_def(2, owner)],
2243 ..lf::Module::default()
2244 });
2245
2246 let (krate, skipped) = lower_crate(&[("aa".to_string(), package)]);
2247 assert!(skipped.is_empty(), "nothing collides: {skipped:?}");
2248 let emitted: Vec<&str> = krate.packages[0].modules[0]
2249 .module
2250 .data_types
2251 .iter()
2252 .map(data_type_name)
2253 .collect();
2254 assert_eq!(emitted, ["Handler_OnEvent"]);
2255 }
2256
2257 #[test]
2258 fn a_module_that_emits_nothing_does_not_block_one_that_does() {
2259 let mut package = package_with(&["Util.Text", "Util_Text"]);
2263 package.interned_strings.push("owner".to_string());
2264 let owner = i32::try_from(package.interned_strings.len() - 1).unwrap();
2265 package.modules.push(lf::Module {
2266 name_interned_dname: 0, ..lf::Module::default()
2268 });
2269 package.modules.push(lf::Module {
2270 name_interned_dname: 1, data_types: vec![record_def(1, owner)],
2272 ..lf::Module::default()
2273 });
2274
2275 let (krate, skipped) = lower_crate(&[("aa".to_string(), package)]);
2276 assert!(skipped.is_empty(), "nothing collides: {skipped:?}");
2277 let modules: Vec<&str> = krate.packages[0]
2278 .modules
2279 .iter()
2280 .map(|m| m.name.as_str())
2281 .collect();
2282 assert_eq!(modules, ["Util_Text"]);
2283 }
2284
2285 #[test]
2286 fn two_type_parameters_that_upper_camel_alike_are_skipped() {
2287 let mut package = package_with(&["Mod", "Pair"]);
2291 let base = i32::try_from(package.interned_strings.len()).unwrap();
2292 package.interned_strings.push("a".to_string());
2293 package.interned_strings.push("A".to_string());
2294 let mut pair = record_def(1, base);
2295 pair.params = vec![
2296 lf::TypeVarWithKind {
2297 var_interned_str: base,
2298 kind: None,
2299 },
2300 lf::TypeVarWithKind {
2301 var_interned_str: base + 1,
2302 kind: None,
2303 },
2304 ];
2305 package.modules.push(lf::Module {
2306 name_interned_dname: 0,
2307 data_types: vec![pair],
2308 ..lf::Module::default()
2309 });
2310
2311 let (krate, skipped) = lower_crate(&[("aa".to_string(), package)]);
2312 assert!(
2313 skipped
2314 .iter()
2315 .any(|s| s.to_string().contains("Rust generic parameter `A`")),
2316 "{skipped:?}"
2317 );
2318 assert!(krate.packages.is_empty(), "nothing emitted");
2319 }
2320
2321 #[test]
2322 fn the_same_package_id_twice_yields_one_module() {
2323 let mut package = package_with(&["Mod", "T"]);
2326 package.interned_strings.push("owner".to_string());
2327 let owner = i32::try_from(package.interned_strings.len() - 1).unwrap();
2328 package.modules.push(lf::Module {
2329 name_interned_dname: 0,
2330 data_types: vec![record_def(1, owner)],
2331 ..lf::Module::default()
2332 });
2333
2334 let (krate, _) = lower_crate(&[
2335 ("aa".to_string(), package.clone()),
2336 ("aa".to_string(), package),
2337 ]);
2338 let names: Vec<&str> = krate.packages.iter().map(|p| p.name.as_str()).collect();
2339 assert_eq!(names.len(), 1, "{names:?}");
2340 }
2341
2342 #[test]
2343 fn two_module_names_that_flatten_alike_are_skipped() {
2344 let mut package = package_with(&["A.B", "A_B", "T"]);
2347 package.interned_strings.push("owner".to_string());
2348 let owner = i32::try_from(package.interned_strings.len() - 1).unwrap();
2349 for name in [0, 1] {
2350 package.modules.push(lf::Module {
2351 name_interned_dname: name,
2352 data_types: vec![record_def(2, owner)],
2353 ..lf::Module::default()
2354 });
2355 }
2356
2357 let (krate, skipped) = lower_crate(&[("aa".to_string(), package)]);
2358 assert!(
2359 skipped
2360 .iter()
2361 .any(|s| s.to_string().contains("map to the Rust module")),
2362 "{skipped:?}"
2363 );
2364 let modules: Vec<&str> = krate
2365 .packages
2366 .iter()
2367 .flat_map(|p| p.modules.iter().map(|m| m.name.as_str()))
2368 .collect();
2369 assert!(modules.is_empty(), "no module may be emitted: {modules:?}");
2370 }
2371
2372 #[test]
2373 fn an_optional_passed_to_a_generic_that_nests_it_is_refused_not_mis_encoded() {
2374 let reference = |name: &str, args: Vec<DamlType>| {
2380 DamlType::Ref(TypeRef {
2381 path: vec![
2382 "crate".to_string(),
2383 "pkg_1_0_0".to_string(),
2384 "Mod".to_string(),
2385 name.to_string(),
2386 ],
2387 args,
2388 })
2389 };
2390 let field = |label: &str, ty: DamlType| Field {
2391 label: label.to_string(),
2392 ty,
2393 };
2394 let module = |data_types: Vec<DataType>| Crate {
2395 packages: vec![PackageModule {
2396 name: "pkg_1_0_0".to_string(),
2397 modules: vec![NamedModule {
2398 name: "Mod".to_string(),
2399 module: Module {
2400 data_types,
2401 ..Module::default()
2402 },
2403 }],
2404 }],
2405 };
2406
2407 let wrap_nesting = DataType::Record(Record {
2409 name: "Wrap".to_string(),
2410 type_params: vec!["a".to_string()],
2411 fields: vec![field(
2412 "value",
2413 DamlType::Optional(Box::new(DamlType::Var("a".to_string()))),
2414 )],
2415 });
2416 let user = |arg: DamlType| {
2417 DataType::Record(Record {
2418 name: "User".to_string(),
2419 type_params: vec![],
2420 fields: vec![field("w", reference("Wrap", vec![arg]))],
2421 })
2422 };
2423
2424 let mut krate = module(vec![
2425 wrap_nesting.clone(),
2426 user(DamlType::Optional(Box::new(DamlType::Text))),
2427 ]);
2428 let mut errors = Vec::new();
2429 drop_ambiguous_nested_optionals(&mut krate, &mut errors);
2430 assert!(
2431 errors
2432 .iter()
2433 .any(|e| e.to_string().contains("not yet representable")),
2434 "{errors:?}"
2435 );
2436 let names: Vec<&str> = krate.packages[0].modules[0]
2437 .module
2438 .data_types
2439 .iter()
2440 .map(data_type_name)
2441 .collect();
2442 assert_eq!(names, ["Wrap"], "only the ambiguous user is dropped");
2443
2444 let mut fine = module(vec![wrap_nesting, user(DamlType::Text)]);
2446 let mut errors = Vec::new();
2447 drop_ambiguous_nested_optionals(&mut fine, &mut errors);
2448 assert!(errors.is_empty(), "{errors:?}");
2449
2450 let wrap_plain = DataType::Record(Record {
2453 name: "Wrap".to_string(),
2454 type_params: vec!["a".to_string()],
2455 fields: vec![field("value", DamlType::Var("a".to_string()))],
2456 });
2457 let mut plain = module(vec![
2458 wrap_plain,
2459 user(DamlType::Optional(Box::new(DamlType::Text))),
2460 ]);
2461 let mut errors = Vec::new();
2462 drop_ambiguous_nested_optionals(&mut plain, &mut errors);
2463 assert!(errors.is_empty(), "{errors:?}");
2464 assert_eq!(plain.packages[0].modules[0].module.data_types.len(), 2);
2465 }
2466
2467 #[test]
2468 fn nesting_introduced_through_an_intermediate_generic_is_still_refused() {
2469 let mut krate = generics_module(vec![
2478 generic_record("Wrap", &["a"], vec![("value", optional(var("a")))]),
2479 generic_record(
2480 "Outer",
2481 &["a"],
2482 vec![("inner", reference("Wrap", vec![var("a")]))],
2483 ),
2484 generic_record(
2485 "User",
2486 &[],
2487 vec![("o", reference("Outer", vec![optional(DamlType::Text)]))],
2488 ),
2489 ]);
2490 let mut errors = Vec::new();
2491 drop_ambiguous_nested_optionals(&mut krate, &mut errors);
2492 assert!(
2493 errors
2494 .iter()
2495 .any(|e| e.to_string().contains("not yet representable")),
2496 "the transitive nesting must be refused, not mis-encoded: {errors:?}"
2497 );
2498 let names: Vec<&str> = krate.packages[0].modules[0]
2499 .module
2500 .data_types
2501 .iter()
2502 .map(data_type_name)
2503 .collect();
2504 assert_eq!(
2505 names,
2506 ["Wrap", "Outer"],
2507 "only the instantiation site is dropped"
2508 );
2509 }
2510
2511 #[test]
2512 fn mutually_recursive_generics_reach_a_fixpoint() {
2513 let mut krate = generics_module(vec![
2521 generic_record("Ping", &["a"], vec![("p", optional(var("a")))]),
2522 generic_record(
2523 "Pong",
2524 &["a"],
2525 vec![
2526 ("q", reference("Ping", vec![var("a")])),
2527 ("r", optional(reference("Pong", vec![var("a")]))),
2528 ],
2529 ),
2530 generic_record(
2531 "User",
2532 &[],
2533 vec![("o", reference("Pong", vec![optional(DamlType::Text)]))],
2534 ),
2535 ]);
2536 let mut errors = Vec::new();
2537 drop_ambiguous_nested_optionals(&mut krate, &mut errors);
2538 assert!(
2539 errors
2540 .iter()
2541 .any(|e| e.to_string().contains("not yet representable")),
2542 "{errors:?}"
2543 );
2544 }
2545
2546 #[test]
2547 fn a_container_between_the_two_optionals_is_not_a_nested_optional() {
2548 for container in [
2556 DamlType::List(Box::new(var("a"))),
2557 DamlType::TextMap(Box::new(var("a"))),
2558 DamlType::GenMap(Box::new(DamlType::Text), Box::new(var("a"))),
2559 ] {
2560 let mut krate = generics_module(vec![
2561 generic_record("Wrap", &["a"], vec![("value", optional(container))]),
2562 generic_record(
2563 "User",
2564 &[],
2565 vec![("w", reference("Wrap", vec![optional(DamlType::Text)]))],
2566 ),
2567 ]);
2568 let mut errors = Vec::new();
2569 drop_ambiguous_nested_optionals(&mut krate, &mut errors);
2570 assert!(errors.is_empty(), "must not be refused: {errors:?}");
2571 assert_eq!(krate.packages[0].modules[0].module.data_types.len(), 2);
2572 }
2573 }
2574
2575 #[test]
2576 fn dropping_an_interface_keeps_its_marker_reachable() {
2577 let mut krate = Crate {
2582 packages: vec![PackageModule {
2583 name: "pkg".to_string(),
2584 modules: vec![NamedModule {
2585 name: "Mod".to_string(),
2586 module: Module {
2587 data_types: vec![DataType::InterfaceMarker("Holding".to_string())],
2588 templates: vec![Template {
2589 name: "Vault".to_string(),
2590 module_name: "Mod".to_string(),
2591 package_id: "id".to_string(),
2592 package_name: "pkg".to_string(),
2593 fields: vec![Field {
2594 label: "held".to_string(),
2595 ty: DamlType::ContractId(Box::new(reference("Holding", vec![]))),
2596 }],
2597 choices: vec![],
2598 key: None,
2599 }],
2600 interfaces: vec![Interface {
2601 name: "Holding".to_string(),
2602 module_name: "Mod".to_string(),
2603 package_id: "id".to_string(),
2604 package_name: "pkg".to_string(),
2605 view: Some(reference("HoldingView", vec![])),
2607 choices: vec![],
2608 }],
2609 },
2610 }],
2611 }],
2612 };
2613
2614 let mut errors = Vec::new();
2615 drop_dangling_references(&mut krate, &mut errors);
2616
2617 let module = &krate.packages[0].modules[0].module;
2618 assert!(module.interfaces.is_empty(), "the interface itself must go");
2619 assert_eq!(
2620 module.data_types.len(),
2621 1,
2622 "the marker is still emitted: {:?}",
2623 module
2624 .data_types
2625 .iter()
2626 .map(data_type_name)
2627 .collect::<Vec<_>>()
2628 );
2629 let templates: Vec<&str> = module.templates.iter().map(|t| t.name.as_str()).collect();
2630 assert_eq!(
2631 templates,
2632 ["Vault"],
2633 "a ContractId of the marker still resolves; reasons: {errors:?}"
2634 );
2635 }
2636
2637 #[test]
2638 fn a_contract_id_type_argument_is_not_a_value_position() {
2639 let mut krate = generics_module(vec![
2643 generic_record("Wrap", &["a"], vec![("value", optional(var("a")))]),
2644 generic_record(
2645 "Outer",
2646 &["a"],
2647 vec![(
2648 "c",
2649 DamlType::ContractId(Box::new(reference("Wrap", vec![var("a")]))),
2650 )],
2651 ),
2652 generic_record(
2653 "User",
2654 &[],
2655 vec![("o", reference("Outer", vec![optional(DamlType::Text)]))],
2656 ),
2657 ]);
2658 let mut errors = Vec::new();
2659 drop_ambiguous_nested_optionals(&mut krate, &mut errors);
2660 assert!(errors.is_empty(), "must not be refused: {errors:?}");
2661 assert_eq!(krate.packages[0].modules[0].module.data_types.len(), 3);
2662 }
2663
2664 #[test]
2665 fn a_parameter_handed_on_inside_a_container_does_not_propagate() {
2666 let mut krate = generics_module(vec![
2671 generic_record("Wrap", &["b"], vec![("value", optional(var("b")))]),
2672 generic_record(
2673 "Outer",
2674 &["a"],
2675 vec![(
2676 "inner",
2677 reference("Wrap", vec![DamlType::List(Box::new(var("a")))]),
2678 )],
2679 ),
2680 generic_record(
2681 "User",
2682 &[],
2683 vec![("o", reference("Outer", vec![optional(DamlType::Text)]))],
2684 ),
2685 ]);
2686 let mut errors = Vec::new();
2687 drop_ambiguous_nested_optionals(&mut krate, &mut errors);
2688 assert!(errors.is_empty(), "must not be refused: {errors:?}");
2689 }
2690
2691 fn var(name: &str) -> DamlType {
2693 DamlType::Var(name.to_string())
2694 }
2695
2696 fn optional(ty: DamlType) -> DamlType {
2697 DamlType::Optional(Box::new(ty))
2698 }
2699
2700 fn reference(name: &str, args: Vec<DamlType>) -> DamlType {
2701 DamlType::Ref(TypeRef {
2702 path: vec![
2703 "crate".to_string(),
2704 "pkg".to_string(),
2705 "Mod".to_string(),
2706 name.to_string(),
2707 ],
2708 args,
2709 })
2710 }
2711
2712 fn generic_record(name: &str, params: &[&str], fields: Vec<(&str, DamlType)>) -> DataType {
2713 DataType::Record(Record {
2714 name: name.to_string(),
2715 type_params: params.iter().map(|p| (*p).to_string()).collect(),
2716 fields: fields
2717 .into_iter()
2718 .map(|(label, ty)| Field {
2719 label: label.to_string(),
2720 ty,
2721 })
2722 .collect(),
2723 })
2724 }
2725
2726 fn generics_module(data_types: Vec<DataType>) -> Crate {
2727 Crate {
2728 packages: vec![PackageModule {
2729 name: "pkg".to_string(),
2730 modules: vec![NamedModule {
2731 name: "Mod".to_string(),
2732 module: Module {
2733 data_types,
2734 ..Module::default()
2735 },
2736 }],
2737 }],
2738 }
2739 }
2740
2741 #[test]
2747 fn both_post_lowering_passes_cover_templates_and_interfaces() {
2748 let reference = |name: &str, args: Vec<DamlType>| {
2749 DamlType::Ref(TypeRef {
2750 path: vec![
2751 "crate".to_string(),
2752 "pkg_1_0_0".to_string(),
2753 "Mod".to_string(),
2754 name.to_string(),
2755 ],
2756 args,
2757 })
2758 };
2759 let module_of = |data_types: Vec<DataType>,
2760 templates: Vec<Template>,
2761 interfaces: Vec<Interface>| Crate {
2762 packages: vec![PackageModule {
2763 name: "pkg_1_0_0".to_string(),
2764 modules: vec![NamedModule {
2765 name: "Mod".to_string(),
2766 module: Module {
2767 data_types,
2768 templates,
2769 interfaces,
2770 },
2771 }],
2772 }],
2773 };
2774 let choice = |ty: DamlType| Choice {
2775 name: "Do".to_string(),
2776 consuming: true,
2777 argument: DamlType::Unit,
2778 returns: ty,
2779 };
2780 let template_of = |name: &str, field: DamlType| Template {
2781 name: name.to_string(),
2782 module_name: "Mod".to_string(),
2783 package_id: "aa".to_string(),
2784 package_name: "pkg".to_string(),
2785 fields: vec![Field {
2786 label: "x".to_string(),
2787 ty: field,
2788 }],
2789 choices: vec![],
2790 key: None,
2791 };
2792
2793 let mut krate = module_of(
2797 vec![DataType::InterfaceMarker("Iface".to_string())],
2798 vec![],
2799 vec![Interface {
2800 name: "Iface".to_string(),
2801 module_name: "Mod".to_string(),
2802 package_id: "aa".to_string(),
2803 package_name: "pkg".to_string(),
2804 view: None,
2805 choices: vec![choice(reference("Bad", vec![]))], }],
2807 );
2808 let mut errors = Vec::new();
2809 drop_dangling_references(&mut krate, &mut errors);
2810 assert!(
2811 errors
2812 .iter()
2813 .any(|e| e.to_string().contains("`Iface` references `Bad`")),
2814 "an interface dangles like anything else: {errors:?}"
2815 );
2816 assert!(
2817 krate.packages.is_empty() || krate.packages[0].modules[0].module.interfaces.is_empty(),
2818 "the interface must not be emitted"
2819 );
2820
2821 let wrap = DataType::Record(Record {
2825 name: "Wrap".to_string(),
2826 type_params: vec!["a".to_string()],
2827 fields: vec![Field {
2828 label: "value".to_string(),
2829 ty: DamlType::Optional(Box::new(DamlType::Var("a".to_string()))),
2830 }],
2831 });
2832 let mut krate = module_of(
2833 vec![wrap],
2834 vec![template_of(
2835 "Tpl",
2836 reference("Wrap", vec![DamlType::Optional(Box::new(DamlType::Text))]),
2837 )],
2838 vec![],
2839 );
2840 let mut errors = Vec::new();
2841 drop_ambiguous_nested_optionals(&mut krate, &mut errors);
2842 assert!(
2843 errors
2844 .iter()
2845 .any(|e| e.to_string().contains("not yet representable")),
2846 "a template payload is an instantiation site too: {errors:?}"
2847 );
2848 assert!(
2849 krate.packages[0].modules[0].module.templates.is_empty(),
2850 "the template must not be emitted"
2851 );
2852 }
2853
2854 #[test]
2855 fn a_type_referencing_a_skipped_type_is_dropped_transitively() {
2856 let path = |name: &str| {
2861 DamlType::Ref(TypeRef {
2862 path: vec![
2863 "crate".to_string(),
2864 "pkg_1_0_0".to_string(),
2865 "Mod".to_string(),
2866 name.to_string(),
2867 ],
2868 args: vec![],
2869 })
2870 };
2871 let record = |name: &str, field: DamlType| {
2872 DataType::Record(Record {
2873 name: name.to_string(),
2874 type_params: vec![],
2875 fields: vec![Field {
2876 label: "x".to_string(),
2877 ty: field,
2878 }],
2879 })
2880 };
2881 let mut krate = Crate {
2882 packages: vec![PackageModule {
2883 name: "pkg_1_0_0".to_string(),
2884 modules: vec![NamedModule {
2885 name: "Mod".to_string(),
2886 module: Module {
2887 data_types: vec![
2889 record("Good", path("Bad")),
2890 record("Worse", path("Good")),
2891 record("Fine", DamlType::Text),
2892 ],
2893 ..Module::default()
2894 },
2895 }],
2896 }],
2897 };
2898
2899 let mut errors = Vec::new();
2900 drop_dangling_references(&mut krate, &mut errors);
2901
2902 let surviving: Vec<&str> = krate.packages[0].modules[0]
2903 .module
2904 .data_types
2905 .iter()
2906 .map(data_type_name)
2907 .collect();
2908 assert_eq!(surviving, ["Fine"], "only the self-contained type survives");
2909
2910 let reasons: Vec<String> = errors.iter().map(ToString::to_string).collect();
2911 assert!(
2912 reasons
2913 .iter()
2914 .any(|r| r.contains("`Good` references `Bad`")),
2915 "{reasons:?}"
2916 );
2917 assert!(
2918 reasons
2919 .iter()
2920 .any(|r| r.contains("`Worse` references `Good`")),
2921 "the drop must cascade: {reasons:?}"
2922 );
2923 }
2924
2925 #[test]
2926 fn a_daml_name_rust_cannot_spell_is_skipped_not_panicked_on() {
2927 assert!(is_rust_ident("Red"));
2932 assert!(is_rust_ident("_x9"));
2933 for bad in ["Red'", "", "9lives", "naïve", "a-b", "A.B"] {
2934 assert!(!is_rust_ident(bad), "{bad:?} is not a Rust identifier");
2935 }
2936
2937 let mut package = package_with(&["Colour"]);
2940 package.interned_strings.push("Red'".to_string());
2941 let constructor = i32::try_from(package.interned_strings.len() - 1).unwrap();
2942 package.modules.push(lf::Module {
2943 name_interned_dname: 0,
2944 data_types: vec![lf::DefDataType {
2945 name_interned_dname: 0,
2946 serializable: true,
2947 data_cons: Some(lf::def_data_type::DataCons::Enum(
2948 lf::def_data_type::EnumConstructors {
2949 constructors_interned_str: vec![constructor],
2950 },
2951 )),
2952 ..lf::DefDataType::default()
2953 }],
2954 ..lf::Module::default()
2955 });
2956
2957 let (krate, skipped) = lower_crate(&[("aa".to_string(), package)]);
2958 assert!(
2959 skipped
2960 .iter()
2961 .any(|s| s.to_string().contains("enum constructor `Red'`")),
2962 "{skipped:?}"
2963 );
2964 assert!(krate.packages.is_empty(), "nothing may be emitted");
2965 }
2966
2967 #[test]
2968 fn the_collision_suffix_is_never_empty() {
2969 assert_ne!(ident_prefix("€€€€"), ident_prefix("£££££"));
2972 assert!(!ident_prefix("€€€€").is_empty());
2973 assert_eq!(ident_prefix("aabbccddee"), "aabbccdd");
2974 }
2975
2976 fn qualified(name: &str) -> DamlType {
2979 DamlType::Ref(TypeRef {
2980 path: vec![
2981 "crate".to_string(),
2982 "pkg_1_0_0".to_string(),
2983 "Mod".to_string(),
2984 name.to_string(),
2985 ],
2986 args: vec![],
2987 })
2988 }
2989
2990 fn record(name: &str, fields: Vec<(&str, DamlType)>) -> DataType {
2991 DataType::Record(Record {
2992 name: name.to_string(),
2993 type_params: vec![],
2994 fields: fields
2995 .into_iter()
2996 .map(|(label, ty)| Field {
2997 label: label.to_string(),
2998 ty,
2999 })
3000 .collect(),
3001 })
3002 }
3003
3004 fn crate_of(data_types: Vec<DataType>) -> Crate {
3005 Crate {
3006 packages: vec![PackageModule {
3007 name: "pkg_1_0_0".to_string(),
3008 modules: vec![NamedModule {
3009 name: "Mod".to_string(),
3010 module: Module {
3011 data_types,
3012 ..Module::default()
3013 },
3014 }],
3015 }],
3016 }
3017 }
3018
3019 fn field_ty(krate: &Crate, type_index: usize, field_index: usize) -> &DamlType {
3020 match &krate.packages[0].modules[0].module.data_types[type_index] {
3021 DataType::Record(record) => &record.fields[field_index].ty,
3022 other => panic!("expected a record, got {other:?}"),
3023 }
3024 }
3025
3026 #[test]
3027 fn optional_self_recursion_is_boxed() {
3028 let mut krate = crate_of(vec![record(
3031 "Tree",
3032 vec![
3033 ("left", DamlType::Optional(Box::new(qualified("Tree")))),
3034 ("size", DamlType::Int64),
3035 ],
3036 )]);
3037 box_recursion(&mut krate);
3038 assert_eq!(
3039 field_ty(&krate, 0, 0),
3040 &DamlType::Optional(Box::new(DamlType::Boxed(Box::new(qualified("Tree"))))),
3041 );
3042 assert_eq!(field_ty(&krate, 0, 1), &DamlType::Int64);
3044 }
3045
3046 #[test]
3047 fn mutual_recursion_is_boxed() {
3048 let mut krate = crate_of(vec![
3051 record("A", vec![("b", qualified("B"))]),
3052 record(
3053 "B",
3054 vec![("a", DamlType::Optional(Box::new(qualified("A"))))],
3055 ),
3056 ]);
3057 box_recursion(&mut krate);
3058 assert_eq!(
3059 field_ty(&krate, 0, 0),
3060 &DamlType::Boxed(Box::new(qualified("B")))
3061 );
3062 assert_eq!(
3063 field_ty(&krate, 1, 0),
3064 &DamlType::Optional(Box::new(DamlType::Boxed(Box::new(qualified("A"))))),
3065 );
3066 }
3067
3068 #[test]
3069 fn generic_instantiation_recursion_is_boxed() {
3070 let wrap_of_t = DamlType::Ref(TypeRef {
3073 path: vec![
3074 "crate".to_string(),
3075 "pkg_1_0_0".to_string(),
3076 "Mod".to_string(),
3077 "Wrap".to_string(),
3078 ],
3079 args: vec![qualified("T")],
3080 });
3081 let mut krate = crate_of(vec![
3082 DataType::Record(Record {
3083 name: "Wrap".to_string(),
3084 type_params: vec!["a".to_string()],
3085 fields: vec![Field {
3086 label: "w".to_string(),
3087 ty: DamlType::Var("a".to_string()),
3088 }],
3089 }),
3090 record("T", vec![("w", wrap_of_t)]),
3091 ]);
3092 box_recursion(&mut krate);
3093 let DamlType::Ref(reference) = field_ty(&krate, 1, 0) else {
3094 panic!("outer Wrap reference must stay a Ref");
3095 };
3096 assert_eq!(
3097 reference.args[0],
3098 DamlType::Boxed(Box::new(qualified("T"))),
3099 "the recursive argument occurrence is boxed"
3100 );
3101 }
3102
3103 #[test]
3104 fn heap_containers_already_break_cycles() {
3105 let mut krate = crate_of(vec![record(
3107 "Tree",
3108 vec![
3109 ("children", DamlType::List(Box::new(qualified("Tree")))),
3110 ("index", DamlType::TextMap(Box::new(qualified("Tree")))),
3111 ],
3112 )]);
3113 let before = krate.clone();
3114 box_recursion(&mut krate);
3115 assert_eq!(krate, before, "heap-indirected recursion is left alone");
3116 }
3117
3118 #[test]
3119 fn lowers_a_real_dar_to_a_qualified_crate() {
3120 let Ok(path) = std::env::var("CANTON_TEST_DAR") else {
3122 eprintln!("skipping lower test: set CANTON_TEST_DAR=/path/to/x.dar");
3123 return;
3124 };
3125
3126 let dar = Dar::open(&path).expect("open DAR");
3127 let (krate, errors) = lower_dar(&dar).expect("decode + lower DAR");
3128
3129 assert!(
3130 !krate.packages.is_empty(),
3131 "a DAR should lower at least one package"
3132 );
3133
3134 let unresolved: Vec<&SkippedType> = errors
3138 .iter()
3139 .filter(|error| error.reason().contains("not in the DAR"))
3140 .collect();
3141 assert!(
3142 unresolved.is_empty(),
3143 "every cross-package reference should resolve: {unresolved:?}"
3144 );
3145
3146 let src = crate::generate_crate(&krate).expect("generate crate");
3148 syn::parse_file(&src).expect("generated source must be valid Rust");
3149
3150 let modules: usize = krate.packages.iter().map(|p| p.modules.len()).sum();
3151 let types: usize = krate
3152 .packages
3153 .iter()
3154 .flat_map(|p| &p.modules)
3155 .map(|m| m.module.data_types.len())
3156 .sum();
3157 println!(
3158 "lowered {} packages / {modules} modules / {types} data types → {} bytes of Rust ({} skipped)",
3159 krate.packages.len(),
3160 src.len(),
3161 errors.len(),
3162 );
3163 }
3164
3165 #[test]
3175 fn an_interned_type_cycle_is_refused_instead_of_overflowing_the_stack() {
3176 let mut package = package_with(&["M.T"]);
3177 package.interned_strings.push("f".to_string());
3178 let label = i32::try_from(package.interned_strings.len() - 1).unwrap();
3179 package.interned_types = vec![lf::Type {
3181 sum: Some(lf::r#type::Sum::Interned(0)),
3182 }];
3183 let mut def = record_def(0, label);
3184 if let Some(lf::def_data_type::DataCons::Record(fields)) = &mut def.data_cons {
3185 fields.fields[0].r#type = Some(lf::Type {
3186 sum: Some(lf::r#type::Sum::Interned(0)),
3187 });
3188 }
3189 package.modules = vec![lf::Module {
3190 name_interned_dname: 0,
3191 data_types: vec![def],
3192 ..lf::Module::default()
3193 }];
3194
3195 let (krate, skipped) = lower_crate(&[("aa".to_string(), package)]);
3198 assert!(
3199 skipped.iter().any(|s| s.to_string().contains("cycle")),
3200 "should be reported as a skipped type: {skipped:?}"
3201 );
3202 let emitted: usize = krate
3204 .packages
3205 .iter()
3206 .flat_map(|p| &p.modules)
3207 .map(|m| m.module.data_types.len())
3208 .sum();
3209 assert_eq!(
3210 emitted, 0,
3211 "a type that could not be resolved is not emitted"
3212 );
3213 }
3214
3215 #[test]
3219 fn an_indirect_interned_cycle_is_refused_too() {
3220 let mut package = package_with(&["M.T"]);
3221 package.interned_strings.push("f".to_string());
3222 let label = i32::try_from(package.interned_strings.len() - 1).unwrap();
3223 package.interned_types = vec![
3224 lf::Type {
3225 sum: Some(lf::r#type::Sum::Interned(1)),
3226 },
3227 lf::Type {
3228 sum: Some(lf::r#type::Sum::Interned(0)),
3229 },
3230 ];
3231 let mut def = record_def(0, label);
3232 if let Some(lf::def_data_type::DataCons::Record(fields)) = &mut def.data_cons {
3233 fields.fields[0].r#type = Some(lf::Type {
3234 sum: Some(lf::r#type::Sum::Interned(0)),
3235 });
3236 }
3237 package.modules = vec![lf::Module {
3238 name_interned_dname: 0,
3239 data_types: vec![def],
3240 ..lf::Module::default()
3241 }];
3242
3243 let (_krate, skipped) = lower_crate(&[("aa".to_string(), package)]);
3244 assert!(
3245 skipped.iter().any(|s| s.to_string().contains("cycle")),
3246 "{skipped:?}"
3247 );
3248 }
3249 #[test]
3259 fn a_name_declared_twice_is_refused_rather_than_silently_resolved() {
3260 let mut package = package_with(&["M", "T"]);
3261 package.interned_strings.push("a".to_string());
3262 let a = i32::try_from(package.interned_strings.len() - 1).unwrap();
3263 package.interned_strings.push("b".to_string());
3264 let b = i32::try_from(package.interned_strings.len() - 1).unwrap();
3265
3266 package.modules = vec![lf::Module {
3267 name_interned_dname: 0,
3268 data_types: vec![record_def(1, a), record_def(1, b)],
3269 templates: vec![lf::DefTemplate {
3270 tycon_interned_dname: 1,
3271 ..lf::DefTemplate::default()
3272 }],
3273 ..lf::Module::default()
3274 }];
3275
3276 let (krate, skipped) = lower_crate(&[("aa".to_string(), package)]);
3277 let reasons: Vec<String> = skipped.iter().map(ToString::to_string).collect();
3278 assert!(
3279 reasons
3280 .iter()
3281 .any(|r| r.contains("declared more than once")),
3282 "the duplicate must be reported: {reasons:?}"
3283 );
3284 let templates: Vec<&str> = krate
3286 .packages
3287 .iter()
3288 .flat_map(|p| &p.modules)
3289 .flat_map(|m| &m.module.templates)
3290 .flat_map(|t| t.fields.iter().map(|f| f.label.as_str()))
3291 .collect();
3292 assert!(
3293 templates.is_empty() || templates == ["a"],
3294 "a template must not silently take the second declaration's fields: {templates:?}"
3295 );
3296 }
3297
3298 #[test]
3302 fn a_module_declared_twice_is_refused() {
3303 let mut package = package_with(&["M"]);
3304 package.interned_strings.push("f".to_string());
3305 let label = i32::try_from(package.interned_strings.len() - 1).unwrap();
3306 let module = lf::Module {
3307 name_interned_dname: 0,
3308 data_types: vec![record_def(0, label)],
3309 ..lf::Module::default()
3310 };
3311 package.modules = vec![module.clone(), module];
3312
3313 let (krate, skipped) = lower_crate(&[("aa".to_string(), package)]);
3314 let reasons: Vec<String> = skipped.iter().map(ToString::to_string).collect();
3315 assert!(
3316 reasons
3317 .iter()
3318 .any(|r| r.contains("declared more than once")),
3319 "{reasons:?}"
3320 );
3321 let modules: usize = krate.packages.iter().map(|p| p.modules.len()).sum();
3322 assert_eq!(modules, 1, "only one module may be emitted");
3323 }
3324}