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: self.source_safe_package_name()?,
1385 fields,
1386 choices,
1387 key,
1388 })
1389 }
1390
1391 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 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 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 if let Some(lf::def_data_type::DataCons::Interface(_)) = &data_type.data_cons {
1444 return Ok(Some(DataType::InterfaceMarker(name)));
1445 }
1446
1447 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 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 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 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 fn type_(&self, ty: &lf::Type) -> Result<DamlType, SkippedType> {
1568 self.apply(ty, &[])
1569 }
1570
1571 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 fn apply(&self, ty: &lf::Type, extra: &[&lf::Type]) -> Result<DamlType, SkippedType> {
1595 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 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 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 let target_id = match module_id.package_id.as_ref().and_then(|p| p.sum.as_ref()) {
1693 None | Some(Sum::SelfPackageId(_)) => qualify.current_id.to_string(),
1695 Some(Sum::ImportedPackageIdInternedStr(index)) => interned_str(self.package, *index)
1697 .ok_or_else(|| SkippedType::new("unresolved imported package id"))?
1698 .to_string(),
1699 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 if let Some(crate_name) = qualify.external.get(&target_id) {
1717 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 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 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 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
1803fn box_recursion(krate: &mut Crate) {
1827 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 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
1848fn 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 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
1883fn 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
1916fn path_key_for(package_module: &str, module_name: &str, type_name: &str) -> String {
1919 format!("crate::{package_module}::{module_name}::{type_name}")
1920}
1921
1922fn path_key_of(reference: &TypeRef) -> String {
1925 reference.path.join("::")
1926}
1927
1928fn 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 for arg in &reference.args {
1937 collect_inline_refs(arg, out);
1938 }
1939 }
1940 _ => {}
1943 }
1944}
1945
1946fn 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
1963fn 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
1983fn 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
1991fn 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
2001fn 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
2020fn 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
2048fn 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 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 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 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, 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 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 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 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 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 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 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 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 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 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, ..lf::Module::default()
2307 });
2308 package.modules.push(lf::Module {
2309 name_interned_dname: 1, 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 #[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 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![]))], }],
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 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 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 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 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 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 assert_ne!(ident_prefix("€€€€"), ident_prefix("£££££"));
3024 assert_ne!(ident_prefix("€€€€"), "");
3025 assert_eq!(ident_prefix("aabbccddee"), "aabbccdd");
3026 }
3027
3028 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 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 assert_eq!(field_ty(&krate, 0, 1), &DamlType::Int64);
3096 }
3097
3098 #[test]
3099 fn mutual_recursion_is_boxed() {
3100 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 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 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 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 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 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 #[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 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 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 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 #[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 #[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 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 #[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}