1use std::collections::HashMap;
23
24use heck::ToUpperCamelCase;
25
26use canton_lf::pb::daml_lf_2 as lf;
27use canton_lf::{
28 Dar, DecodeError, decode_all, imported_package_id, interned_dotted_name, interned_str,
29 interned_type, package_name, package_version,
30};
31
32use crate::ir::{
33 Choice, Crate, DamlType, DataType, Enum, Field, Interface, Module, NamedModule, PackageModule,
34 Record, Template, TypeRef, Variant, VariantConstructor,
35};
36
37#[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 Ok(lower_crate_with(&decode_all(dar)?, external))
120}
121
122#[derive(Clone, Debug, Default)]
130pub struct ExternalPackages {
131 by_key: HashMap<String, String>,
132}
133
134impl ExternalPackages {
135 #[must_use]
137 pub fn new() -> Self {
138 Self::default()
139 }
140
141 #[must_use]
144 pub fn with(mut self, key: impl Into<String>, crate_name: impl Into<String>) -> Self {
145 self.by_key.insert(key.into(), crate_name.into());
146 self
147 }
148
149 #[must_use]
152 pub fn crate_names(&self) -> Vec<String> {
153 let mut names: Vec<String> = self.by_key.values().cloned().collect();
154 names.sort_unstable();
155 names.dedup();
156 names
157 }
158
159 fn resolve(&self, packages: &[(String, lf::Package)]) -> HashMap<String, String> {
162 let mut resolved = HashMap::new();
163 for (id, package) in packages {
164 let by_id = self.by_key.get(id.as_str());
165 let by_name = canton_lf::package_name(package).and_then(|n| self.by_key.get(n));
166 if let Some(crate_name) = by_id.or(by_name) {
167 resolved.insert(id.clone(), crate_name.clone());
168 }
169 }
170 resolved
171 }
172}
173
174#[must_use]
181#[cfg(test)]
183pub(crate) fn lower_crate(packages: &[(String, lf::Package)]) -> (Crate, Vec<SkippedType>) {
184 lower_crate_with(packages, &ExternalPackages::default())
185}
186
187pub(crate) fn lower_crate_with(
188 packages: &[(String, lf::Package)],
189 external: &ExternalPackages,
190) -> (Crate, Vec<SkippedType>) {
191 let external = external.resolve(packages);
192 let module_names = package_module_names(packages);
193
194 let mut krate = Crate::default();
195 let mut errors = Vec::new();
196
197 let mut lowered: std::collections::HashSet<&str> = std::collections::HashSet::new();
203
204 for (id, package) in packages {
205 if !lowered.insert(id.as_str()) {
206 continue;
207 }
208 if external.contains_key(id.as_str()) {
212 continue;
213 }
214 let lowering = Lowering {
215 package,
216 qualify: Some(Qualify {
217 module_names: &module_names,
218 current_id: id,
219 external: &external,
220 }),
221 depth: std::cell::Cell::new(0),
222 };
223 let mut package_module = PackageModule {
224 name: module_names[id.as_str()].clone(),
225 modules: Vec::new(),
226 };
227 let colliding_modules = colliding_module_names(package);
231 let mut seen_modules: std::collections::HashSet<String> = std::collections::HashSet::new();
237
238 for lf_module in &package.modules {
239 let Some(dotted) = interned_dotted_name(package, lf_module.name_interned_dname) else {
240 errors.push(SkippedType::new("unresolved module name"));
241 continue;
242 };
243 if !seen_modules.insert(dotted.clone()) {
244 errors.push(SkippedType::new(format!(
245 "module `{dotted}` is declared more than once in this package"
246 )));
247 continue;
248 }
249 let module_ident = dotted.replace('.', "_");
250 if !is_rust_ident(&module_ident) {
251 errors.push(SkippedType::new(format!(
252 "module `{dotted}` is not representable as a Rust module name"
253 )));
254 continue;
255 }
256 if let Some(sources) = colliding_modules.get(&module_ident) {
257 errors.push(SkippedType::new(format!(
258 "modules `{}` both map to the Rust module `{}`; \
259 rename one in Daml to generate either",
260 sources.join("` and `"),
261 dotted.replace('.', "_"),
262 )));
263 continue;
264 }
265 let ir_module = lowering.module(lf_module, &dotted, &mut errors);
266 if !ir_module.data_types.is_empty()
267 || !ir_module.templates.is_empty()
268 || !ir_module.interfaces.is_empty()
269 {
270 package_module.modules.push(NamedModule {
271 name: dotted.replace('.', "_"),
272 module: ir_module,
273 });
274 }
275 }
276 if !package_module.modules.is_empty() {
277 krate.packages.push(package_module);
278 }
279 }
280
281 drop_ambiguous_nested_optionals(&mut krate, &mut errors);
284
285 drop_dangling_references(&mut krate, &mut errors);
288
289 box_recursion(&mut krate);
292
293 (krate, errors)
294}
295
296fn parameters_used_under_optional(krate: &Crate) -> HashMap<String, Vec<bool>> {
319 struct Generic<'a> {
320 params: &'a [String],
321 direct: std::collections::BTreeSet<&'a str>,
323 forwards: Vec<(String, usize, &'a str)>,
326 }
327
328 let mut generics: HashMap<String, Generic<'_>> = HashMap::new();
329 for package in &krate.packages {
330 for module in &package.modules {
331 for data_type in &module.module.data_types {
332 let (name, params, types) = match data_type {
333 DataType::Record(record) => (
334 &record.name,
335 &record.type_params,
336 record
337 .fields
338 .iter()
339 .map(|field| &field.ty)
340 .collect::<Vec<_>>(),
341 ),
342 DataType::Variant(variant) => (
343 &variant.name,
344 &variant.type_params,
345 variant
346 .constructors
347 .iter()
348 .filter_map(|constructor| constructor.payload.as_ref())
349 .collect(),
350 ),
351 DataType::Enum(_) | DataType::InterfaceMarker(_) => continue,
352 };
353 if params.is_empty() {
354 continue;
355 }
356 let mut generic = Generic {
357 params,
358 direct: std::collections::BTreeSet::new(),
359 forwards: Vec::new(),
360 };
361 for ty in types {
362 collect_nesting_positions(ty, &mut generic.direct, &mut generic.forwards);
363 }
364 generics.insert(path_key_for(&package.name, &module.name, name), generic);
365 }
366 }
367 }
368
369 let mut nests_parameter: HashMap<String, Vec<bool>> = generics
373 .iter()
374 .map(|(key, generic)| {
375 let flags = generic
376 .params
377 .iter()
378 .map(|param| generic.direct.contains(param.as_str()))
379 .collect();
380 (key.clone(), flags)
381 })
382 .collect();
383
384 loop {
385 let mut changed = false;
386 for (key, generic) in &generics {
387 for (target, index, param) in &generic.forwards {
388 let target_nests = nests_parameter
389 .get(target)
390 .and_then(|flags| flags.get(*index))
391 .copied()
392 .unwrap_or(false);
393 if !target_nests {
394 continue;
395 }
396 let Some(position) = generic.params.iter().position(|p| p == param) else {
397 continue;
398 };
399 let Some(flag) = nests_parameter
400 .get_mut(key)
401 .and_then(|flags| flags.get_mut(position))
402 else {
403 continue;
404 };
405 if !*flag {
406 *flag = true;
407 changed = true;
408 }
409 }
410 }
411 if !changed {
412 break;
413 }
414 }
415
416 nests_parameter
417}
418
419fn collect_nesting_positions<'a>(
423 ty: &'a DamlType,
424 direct: &mut std::collections::BTreeSet<&'a str>,
425 forwards: &mut Vec<(String, usize, &'a str)>,
426) {
427 match ty {
428 DamlType::Optional(inner) => {
429 if let Some(param) = bare_parameter(inner) {
430 direct.insert(param);
431 }
432 collect_nesting_positions(inner, direct, forwards);
433 }
434 DamlType::Ref(reference) => {
435 let target = path_key_of(reference);
436 for (index, arg) in reference.args.iter().enumerate() {
437 if let Some(param) = bare_parameter(arg) {
438 forwards.push((target.clone(), index, param));
439 }
440 collect_nesting_positions(arg, direct, forwards);
441 }
442 }
443 DamlType::List(inner) | DamlType::TextMap(inner) | DamlType::Boxed(inner) => {
447 collect_nesting_positions(inner, direct, forwards);
448 }
449 DamlType::GenMap(key, value) => {
450 collect_nesting_positions(key, direct, forwards);
451 collect_nesting_positions(value, direct, forwards);
452 }
453 _ => {}
454 }
455}
456
457fn bare_parameter(ty: &DamlType) -> Option<&str> {
461 match ty {
462 DamlType::Var(name) => Some(name),
463 DamlType::Boxed(inner) => bare_parameter(inner),
464 _ => None,
465 }
466}
467
468fn drop_ambiguous_nested_optionals(krate: &mut Crate, errors: &mut Vec<SkippedType>) {
488 let nests_parameter = parameters_used_under_optional(krate);
489
490 let mut ambiguous: Vec<(String, String, String, String)> = Vec::new();
494 for package in &krate.packages {
495 for module in &package.modules {
496 let mut check = |name: &str, types: Vec<&TypeRef>| {
497 if let Some(target) = types.iter().find_map(|reference| {
498 let nests = nests_parameter.get(&path_key_of(reference))?;
499 reference
500 .args
501 .iter()
502 .zip(nests)
503 .any(|(arg, &nested)| nested && matches!(arg, DamlType::Optional(_)))
504 .then(|| path_key_of(reference))
505 }) {
506 ambiguous.push((
507 package.name.clone(),
508 module.name.clone(),
509 name.to_string(),
510 target,
511 ));
512 }
513 };
514
515 for data_type in &module.module.data_types {
516 let mut types = Vec::new();
517 collect_type_refs_of_data_type(data_type, &mut types);
518 check(data_type_name(data_type), types);
519 }
520 for template in &module.module.templates {
521 let mut types = Vec::new();
522 each_type_of_template(template, &mut |ty| {
523 collect_type_refs(ty, &mut types);
524 });
525 check(&template.name, types);
526 }
527 for interface in &module.module.interfaces {
528 let mut types = Vec::new();
529 each_type_of_interface(interface, &mut |ty| {
530 collect_type_refs(ty, &mut types);
531 });
532 check(&interface.name, types);
533 }
534 }
535 }
536
537 for (package_name, module_name, name, target) in ambiguous {
538 let key = path_key_for(&package_name, &module_name, &name);
539 for package in &mut krate.packages {
540 for module in &mut package.modules {
541 let at = |name: &str| path_key_for(&package.name, &module.name, name);
542 module
543 .module
544 .data_types
545 .retain(|data_type| at(data_type_name(data_type)) != key);
546 module
547 .module
548 .templates
549 .retain(|template| at(&template.name) != key);
550 module
551 .module
552 .interfaces
553 .retain(|interface| at(&interface.name) != key);
554 }
555 }
556 errors.push(
557 SkippedType::new(format!(
558 "`{name}` instantiates `{}` with an Optional that the target nests \
559 under its own Optional; the LF-JSON form of that is not yet representable",
560 target.rsplit("::").next().unwrap_or(&target),
561 ))
562 .in_module(&module_name),
563 );
564 }
565}
566
567fn collect_type_refs_of_data_type<'a>(data_type: &'a DataType, out: &mut Vec<&'a TypeRef>) {
570 let mut walk = |ty: &'a DamlType| collect_type_refs(ty, out);
571 match data_type {
572 DataType::Record(record) => record.fields.iter().for_each(|field| walk(&field.ty)),
573 DataType::Variant(variant) => variant
574 .constructors
575 .iter()
576 .filter_map(|constructor| constructor.payload.as_ref())
577 .for_each(walk),
578 DataType::Enum(_) | DataType::InterfaceMarker(_) => {}
579 }
580}
581
582fn collect_type_refs<'a>(ty: &'a DamlType, out: &mut Vec<&'a TypeRef>) {
584 match ty {
585 DamlType::Ref(reference) => {
586 out.push(reference);
587 reference
588 .args
589 .iter()
590 .for_each(|arg| collect_type_refs(arg, out));
591 }
592 DamlType::List(inner)
593 | DamlType::Optional(inner)
594 | DamlType::TextMap(inner)
595 | DamlType::Boxed(inner) => collect_type_refs(inner, out),
596 DamlType::GenMap(key, value) => {
597 collect_type_refs(key, out);
598 collect_type_refs(value, out);
599 }
600 _ => {}
606 }
607}
608
609fn declared_keys(krate: &Crate) -> std::collections::HashSet<String> {
613 krate
614 .packages
615 .iter()
616 .flat_map(|package| {
617 package.modules.iter().flat_map(move |module| {
618 let at = |name: &str| path_key_for(&package.name, &module.name, name);
619 module
620 .module
621 .data_types
622 .iter()
623 .map(move |data_type| at(data_type_name(data_type)))
624 .chain(
625 module
626 .module
627 .templates
628 .iter()
629 .map(move |template| at(&template.name)),
630 )
631 })
632 })
633 .collect()
634}
635
636fn still_declares(module: &Module, key: &str, at: &impl Fn(&str) -> String) -> bool {
639 module
640 .data_types
641 .iter()
642 .any(|data_type| at(data_type_name(data_type)) == key)
643 || module.templates.iter().any(|t| at(&t.name) == key)
644}
645
646fn drop_dangling_references(krate: &mut Crate, errors: &mut Vec<SkippedType>) {
657 let mut declared = declared_keys(krate);
658
659 loop {
660 let mut dropped = false;
661 for package in &mut krate.packages {
662 let package_name = package.name.clone();
663 for module in &mut package.modules {
664 let module_name = module.name.clone();
665 let at = |name: &str| path_key_for(&package_name, &module_name, name);
666
667 let missing = |referenced: Vec<String>| -> Option<String> {
668 referenced
669 .into_iter()
670 .find(|key| key.starts_with("crate::") && !declared.contains(key))
671 };
672
673 let mut report = Vec::new();
674 module.module.data_types.retain(|data_type| {
675 let mut referenced = Vec::new();
676 collect_all_refs_of_data_type(data_type, &mut referenced);
677 match missing(referenced) {
678 Some(gone) => {
679 report.push((
680 at(data_type_name(data_type)),
681 data_type_name(data_type).to_string(),
682 gone,
683 ));
684 false
685 }
686 None => true,
687 }
688 });
689 module.module.templates.retain(|template| {
690 let mut referenced = Vec::new();
691 collect_all_refs_of_template(template, &mut referenced);
692 match missing(referenced) {
693 Some(gone) => {
694 report.push((at(&template.name), template.name.clone(), gone));
695 false
696 }
697 None => true,
698 }
699 });
700 module.module.interfaces.retain(|interface| {
704 if !declared.contains(&at(&interface.name)) {
705 return false;
706 }
707 let mut referenced = Vec::new();
708 collect_all_refs_of_interface(interface, &mut referenced);
709 match missing(referenced) {
710 Some(gone) => {
711 report.push((at(&interface.name), interface.name.clone(), gone));
712 false
713 }
714 None => true,
715 }
716 });
717
718 for (key, name, gone) in report {
719 if !still_declares(&module.module, &key, &at) {
725 declared.remove(&key);
726 }
727 errors.push(
728 SkippedType::new(format!(
729 "`{name}` references `{}`, which was skipped",
730 gone.rsplit("::").next().unwrap_or(&gone),
731 ))
732 .in_module(&module_name),
733 );
734 dropped = true;
735 }
736 }
737 }
738 if !dropped {
739 break;
740 }
741 }
742
743 krate.packages.iter_mut().for_each(|package| {
744 package.modules.retain(|module| {
745 !module.module.data_types.is_empty()
746 || !module.module.templates.is_empty()
747 || !module.module.interfaces.is_empty()
748 });
749 });
750 krate.packages.retain(|package| !package.modules.is_empty());
751}
752
753fn collect_all_refs_of_data_type(data_type: &DataType, out: &mut Vec<String>) {
755 match data_type {
756 DataType::Record(record) => record
757 .fields
758 .iter()
759 .for_each(|field| collect_all_refs(&field.ty, out)),
760 DataType::Variant(variant) => variant
761 .constructors
762 .iter()
763 .filter_map(|constructor| constructor.payload.as_ref())
764 .for_each(|payload| collect_all_refs(payload, out)),
765 DataType::Enum(_) | DataType::InterfaceMarker(_) => {}
766 }
767}
768
769fn collect_all_refs_of_template(template: &Template, out: &mut Vec<String>) {
772 each_type_of_template(template, &mut |ty| collect_all_refs(ty, out));
773}
774
775fn collect_all_refs_of_interface(interface: &Interface, out: &mut Vec<String>) {
778 each_type_of_interface(interface, &mut |ty| collect_all_refs(ty, out));
779}
780
781fn each_type_of_template<'a>(template: &'a Template, visit: &mut impl FnMut(&'a DamlType)) {
784 for field in &template.fields {
785 visit(&field.ty);
786 }
787 for choice in &template.choices {
788 visit(&choice.argument);
789 visit(&choice.returns);
790 }
791 if let Some(key) = &template.key {
792 visit(key);
793 }
794}
795
796fn each_type_of_interface<'a>(interface: &'a Interface, visit: &mut impl FnMut(&'a DamlType)) {
798 if let Some(view) = &interface.view {
799 visit(view);
800 }
801 for choice in &interface.choices {
802 visit(&choice.argument);
803 visit(&choice.returns);
804 }
805}
806
807fn collect_all_refs(ty: &DamlType, out: &mut Vec<String>) {
810 match ty {
811 DamlType::Ref(reference) => {
812 out.push(path_key_of(reference));
813 reference
814 .args
815 .iter()
816 .for_each(|arg| collect_all_refs(arg, out));
817 }
818 DamlType::ContractId(inner)
819 | DamlType::List(inner)
820 | DamlType::Optional(inner)
821 | DamlType::TextMap(inner)
822 | DamlType::Boxed(inner) => collect_all_refs(inner, out),
823 DamlType::GenMap(key, value) => {
824 collect_all_refs(key, out);
825 collect_all_refs(value, out);
826 }
827 _ => {}
828 }
829}
830
831fn package_module_names(packages: &[(String, lf::Package)]) -> HashMap<&str, String> {
840 let mut names: HashMap<&str, String> = packages
841 .iter()
842 .map(|(id, package)| {
843 let base =
844 package_name(package).map_or_else(|| format!("p_{}", ident_prefix(id)), sanitize);
845 (id.as_str(), base)
846 })
847 .collect();
848
849 let versioned = |id: &str| {
855 packages
856 .iter()
857 .find(|(candidate, _)| candidate == id)
858 .and_then(|(_, package)| package_version(package))
859 .map(sanitize)
860 .filter(|version| !version.is_empty())
861 };
862 for step in 0..3 {
869 let mut counts: HashMap<String, usize> = HashMap::new();
870 for name in names.values() {
871 *counts.entry(name.clone()).or_default() += 1;
872 }
873 let ambiguous: std::collections::HashSet<String> = counts
874 .into_iter()
875 .filter(|(_, count)| *count > 1)
876 .map(|(name, _)| name)
877 .collect();
878 if ambiguous.is_empty() {
879 break;
880 }
881 for (id, name) in &mut names {
882 if !ambiguous.contains(name.as_str()) {
883 continue;
884 }
885 let suffix = match step {
886 0 => versioned(id).unwrap_or_else(|| ident_prefix(id)),
887 1 => ident_prefix(id),
888 _ => sanitize(id),
889 };
890 *name = format!("{name}_{suffix}");
891 }
892 }
893 names
894}
895
896fn ident_prefix(package_id: &str) -> String {
902 let prefix: String = package_id
903 .chars()
904 .filter(char::is_ascii_alphanumeric)
905 .take(8)
906 .collect();
907 if prefix.is_empty() {
908 use std::hash::{Hash as _, Hasher as _};
909 let mut hasher = std::collections::hash_map::DefaultHasher::new();
910 package_id.hash(&mut hasher);
911 format!("h{:x}", hasher.finish())
912 } else {
913 prefix
914 }
915}
916
917fn colliding_module_names(
920 package: &lf::Package,
921) -> std::collections::BTreeMap<String, Vec<String>> {
922 let mut claims: std::collections::BTreeMap<String, Vec<String>> =
923 std::collections::BTreeMap::new();
924 for lf_module in package.modules.iter().filter(|m| emits_anything(m)) {
925 let Some(dotted) = interned_dotted_name(package, lf_module.name_interned_dname) else {
926 continue;
927 };
928 let sources = claims.entry(dotted.replace('.', "_")).or_default();
929 if !sources.contains(&dotted) {
930 sources.push(dotted);
931 }
932 }
933 claims.retain(|_, sources| sources.len() > 1);
934 claims
935}
936
937fn emits_a_type(def: &lf::DefDataType) -> bool {
940 matches!(
941 def.data_cons,
942 Some(lf::def_data_type::DataCons::Interface(_))
943 ) || def.serializable
944}
945
946fn emits_anything(lf_module: &lf::Module) -> bool {
950 !lf_module.templates.is_empty()
951 || !lf_module.interfaces.is_empty()
952 || lf_module.data_types.iter().any(emits_a_type)
953}
954
955fn sanitize(input: &str) -> String {
958 input
959 .chars()
960 .map(|c| if c.is_ascii_alphanumeric() { c } else { '_' })
961 .collect()
962}
963
964struct Lowering<'a> {
966 package: &'a lf::Package,
967 qualify: Option<Qualify<'a>>,
970 depth: std::cell::Cell<u32>,
976}
977
978const MAX_TYPE_DEPTH: u32 = 256;
994
995struct DepthGuard<'a>(&'a std::cell::Cell<u32>);
997
998impl Drop for DepthGuard<'_> {
999 fn drop(&mut self) {
1000 self.0.set(self.0.get() - 1);
1001 }
1002}
1003
1004pub(crate) const EXTERNAL_CRATE_ROOT: &str = "::";
1007
1008struct Qualify<'a> {
1010 module_names: &'a HashMap<&'a str, String>,
1013 current_id: &'a str,
1015 external: &'a HashMap<String, String>,
1018}
1019
1020impl Lowering<'_> {
1021 fn module(
1026 &self,
1027 lf_module: &lf::Module,
1028 module_dotted: &str,
1029 errors: &mut Vec<SkippedType>,
1030 ) -> Module {
1031 let colliding = self.colliding_flattened_names(lf_module);
1037
1038 let template_names: std::collections::HashSet<String> = lf_module
1040 .templates
1041 .iter()
1042 .filter_map(|def| rust_name(self.package, def.tycon_interned_dname).ok())
1043 .filter(|name| !colliding.contains_key(name))
1044 .collect();
1045
1046 for (flattened, sources) in &colliding {
1047 errors.push(
1048 SkippedType::new(format!(
1049 "`{}` both map to the Rust name `{flattened}`; \
1050 rename one in Daml to generate either",
1051 sources.join("` and `"),
1052 ))
1053 .in_module(module_dotted),
1054 );
1055 }
1056 let skip = |name: &str| colliding.contains_key(name);
1057
1058 let mut module = Module::default();
1059 let mut payloads: HashMap<String, Vec<Field>> = HashMap::new();
1061 let mut seen_types: std::collections::HashSet<String> = std::collections::HashSet::new();
1069 for data_type in &lf_module.data_types {
1070 match self.data_type(data_type) {
1071 Ok(Some(lowered)) if skip(data_type_name(&lowered)) => {}
1072 Ok(Some(lowered)) if !seen_types.insert(data_type_name(&lowered).to_string()) => {
1073 errors.push(
1074 SkippedType::new(format!(
1075 "`{}` is declared more than once in this module",
1076 data_type_name(&lowered)
1077 ))
1078 .in_module(module_dotted),
1079 );
1080 }
1081 Ok(Some(DataType::Record(record))) if template_names.contains(&record.name) => {
1082 payloads.insert(record.name, record.fields);
1083 }
1084 Ok(Some(lowered)) => module.data_types.push(lowered),
1085 Ok(None) => {}
1086 Err(error) => errors.push(error.in_module(module_dotted)),
1087 }
1088 }
1089
1090 for def in &lf_module.templates {
1091 match self.template(def, module_dotted, &mut payloads) {
1092 Ok(template) if skip(&template.name) => {}
1093 Ok(template) => module.templates.push(template),
1094 Err(error) => errors.push(error.in_module(module_dotted)),
1095 }
1096 }
1097
1098 for def in &lf_module.interfaces {
1099 match self.interface(def, module_dotted) {
1100 Ok(interface) if skip(&interface.name) => {}
1101 Ok(interface) => module.interfaces.push(interface),
1102 Err(error) => errors.push(error.in_module(module_dotted)),
1103 }
1104 }
1105 module
1106 }
1107
1108 fn colliding_flattened_names(
1112 &self,
1113 lf_module: &lf::Module,
1114 ) -> std::collections::BTreeMap<String, Vec<String>> {
1115 let declared = lf_module
1119 .data_types
1120 .iter()
1121 .filter(|def| emits_a_type(def))
1122 .map(|def| def.name_interned_dname)
1123 .chain(
1124 lf_module
1125 .templates
1126 .iter()
1127 .map(|def| def.tycon_interned_dname),
1128 )
1129 .chain(
1130 lf_module
1131 .interfaces
1132 .iter()
1133 .map(|def| def.tycon_interned_dname),
1134 );
1135
1136 let mut claims: std::collections::BTreeMap<String, Vec<String>> =
1137 std::collections::BTreeMap::new();
1138 for index in declared {
1139 let Some(dotted) = interned_dotted_name(self.package, index) else {
1140 continue;
1141 };
1142 let flattened = dotted.replace('.', "_");
1143 let sources = claims.entry(flattened).or_default();
1144 if !sources.contains(&dotted) {
1145 sources.push(dotted);
1146 }
1147 }
1148 claims.retain(|_, sources| sources.len() > 1);
1149 claims
1150 }
1151
1152 fn interface(
1156 &self,
1157 def: &lf::DefInterface,
1158 module_dotted: &str,
1159 ) -> Result<Interface, SkippedType> {
1160 let name = rust_name(self.package, def.tycon_interned_dname)?;
1161 let choices = def
1162 .choices
1163 .iter()
1164 .map(|choice| self.choice(choice))
1165 .collect::<Result<Vec<_>, _>>()?;
1166 let view = def
1167 .view
1168 .as_ref()
1169 .map(|ty| self.type_(ty))
1170 .transpose()?
1171 .filter(|view| !matches!(view, DamlType::Unit));
1173 Ok(Interface {
1174 name,
1175 module_name: module_dotted.to_string(),
1176 package_id: self
1177 .qualify
1178 .as_ref()
1179 .map_or_else(String::new, |q| q.current_id.to_string()),
1180 package_name: package_name(self.package).unwrap_or_default().to_string(),
1181 view,
1182 choices,
1183 })
1184 }
1185
1186 fn template(
1189 &self,
1190 def: &lf::DefTemplate,
1191 module_dotted: &str,
1192 payloads: &mut HashMap<String, Vec<Field>>,
1193 ) -> Result<Template, SkippedType> {
1194 let name = rust_name(self.package, def.tycon_interned_dname)?;
1195 let fields = payloads.remove(&name).ok_or_else(|| {
1196 SkippedType::new(format!("template {name}: payload record not found"))
1197 })?;
1198 let choices = def
1199 .choices
1200 .iter()
1201 .map(|choice| self.choice(choice))
1202 .collect::<Result<Vec<_>, _>>()?;
1203 let key = match def.key.as_ref() {
1204 Some(key) => Some(self.type_(key.r#type.as_ref().ok_or_else(|| {
1207 SkippedType::new(format!("template {name}: contract key has no type"))
1208 })?)?),
1209 None => None,
1210 };
1211 Ok(Template {
1212 name,
1213 module_name: module_dotted.to_string(),
1214 package_id: self
1215 .qualify
1216 .as_ref()
1217 .map_or_else(String::new, |q| q.current_id.to_string()),
1218 package_name: package_name(self.package).unwrap_or_default().to_string(),
1219 fields,
1220 choices,
1221 key,
1222 })
1223 }
1224
1225 fn choice(&self, choice: &lf::TemplateChoice) -> Result<Choice, SkippedType> {
1229 let name = interned_str(self.package, choice.name_interned_str)
1230 .ok_or_else(|| SkippedType::new("unresolved choice name"))?
1231 .to_string();
1232 let argument = choice
1233 .arg_binder
1234 .as_ref()
1235 .and_then(|binder| binder.r#type.as_ref())
1236 .ok_or_else(|| SkippedType::new(format!("choice {name}: no argument type")))?;
1237 let returns = choice
1238 .ret_type
1239 .as_ref()
1240 .ok_or_else(|| SkippedType::new(format!("choice {name}: no return type")))?;
1241 Ok(Choice {
1242 name,
1243 consuming: choice.consuming,
1244 argument: self.type_(argument)?,
1245 returns: self.type_(returns)?,
1246 })
1247 }
1248
1249 fn data_type(&self, data_type: &lf::DefDataType) -> Result<Option<DataType>, SkippedType> {
1252 let name = rust_name(self.package, data_type.name_interned_dname)?;
1253
1254 if let Some(lf::def_data_type::DataCons::Interface(_)) = &data_type.data_cons {
1258 return Ok(Some(DataType::InterfaceMarker(name)));
1259 }
1260
1261 if !data_type.serializable {
1264 return Ok(None);
1265 }
1266 let type_params = data_type
1267 .params
1268 .iter()
1269 .map(|param| {
1270 interned_str(self.package, param.var_interned_str)
1271 .map(str::to_string)
1272 .ok_or_else(|| SkippedType::new("unresolved type parameter"))
1273 })
1274 .collect::<Result<Vec<_>, _>>()?;
1275 check_type_params(&name, &type_params)?;
1276
1277 let Some(data_cons) = &data_type.data_cons else {
1278 return Err(SkippedType::new(format!("{name}: no data constructor")));
1279 };
1280
1281 match data_cons {
1282 lf::def_data_type::DataCons::Record(fields) => {
1283 let fields = self.fields(&fields.fields)?;
1284 Ok(Some(DataType::Record(Record {
1285 name,
1286 type_params,
1287 fields,
1288 })))
1289 }
1290 lf::def_data_type::DataCons::Variant(fields) => {
1291 let constructors = fields
1292 .fields
1293 .iter()
1294 .map(|field| self.variant_constructor(field))
1295 .collect::<Result<Vec<_>, _>>()?;
1296 Ok(Some(DataType::Variant(Variant {
1297 name,
1298 type_params,
1299 constructors,
1300 })))
1301 }
1302 lf::def_data_type::DataCons::Enum(constructors) => {
1303 let constructors = constructors
1304 .constructors_interned_str
1305 .iter()
1306 .map(|&index| {
1307 let name = interned_str(self.package, index)
1308 .ok_or_else(|| SkippedType::new("unresolved enum constructor"))?;
1309 if is_rust_ident(name) {
1310 Ok(name.to_string())
1311 } else {
1312 Err(SkippedType::new(format!(
1313 "enum constructor `{name}` is not representable as a Rust identifier"
1314 )))
1315 }
1316 })
1317 .collect::<Result<Vec<_>, _>>()?;
1318 Ok(Some(DataType::Enum(Enum { name, constructors })))
1319 }
1320 lf::def_data_type::DataCons::Interface(_) => Ok(None),
1322 }
1323 }
1324
1325 fn fields(&self, fields: &[lf::FieldWithType]) -> Result<Vec<Field>, SkippedType> {
1326 let lowered = fields
1327 .iter()
1328 .map(|field| {
1329 let label = interned_str(self.package, field.field_interned_str)
1330 .ok_or_else(|| SkippedType::new("unresolved field label"))?
1331 .to_string();
1332 let ty = self.type_(field_type(field)?)?;
1333 Ok(Field { label, ty })
1334 })
1335 .collect::<Result<Vec<Field>, SkippedType>>()?;
1336
1337 let mut by_rust_name: HashMap<String, &str> = HashMap::new();
1341 for field in &lowered {
1342 let rust = heck::ToSnakeCase::to_snake_case(field.label.as_str());
1343 if let Some(previous) = by_rust_name.insert(rust.clone(), &field.label) {
1344 return Err(SkippedType::new(format!(
1345 "fields `{previous}` and `{}` both map to the Rust field `{rust}`",
1346 field.label
1347 )));
1348 }
1349 }
1350 Ok(lowered)
1351 }
1352
1353 fn variant_constructor(
1354 &self,
1355 field: &lf::FieldWithType,
1356 ) -> Result<VariantConstructor, SkippedType> {
1357 let name = interned_str(self.package, field.field_interned_str)
1358 .ok_or_else(|| SkippedType::new("unresolved variant constructor"))?
1359 .to_string();
1360 if !is_rust_ident(&name) {
1361 return Err(SkippedType::new(format!(
1362 "variant constructor `{name}` is not representable as a Rust identifier"
1363 )));
1364 }
1365 let payload = match self.type_(field_type(field)?)? {
1367 DamlType::Unit => None,
1368 other => Some(other),
1369 };
1370 Ok(VariantConstructor { name, payload })
1371 }
1372
1373 fn type_(&self, ty: &lf::Type) -> Result<DamlType, SkippedType> {
1376 self.apply(ty, &[])
1377 }
1378
1379 fn enter(&self) -> Result<DepthGuard<'_>, SkippedType> {
1387 let depth = self.depth.get();
1388 if depth >= MAX_TYPE_DEPTH {
1389 return Err(SkippedType::new(format!(
1390 "type nests deeper than {MAX_TYPE_DEPTH} levels, or its interned \
1391 types refer to each other in a cycle"
1392 )));
1393 }
1394 self.depth.set(depth + 1);
1395 Ok(DepthGuard(&self.depth))
1396 }
1397
1398 fn apply(&self, ty: &lf::Type, extra: &[&lf::Type]) -> Result<DamlType, SkippedType> {
1403 let _guard = self.enter()?;
1406 let Some(sum) = &ty.sum else {
1407 return Err(SkippedType::new("empty type"));
1408 };
1409 match sum {
1410 lf::r#type::Sum::Tapp(app) => {
1411 let lhs = app
1412 .lhs
1413 .as_deref()
1414 .ok_or_else(|| SkippedType::new("type application without a function"))?;
1415 let rhs = app
1416 .rhs
1417 .as_deref()
1418 .ok_or_else(|| SkippedType::new("type application without an argument"))?;
1419 let mut args = Vec::with_capacity(extra.len() + 1);
1420 args.push(rhs);
1421 args.extend_from_slice(extra);
1422 self.apply(lhs, &args)
1423 }
1424 lf::r#type::Sum::Var(var) => {
1425 if !extra.is_empty() || !var.args.is_empty() {
1430 return Err(SkippedType::new("type-variable application is unsupported"));
1431 }
1432 let name = interned_str(self.package, var.var_interned_str)
1433 .ok_or_else(|| SkippedType::new("unresolved type variable"))?
1434 .to_string();
1435 Ok(DamlType::Var(name))
1436 }
1437 lf::r#type::Sum::Con(con) => {
1438 let tycon = con
1439 .tycon
1440 .as_ref()
1441 .ok_or_else(|| SkippedType::new("type constructor without a name"))?;
1442 let path = self.con_path(tycon)?;
1443 let args = con
1444 .args
1445 .iter()
1446 .chain(extra.iter().copied())
1447 .map(|arg| self.type_(arg))
1448 .collect::<Result<Vec<_>, _>>()?;
1449 Ok(DamlType::Ref(TypeRef { path, args }))
1450 }
1451 lf::r#type::Sum::Builtin(builtin) => {
1452 let args: Vec<&lf::Type> =
1453 builtin.args.iter().chain(extra.iter().copied()).collect();
1454 self.builtin(builtin.builtin, &args)
1455 }
1456 lf::r#type::Sum::Interned(index) => {
1457 let resolved = interned_type(self.package, *index)
1458 .ok_or_else(|| SkippedType::new("unresolved interned type"))?;
1459 self.apply(resolved, extra)
1460 }
1461 lf::r#type::Sum::Nat(_) => Err(SkippedType::new("unexpected bare type-level Nat")),
1462 lf::r#type::Sum::Forall(_) | lf::r#type::Sum::Struct(_) | lf::r#type::Sum::Syn(_) => {
1463 Err(SkippedType::new(
1464 "unsupported LF type (forall / struct / syn)",
1465 ))
1466 }
1467 }
1468 }
1469
1470 fn con_path(&self, tycon: &lf::TypeConId) -> Result<Vec<String>, SkippedType> {
1483 use lf::self_or_imported_package_id::Sum;
1484
1485 let type_name = rust_name(self.package, tycon.name_interned_dname)?;
1486 let Some(qualify) = &self.qualify else {
1487 return Ok(vec![type_name]);
1488 };
1489
1490 let module_id = tycon
1491 .module
1492 .as_ref()
1493 .ok_or_else(|| SkippedType::new("type constructor without a module"))?;
1494 let module_dotted =
1495 interned_dotted_name(self.package, module_id.module_name_interned_dname)
1496 .ok_or_else(|| SkippedType::new("unresolved referenced module name"))?;
1497
1498 let target_id = match module_id.package_id.as_ref().and_then(|p| p.sum.as_ref()) {
1501 None | Some(Sum::SelfPackageId(_)) => qualify.current_id.to_string(),
1503 Some(Sum::ImportedPackageIdInternedStr(index)) => interned_str(self.package, *index)
1505 .ok_or_else(|| SkippedType::new("unresolved imported package id"))?
1506 .to_string(),
1507 Some(Sum::PackageImportId(index)) => imported_package_id(self.package, *index)
1510 .ok_or_else(|| SkippedType::new("unresolved package import id"))?
1511 .to_string(),
1512 };
1513 let package_module = qualify
1514 .module_names
1515 .get(target_id.as_str())
1516 .ok_or_else(|| {
1517 SkippedType::new(format!("reference to package {target_id} not in the DAR"))
1518 })?;
1519
1520 if let Some(crate_name) = qualify.external.get(&target_id) {
1525 return Ok(vec![
1530 EXTERNAL_CRATE_ROOT.to_string(),
1531 crate_name.clone(),
1532 package_module.clone(),
1533 module_dotted.replace('.', "_"),
1534 type_name,
1535 ]);
1536 }
1537
1538 Ok(vec![
1539 "crate".to_string(),
1540 package_module.clone(),
1541 module_dotted.replace('.', "_"),
1542 type_name,
1543 ])
1544 }
1545
1546 fn builtin(&self, builtin: i32, args: &[&lf::Type]) -> Result<DamlType, SkippedType> {
1547 use lf::BuiltinType;
1548
1549 let kind = BuiltinType::try_from(builtin)
1550 .map_err(|_| SkippedType::new(format!("unknown builtin type {builtin}")))?;
1551 let arg = |index: usize| -> Result<DamlType, SkippedType> {
1552 let ty = args.get(index).copied().ok_or_else(|| {
1553 SkippedType::new(format!("{kind:?} missing type argument {index}"))
1554 })?;
1555 self.type_(ty)
1556 };
1557
1558 let ty = match kind {
1559 BuiltinType::Unit => DamlType::Unit,
1560 BuiltinType::Bool => DamlType::Bool,
1561 BuiltinType::Int64 => DamlType::Int64,
1562 BuiltinType::Date => DamlType::Date,
1563 BuiltinType::Timestamp => DamlType::Timestamp,
1564 BuiltinType::Party => DamlType::Party,
1565 BuiltinType::Text => DamlType::Text,
1566 BuiltinType::Numeric => DamlType::Numeric(self.numeric_scale(args)?),
1567 BuiltinType::ContractId => DamlType::ContractId(Box::new(arg(0)?)),
1568 BuiltinType::Optional => DamlType::Optional(Box::new(arg(0)?)),
1569 BuiltinType::List => DamlType::List(Box::new(arg(0)?)),
1570 BuiltinType::Textmap => DamlType::TextMap(Box::new(arg(0)?)),
1571 BuiltinType::Genmap => DamlType::GenMap(Box::new(arg(0)?), Box::new(arg(1)?)),
1572 other => {
1573 return Err(SkippedType::new(format!(
1574 "builtin type {other:?} is not representable in codegen"
1575 )));
1576 }
1577 };
1578 Ok(ty)
1579 }
1580
1581 fn numeric_scale(&self, args: &[&lf::Type]) -> Result<u8, SkippedType> {
1583 let mut ty = *args
1584 .first()
1585 .ok_or_else(|| SkippedType::new("Numeric without a scale argument"))?;
1586 if let Some(lf::r#type::Sum::Interned(index)) = &ty.sum {
1588 ty = interned_type(self.package, *index)
1589 .ok_or_else(|| SkippedType::new("unresolved Numeric scale"))?;
1590 }
1591 match &ty.sum {
1592 Some(lf::r#type::Sum::Nat(scale)) => u8::try_from(*scale)
1593 .map_err(|_| SkippedType::new(format!("Numeric scale {scale} out of range"))),
1594 _ => Err(SkippedType::new("Numeric scale is not a type-level Nat")),
1595 }
1596 }
1597}
1598
1599fn box_recursion(krate: &mut Crate) {
1623 let mut edges: HashMap<String, Vec<String>> = HashMap::new();
1625 for package in &krate.packages {
1626 for module in &package.modules {
1627 let at = |name: &str| path_key_for(&package.name, &module.name, name);
1628 module_containment_edges(&module.module, &at, &mut edges);
1629 }
1630 }
1631
1632 for package in &mut krate.packages {
1635 let package_name = package.name.clone();
1636 for module in &mut package.modules {
1637 let module_name = module.name.clone();
1638 let at = |name: &str| path_key_for(&package_name, &module_name, name);
1639 module_box_cycles(&mut module.module, &at, &edges);
1640 }
1641 }
1642}
1643
1644fn module_containment_edges(
1647 module: &Module,
1648 at: &impl Fn(&str) -> String,
1649 edges: &mut HashMap<String, Vec<String>>,
1650) {
1651 for data_type in &module.data_types {
1652 match data_type {
1653 DataType::Record(record) => {
1654 let node = edges.entry(at(&record.name)).or_default();
1655 for field in &record.fields {
1656 collect_inline_refs(&field.ty, node);
1657 }
1658 }
1659 DataType::Variant(variant) => {
1660 let node = edges.entry(at(&variant.name)).or_default();
1661 for constructor in &variant.constructors {
1662 if let Some(payload) = &constructor.payload {
1663 collect_inline_refs(payload, node);
1664 }
1665 }
1666 }
1667 DataType::Enum(_) | DataType::InterfaceMarker(_) => {}
1668 }
1669 }
1670 for template in &module.templates {
1672 let node = edges.entry(at(&template.name)).or_default();
1673 for field in &template.fields {
1674 collect_inline_refs(&field.ty, node);
1675 }
1676 }
1677}
1678
1679fn module_box_cycles(
1681 module: &mut Module,
1682 at: &impl Fn(&str) -> String,
1683 edges: &HashMap<String, Vec<String>>,
1684) {
1685 for data_type in &mut module.data_types {
1686 match data_type {
1687 DataType::Record(record) => {
1688 let holder = at(&record.name);
1689 for field in &mut record.fields {
1690 box_cycle_closers(&mut field.ty, &holder, edges);
1691 }
1692 }
1693 DataType::Variant(variant) => {
1694 let holder = at(&variant.name);
1695 for constructor in &mut variant.constructors {
1696 if let Some(payload) = &mut constructor.payload {
1697 box_cycle_closers(payload, &holder, edges);
1698 }
1699 }
1700 }
1701 DataType::Enum(_) | DataType::InterfaceMarker(_) => {}
1702 }
1703 }
1704 for template in &mut module.templates {
1705 let holder = at(&template.name);
1706 for field in &mut template.fields {
1707 box_cycle_closers(&mut field.ty, &holder, edges);
1708 }
1709 }
1710}
1711
1712fn path_key_for(package_module: &str, module_name: &str, type_name: &str) -> String {
1715 format!("crate::{package_module}::{module_name}::{type_name}")
1716}
1717
1718fn path_key_of(reference: &TypeRef) -> String {
1721 reference.path.join("::")
1722}
1723
1724fn collect_inline_refs(ty: &DamlType, out: &mut Vec<String>) {
1727 match ty {
1728 DamlType::Optional(inner) => collect_inline_refs(inner, out),
1729 DamlType::Ref(reference) => {
1730 out.push(path_key_of(reference));
1731 for arg in &reference.args {
1733 collect_inline_refs(arg, out);
1734 }
1735 }
1736 _ => {}
1739 }
1740}
1741
1742fn inline_reaches(edges: &HashMap<String, Vec<String>>, from: &str, to: &str) -> bool {
1744 let mut stack = vec![from];
1745 let mut seen = std::collections::HashSet::new();
1746 while let Some(node) = stack.pop() {
1747 if node == to {
1748 return true;
1749 }
1750 if seen.insert(node)
1751 && let Some(next) = edges.get(node)
1752 {
1753 stack.extend(next.iter().map(String::as_str));
1754 }
1755 }
1756 false
1757}
1758
1759fn box_cycle_closers(ty: &mut DamlType, holder: &str, edges: &HashMap<String, Vec<String>>) {
1763 match ty {
1764 DamlType::Optional(inner) => box_cycle_closers(inner, holder, edges),
1765 DamlType::Ref(reference) => {
1766 for arg in &mut reference.args {
1767 box_cycle_closers(arg, holder, edges);
1768 }
1769 let key = path_key_of(reference);
1770 if key == holder || inline_reaches(edges, &key, holder) {
1771 let inner = std::mem::replace(ty, DamlType::Unit);
1772 *ty = DamlType::Boxed(Box::new(inner));
1773 }
1774 }
1775 _ => {}
1776 }
1777}
1778
1779fn field_type(field: &lf::FieldWithType) -> Result<&lf::Type, SkippedType> {
1781 field
1782 .r#type
1783 .as_ref()
1784 .ok_or_else(|| SkippedType::new("field without a type"))
1785}
1786
1787fn data_type_name(data_type: &DataType) -> &str {
1789 match data_type {
1790 DataType::Record(record) => &record.name,
1791 DataType::Variant(variant) => &variant.name,
1792 DataType::Enum(enumeration) => &enumeration.name,
1793 DataType::InterfaceMarker(name) => name,
1794 }
1795}
1796
1797fn rust_name(package: &lf::Package, name_interned_dname: i32) -> Result<String, SkippedType> {
1804 let dotted = interned_dotted_name(package, name_interned_dname)
1805 .ok_or_else(|| SkippedType::new("unresolved dotted name"))?;
1806 let name = dotted.replace('.', "_");
1807 if is_rust_ident(&name) {
1808 Ok(name)
1809 } else {
1810 Err(SkippedType::new(format!(
1811 "`{dotted}` is not representable as a Rust identifier"
1812 )))
1813 }
1814}
1815
1816fn check_type_params(name: &str, params: &[String]) -> Result<(), SkippedType> {
1825 let mut seen: std::collections::BTreeMap<String, &str> = std::collections::BTreeMap::new();
1826 for param in params {
1827 let ident = param.to_upper_camel_case();
1828 if !is_rust_ident(&ident) {
1829 return Err(SkippedType::new(format!(
1830 "{name}: type parameter `{param}` is not representable as a Rust \
1831 generic parameter"
1832 )));
1833 }
1834 if let Some(previous) = seen.insert(ident.clone(), param) {
1835 return Err(SkippedType::new(format!(
1836 "{name}: type parameters `{previous}` and `{param}` both map to the \
1837 Rust generic parameter `{ident}`; rename one in Daml"
1838 )));
1839 }
1840 }
1841 Ok(())
1842}
1843
1844fn is_rust_ident(name: &str) -> bool {
1852 !name.is_empty()
1853 && !name.starts_with(|c: char| c.is_ascii_digit())
1854 && name.chars().all(|c| c.is_ascii_alphanumeric() || c == '_')
1855}
1856
1857#[cfg(test)]
1858#[allow(clippy::unwrap_used, clippy::expect_used)]
1859mod tests {
1860 use super::*;
1861 use canton_lf::Dar;
1862
1863 fn package_with(names: &[&str]) -> lf::Package {
1872 let mut strings: Vec<String> = Vec::new();
1873 let mut dotted = Vec::new();
1874 for name in names {
1875 let segments = name
1876 .split('.')
1877 .map(|segment| {
1878 strings.push(segment.to_string());
1879 i32::try_from(strings.len() - 1).unwrap()
1880 })
1881 .collect();
1882 dotted.push(lf::InternedDottedName {
1883 segments_interned_str: segments,
1884 });
1885 }
1886 lf::Package {
1887 interned_strings: strings,
1888 interned_dotted_names: dotted,
1889 ..lf::Package::default()
1890 }
1891 }
1892
1893 fn record_def(name: i32, label: i32) -> lf::DefDataType {
1896 lf::DefDataType {
1897 name_interned_dname: name,
1898 serializable: true,
1899 data_cons: Some(lf::def_data_type::DataCons::Record(
1900 lf::def_data_type::Fields {
1901 fields: vec![lf::FieldWithType {
1902 field_interned_str: label,
1903 r#type: Some(lf::Type {
1904 sum: Some(lf::r#type::Sum::Builtin(lf::r#type::Builtin {
1905 builtin: lf::BuiltinType::Party as i32,
1906 args: vec![],
1907 })),
1908 }),
1909 }],
1910 },
1911 )),
1912 ..lf::DefDataType::default()
1913 }
1914 }
1915
1916 #[test]
1917 fn two_daml_names_that_flatten_alike_are_skipped_not_silently_merged() {
1918 let mut package = package_with(&["Foo.Bar", "Foo_Bar"]);
1924 package.interned_strings.push("owner".to_string());
1925 package.interned_strings.push("note".to_string());
1926 let (owner, note) = (3, 4);
1927
1928 package.modules.push(lf::Module {
1929 name_interned_dname: 1, data_types: vec![record_def(0, note), record_def(1, owner)],
1931 templates: vec![lf::DefTemplate {
1932 tycon_interned_dname: 1,
1933 ..lf::DefTemplate::default()
1934 }],
1935 ..lf::Module::default()
1936 });
1937
1938 let (krate, skipped) = lower_crate(&[("aa".to_string(), package)]);
1939 let reasons: Vec<String> = skipped.iter().map(ToString::to_string).collect();
1940 assert!(
1941 reasons.iter().any(|r| r.contains("Foo.Bar")
1942 && r.contains("Foo_Bar")
1943 && r.contains("map to the Rust name")),
1944 "the clash must be reported naming both Daml sources: {reasons:?}"
1945 );
1946 let emitted: Vec<&str> = krate
1949 .packages
1950 .iter()
1951 .flat_map(|p| &p.modules)
1952 .flat_map(|m| m.module.templates.iter().map(|t| t.name.as_str()))
1953 .collect();
1954 assert!(!emitted.contains(&"Foo_Bar"), "emitted: {emitted:?}");
1955 }
1956
1957 fn named_package(name: &str, version: Option<&str>) -> lf::Package {
1959 lf::Package {
1960 interned_strings: vec![name.to_string(), version.unwrap_or_default().to_string()],
1961 metadata: Some(lf::PackageMetadata {
1962 name_interned_str: 0,
1963 version_interned_str: 1,
1964 upgraded_package_id: None,
1965 }),
1966 ..lf::Package::default()
1967 }
1968 }
1969
1970 fn module_names(packages: &[(&str, lf::Package)]) -> Vec<String> {
1971 let owned: Vec<(String, lf::Package)> = packages
1972 .iter()
1973 .map(|(id, package)| ((*id).to_string(), package.clone()))
1974 .collect();
1975 let names = package_module_names(&owned);
1976 owned
1978 .iter()
1979 .map(|(id, _)| names[id.as_str()].clone())
1980 .collect()
1981 }
1982
1983 #[test]
1984 fn a_package_module_is_named_after_the_package_alone() {
1985 assert_eq!(
1988 module_names(&[
1989 ("aaa", named_package("quickstart-licensing", Some("0.0.1"))),
1990 ("bbb", named_package("splice-amulet", Some("0.1.14"))),
1991 ]),
1992 ["quickstart_licensing", "splice_amulet"],
1993 );
1994 }
1995
1996 #[test]
1997 fn two_versions_of_one_package_are_separated_by_version() {
1998 assert_eq!(
2002 module_names(&[
2003 ("aaa", named_package("my-app", Some("1.0.0"))),
2004 ("bbb", named_package("my-app", Some("2.0.0"))),
2005 ("ccc", named_package("other", Some("1.0.0"))),
2006 ]),
2007 ["my_app_1_0_0", "my_app_2_0_0", "other"],
2008 );
2009 }
2010
2011 #[test]
2012 fn packages_alike_down_to_the_version_fall_back_to_the_id() {
2013 assert_eq!(
2017 module_names(&[
2018 ("aaaaaaaaaaaa", named_package("my-app", Some("1.0.0"))),
2019 ("bbbbbbbbbbbb", named_package("my-app", Some("1.0.0"))),
2020 ("cccccccccccc", named_package("no-version", Some(""))),
2021 ("dddddddddddd", named_package("no-version", None)),
2022 ("eeeeeeeeeeee", lf::Package::default()),
2023 ]),
2024 [
2025 "my_app_1_0_0_aaaaaaaa",
2026 "my_app_1_0_0_bbbbbbbb",
2027 "no_version_cccccccc",
2028 "no_version_dddddddd",
2029 "p_eeeeeeee",
2030 ],
2031 );
2032 }
2033
2034 #[test]
2035 fn ids_sharing_a_prefix_still_get_distinct_module_names() {
2036 let names = module_names(&[
2040 ("aaaaaaaa1111", named_package("my-app", Some("1.0.0"))),
2041 ("aaaaaaaa2222", named_package("my-app", Some("1.0.0"))),
2042 ]);
2043 assert_eq!(names.len(), 2);
2044 assert_ne!(names[0], names[1], "{names:?}");
2045 assert!(
2046 names.iter().all(|n| n.starts_with("my_app_1_0_0_")),
2047 "{names:?}"
2048 );
2049 }
2050
2051 #[test]
2052 fn a_non_serializable_claimant_does_not_take_a_real_type_down() {
2053 let mut package = package_with(&["Mod", "Handler.OnEvent", "Handler_OnEvent"]);
2058 package.interned_strings.push("owner".to_string());
2059 let owner = i32::try_from(package.interned_strings.len() - 1).unwrap();
2060 let mut ghost = record_def(1, owner);
2061 ghost.serializable = false;
2062 package.modules.push(lf::Module {
2063 name_interned_dname: 0,
2064 data_types: vec![ghost, record_def(2, owner)],
2065 ..lf::Module::default()
2066 });
2067
2068 let (krate, skipped) = lower_crate(&[("aa".to_string(), package)]);
2069 assert!(skipped.is_empty(), "nothing collides: {skipped:?}");
2070 let emitted: Vec<&str> = krate.packages[0].modules[0]
2071 .module
2072 .data_types
2073 .iter()
2074 .map(data_type_name)
2075 .collect();
2076 assert_eq!(emitted, ["Handler_OnEvent"]);
2077 }
2078
2079 #[test]
2080 fn a_module_that_emits_nothing_does_not_block_one_that_does() {
2081 let mut package = package_with(&["Util.Text", "Util_Text"]);
2085 package.interned_strings.push("owner".to_string());
2086 let owner = i32::try_from(package.interned_strings.len() - 1).unwrap();
2087 package.modules.push(lf::Module {
2088 name_interned_dname: 0, ..lf::Module::default()
2090 });
2091 package.modules.push(lf::Module {
2092 name_interned_dname: 1, data_types: vec![record_def(1, owner)],
2094 ..lf::Module::default()
2095 });
2096
2097 let (krate, skipped) = lower_crate(&[("aa".to_string(), package)]);
2098 assert!(skipped.is_empty(), "nothing collides: {skipped:?}");
2099 let modules: Vec<&str> = krate.packages[0]
2100 .modules
2101 .iter()
2102 .map(|m| m.name.as_str())
2103 .collect();
2104 assert_eq!(modules, ["Util_Text"]);
2105 }
2106
2107 #[test]
2108 fn two_type_parameters_that_upper_camel_alike_are_skipped() {
2109 let mut package = package_with(&["Mod", "Pair"]);
2113 let base = i32::try_from(package.interned_strings.len()).unwrap();
2114 package.interned_strings.push("a".to_string());
2115 package.interned_strings.push("A".to_string());
2116 let mut pair = record_def(1, base);
2117 pair.params = vec![
2118 lf::TypeVarWithKind {
2119 var_interned_str: base,
2120 kind: None,
2121 },
2122 lf::TypeVarWithKind {
2123 var_interned_str: base + 1,
2124 kind: None,
2125 },
2126 ];
2127 package.modules.push(lf::Module {
2128 name_interned_dname: 0,
2129 data_types: vec![pair],
2130 ..lf::Module::default()
2131 });
2132
2133 let (krate, skipped) = lower_crate(&[("aa".to_string(), package)]);
2134 assert!(
2135 skipped
2136 .iter()
2137 .any(|s| s.to_string().contains("Rust generic parameter `A`")),
2138 "{skipped:?}"
2139 );
2140 assert!(krate.packages.is_empty(), "nothing emitted");
2141 }
2142
2143 #[test]
2144 fn the_same_package_id_twice_yields_one_module() {
2145 let mut package = package_with(&["Mod", "T"]);
2148 package.interned_strings.push("owner".to_string());
2149 let owner = i32::try_from(package.interned_strings.len() - 1).unwrap();
2150 package.modules.push(lf::Module {
2151 name_interned_dname: 0,
2152 data_types: vec![record_def(1, owner)],
2153 ..lf::Module::default()
2154 });
2155
2156 let (krate, _) = lower_crate(&[
2157 ("aa".to_string(), package.clone()),
2158 ("aa".to_string(), package),
2159 ]);
2160 let names: Vec<&str> = krate.packages.iter().map(|p| p.name.as_str()).collect();
2161 assert_eq!(names.len(), 1, "{names:?}");
2162 }
2163
2164 #[test]
2165 fn two_module_names_that_flatten_alike_are_skipped() {
2166 let mut package = package_with(&["A.B", "A_B", "T"]);
2169 package.interned_strings.push("owner".to_string());
2170 let owner = i32::try_from(package.interned_strings.len() - 1).unwrap();
2171 for name in [0, 1] {
2172 package.modules.push(lf::Module {
2173 name_interned_dname: name,
2174 data_types: vec![record_def(2, owner)],
2175 ..lf::Module::default()
2176 });
2177 }
2178
2179 let (krate, skipped) = lower_crate(&[("aa".to_string(), package)]);
2180 assert!(
2181 skipped
2182 .iter()
2183 .any(|s| s.to_string().contains("map to the Rust module")),
2184 "{skipped:?}"
2185 );
2186 let modules: Vec<&str> = krate
2187 .packages
2188 .iter()
2189 .flat_map(|p| p.modules.iter().map(|m| m.name.as_str()))
2190 .collect();
2191 assert!(modules.is_empty(), "no module may be emitted: {modules:?}");
2192 }
2193
2194 #[test]
2195 fn an_optional_passed_to_a_generic_that_nests_it_is_refused_not_mis_encoded() {
2196 let reference = |name: &str, args: Vec<DamlType>| {
2202 DamlType::Ref(TypeRef {
2203 path: vec![
2204 "crate".to_string(),
2205 "pkg_1_0_0".to_string(),
2206 "Mod".to_string(),
2207 name.to_string(),
2208 ],
2209 args,
2210 })
2211 };
2212 let field = |label: &str, ty: DamlType| Field {
2213 label: label.to_string(),
2214 ty,
2215 };
2216 let module = |data_types: Vec<DataType>| Crate {
2217 packages: vec![PackageModule {
2218 name: "pkg_1_0_0".to_string(),
2219 modules: vec![NamedModule {
2220 name: "Mod".to_string(),
2221 module: Module {
2222 data_types,
2223 ..Module::default()
2224 },
2225 }],
2226 }],
2227 };
2228
2229 let wrap_nesting = DataType::Record(Record {
2231 name: "Wrap".to_string(),
2232 type_params: vec!["a".to_string()],
2233 fields: vec![field(
2234 "value",
2235 DamlType::Optional(Box::new(DamlType::Var("a".to_string()))),
2236 )],
2237 });
2238 let user = |arg: DamlType| {
2239 DataType::Record(Record {
2240 name: "User".to_string(),
2241 type_params: vec![],
2242 fields: vec![field("w", reference("Wrap", vec![arg]))],
2243 })
2244 };
2245
2246 let mut krate = module(vec![
2247 wrap_nesting.clone(),
2248 user(DamlType::Optional(Box::new(DamlType::Text))),
2249 ]);
2250 let mut errors = Vec::new();
2251 drop_ambiguous_nested_optionals(&mut krate, &mut errors);
2252 assert!(
2253 errors
2254 .iter()
2255 .any(|e| e.to_string().contains("not yet representable")),
2256 "{errors:?}"
2257 );
2258 let names: Vec<&str> = krate.packages[0].modules[0]
2259 .module
2260 .data_types
2261 .iter()
2262 .map(data_type_name)
2263 .collect();
2264 assert_eq!(names, ["Wrap"], "only the ambiguous user is dropped");
2265
2266 let mut fine = module(vec![wrap_nesting, user(DamlType::Text)]);
2268 let mut errors = Vec::new();
2269 drop_ambiguous_nested_optionals(&mut fine, &mut errors);
2270 assert!(errors.is_empty(), "{errors:?}");
2271
2272 let wrap_plain = DataType::Record(Record {
2275 name: "Wrap".to_string(),
2276 type_params: vec!["a".to_string()],
2277 fields: vec![field("value", DamlType::Var("a".to_string()))],
2278 });
2279 let mut plain = module(vec![
2280 wrap_plain,
2281 user(DamlType::Optional(Box::new(DamlType::Text))),
2282 ]);
2283 let mut errors = Vec::new();
2284 drop_ambiguous_nested_optionals(&mut plain, &mut errors);
2285 assert!(errors.is_empty(), "{errors:?}");
2286 assert_eq!(plain.packages[0].modules[0].module.data_types.len(), 2);
2287 }
2288
2289 #[test]
2290 fn nesting_introduced_through_an_intermediate_generic_is_still_refused() {
2291 let mut krate = generics_module(vec![
2300 generic_record("Wrap", &["a"], vec![("value", optional(var("a")))]),
2301 generic_record(
2302 "Outer",
2303 &["a"],
2304 vec![("inner", reference("Wrap", vec![var("a")]))],
2305 ),
2306 generic_record(
2307 "User",
2308 &[],
2309 vec![("o", reference("Outer", vec![optional(DamlType::Text)]))],
2310 ),
2311 ]);
2312 let mut errors = Vec::new();
2313 drop_ambiguous_nested_optionals(&mut krate, &mut errors);
2314 assert!(
2315 errors
2316 .iter()
2317 .any(|e| e.to_string().contains("not yet representable")),
2318 "the transitive nesting must be refused, not mis-encoded: {errors:?}"
2319 );
2320 let names: Vec<&str> = krate.packages[0].modules[0]
2321 .module
2322 .data_types
2323 .iter()
2324 .map(data_type_name)
2325 .collect();
2326 assert_eq!(
2327 names,
2328 ["Wrap", "Outer"],
2329 "only the instantiation site is dropped"
2330 );
2331 }
2332
2333 #[test]
2334 fn mutually_recursive_generics_reach_a_fixpoint() {
2335 let mut krate = generics_module(vec![
2343 generic_record("Ping", &["a"], vec![("p", optional(var("a")))]),
2344 generic_record(
2345 "Pong",
2346 &["a"],
2347 vec![
2348 ("q", reference("Ping", vec![var("a")])),
2349 ("r", optional(reference("Pong", vec![var("a")]))),
2350 ],
2351 ),
2352 generic_record(
2353 "User",
2354 &[],
2355 vec![("o", reference("Pong", vec![optional(DamlType::Text)]))],
2356 ),
2357 ]);
2358 let mut errors = Vec::new();
2359 drop_ambiguous_nested_optionals(&mut krate, &mut errors);
2360 assert!(
2361 errors
2362 .iter()
2363 .any(|e| e.to_string().contains("not yet representable")),
2364 "{errors:?}"
2365 );
2366 }
2367
2368 #[test]
2369 fn a_container_between_the_two_optionals_is_not_a_nested_optional() {
2370 for container in [
2378 DamlType::List(Box::new(var("a"))),
2379 DamlType::TextMap(Box::new(var("a"))),
2380 DamlType::GenMap(Box::new(DamlType::Text), Box::new(var("a"))),
2381 ] {
2382 let mut krate = generics_module(vec![
2383 generic_record("Wrap", &["a"], vec![("value", optional(container))]),
2384 generic_record(
2385 "User",
2386 &[],
2387 vec![("w", reference("Wrap", vec![optional(DamlType::Text)]))],
2388 ),
2389 ]);
2390 let mut errors = Vec::new();
2391 drop_ambiguous_nested_optionals(&mut krate, &mut errors);
2392 assert!(errors.is_empty(), "must not be refused: {errors:?}");
2393 assert_eq!(krate.packages[0].modules[0].module.data_types.len(), 2);
2394 }
2395 }
2396
2397 #[test]
2398 fn dropping_an_interface_keeps_its_marker_reachable() {
2399 let mut krate = Crate {
2404 packages: vec![PackageModule {
2405 name: "pkg".to_string(),
2406 modules: vec![NamedModule {
2407 name: "Mod".to_string(),
2408 module: Module {
2409 data_types: vec![DataType::InterfaceMarker("Holding".to_string())],
2410 templates: vec![Template {
2411 name: "Vault".to_string(),
2412 module_name: "Mod".to_string(),
2413 package_id: "id".to_string(),
2414 package_name: "pkg".to_string(),
2415 fields: vec![Field {
2416 label: "held".to_string(),
2417 ty: DamlType::ContractId(Box::new(reference("Holding", vec![]))),
2418 }],
2419 choices: vec![],
2420 key: None,
2421 }],
2422 interfaces: vec![Interface {
2423 name: "Holding".to_string(),
2424 module_name: "Mod".to_string(),
2425 package_id: "id".to_string(),
2426 package_name: "pkg".to_string(),
2427 view: Some(reference("HoldingView", vec![])),
2429 choices: vec![],
2430 }],
2431 },
2432 }],
2433 }],
2434 };
2435
2436 let mut errors = Vec::new();
2437 drop_dangling_references(&mut krate, &mut errors);
2438
2439 let module = &krate.packages[0].modules[0].module;
2440 assert!(module.interfaces.is_empty(), "the interface itself must go");
2441 assert_eq!(
2442 module.data_types.len(),
2443 1,
2444 "the marker is still emitted: {:?}",
2445 module
2446 .data_types
2447 .iter()
2448 .map(data_type_name)
2449 .collect::<Vec<_>>()
2450 );
2451 let templates: Vec<&str> = module.templates.iter().map(|t| t.name.as_str()).collect();
2452 assert_eq!(
2453 templates,
2454 ["Vault"],
2455 "a ContractId of the marker still resolves; reasons: {errors:?}"
2456 );
2457 }
2458
2459 #[test]
2460 fn a_contract_id_type_argument_is_not_a_value_position() {
2461 let mut krate = generics_module(vec![
2465 generic_record("Wrap", &["a"], vec![("value", optional(var("a")))]),
2466 generic_record(
2467 "Outer",
2468 &["a"],
2469 vec![(
2470 "c",
2471 DamlType::ContractId(Box::new(reference("Wrap", vec![var("a")]))),
2472 )],
2473 ),
2474 generic_record(
2475 "User",
2476 &[],
2477 vec![("o", reference("Outer", vec![optional(DamlType::Text)]))],
2478 ),
2479 ]);
2480 let mut errors = Vec::new();
2481 drop_ambiguous_nested_optionals(&mut krate, &mut errors);
2482 assert!(errors.is_empty(), "must not be refused: {errors:?}");
2483 assert_eq!(krate.packages[0].modules[0].module.data_types.len(), 3);
2484 }
2485
2486 #[test]
2487 fn a_parameter_handed_on_inside_a_container_does_not_propagate() {
2488 let mut krate = generics_module(vec![
2493 generic_record("Wrap", &["b"], vec![("value", optional(var("b")))]),
2494 generic_record(
2495 "Outer",
2496 &["a"],
2497 vec![(
2498 "inner",
2499 reference("Wrap", vec![DamlType::List(Box::new(var("a")))]),
2500 )],
2501 ),
2502 generic_record(
2503 "User",
2504 &[],
2505 vec![("o", reference("Outer", vec![optional(DamlType::Text)]))],
2506 ),
2507 ]);
2508 let mut errors = Vec::new();
2509 drop_ambiguous_nested_optionals(&mut krate, &mut errors);
2510 assert!(errors.is_empty(), "must not be refused: {errors:?}");
2511 }
2512
2513 fn var(name: &str) -> DamlType {
2515 DamlType::Var(name.to_string())
2516 }
2517
2518 fn optional(ty: DamlType) -> DamlType {
2519 DamlType::Optional(Box::new(ty))
2520 }
2521
2522 fn reference(name: &str, args: Vec<DamlType>) -> DamlType {
2523 DamlType::Ref(TypeRef {
2524 path: vec![
2525 "crate".to_string(),
2526 "pkg".to_string(),
2527 "Mod".to_string(),
2528 name.to_string(),
2529 ],
2530 args,
2531 })
2532 }
2533
2534 fn generic_record(name: &str, params: &[&str], fields: Vec<(&str, DamlType)>) -> DataType {
2535 DataType::Record(Record {
2536 name: name.to_string(),
2537 type_params: params.iter().map(|p| (*p).to_string()).collect(),
2538 fields: fields
2539 .into_iter()
2540 .map(|(label, ty)| Field {
2541 label: label.to_string(),
2542 ty,
2543 })
2544 .collect(),
2545 })
2546 }
2547
2548 fn generics_module(data_types: Vec<DataType>) -> Crate {
2549 Crate {
2550 packages: vec![PackageModule {
2551 name: "pkg".to_string(),
2552 modules: vec![NamedModule {
2553 name: "Mod".to_string(),
2554 module: Module {
2555 data_types,
2556 ..Module::default()
2557 },
2558 }],
2559 }],
2560 }
2561 }
2562
2563 #[test]
2569 fn both_post_lowering_passes_cover_templates_and_interfaces() {
2570 let reference = |name: &str, args: Vec<DamlType>| {
2571 DamlType::Ref(TypeRef {
2572 path: vec![
2573 "crate".to_string(),
2574 "pkg_1_0_0".to_string(),
2575 "Mod".to_string(),
2576 name.to_string(),
2577 ],
2578 args,
2579 })
2580 };
2581 let module_of = |data_types: Vec<DataType>,
2582 templates: Vec<Template>,
2583 interfaces: Vec<Interface>| Crate {
2584 packages: vec![PackageModule {
2585 name: "pkg_1_0_0".to_string(),
2586 modules: vec![NamedModule {
2587 name: "Mod".to_string(),
2588 module: Module {
2589 data_types,
2590 templates,
2591 interfaces,
2592 },
2593 }],
2594 }],
2595 };
2596 let choice = |ty: DamlType| Choice {
2597 name: "Do".to_string(),
2598 consuming: true,
2599 argument: DamlType::Unit,
2600 returns: ty,
2601 };
2602 let template_of = |name: &str, field: DamlType| Template {
2603 name: name.to_string(),
2604 module_name: "Mod".to_string(),
2605 package_id: "aa".to_string(),
2606 package_name: "pkg".to_string(),
2607 fields: vec![Field {
2608 label: "x".to_string(),
2609 ty: field,
2610 }],
2611 choices: vec![],
2612 key: None,
2613 };
2614
2615 let mut krate = module_of(
2619 vec![DataType::InterfaceMarker("Iface".to_string())],
2620 vec![],
2621 vec![Interface {
2622 name: "Iface".to_string(),
2623 module_name: "Mod".to_string(),
2624 package_id: "aa".to_string(),
2625 package_name: "pkg".to_string(),
2626 view: None,
2627 choices: vec![choice(reference("Bad", vec![]))], }],
2629 );
2630 let mut errors = Vec::new();
2631 drop_dangling_references(&mut krate, &mut errors);
2632 assert!(
2633 errors
2634 .iter()
2635 .any(|e| e.to_string().contains("`Iface` references `Bad`")),
2636 "an interface dangles like anything else: {errors:?}"
2637 );
2638 assert!(
2639 krate.packages.is_empty() || krate.packages[0].modules[0].module.interfaces.is_empty(),
2640 "the interface must not be emitted"
2641 );
2642
2643 let wrap = DataType::Record(Record {
2647 name: "Wrap".to_string(),
2648 type_params: vec!["a".to_string()],
2649 fields: vec![Field {
2650 label: "value".to_string(),
2651 ty: DamlType::Optional(Box::new(DamlType::Var("a".to_string()))),
2652 }],
2653 });
2654 let mut krate = module_of(
2655 vec![wrap],
2656 vec![template_of(
2657 "Tpl",
2658 reference("Wrap", vec![DamlType::Optional(Box::new(DamlType::Text))]),
2659 )],
2660 vec![],
2661 );
2662 let mut errors = Vec::new();
2663 drop_ambiguous_nested_optionals(&mut krate, &mut errors);
2664 assert!(
2665 errors
2666 .iter()
2667 .any(|e| e.to_string().contains("not yet representable")),
2668 "a template payload is an instantiation site too: {errors:?}"
2669 );
2670 assert!(
2671 krate.packages[0].modules[0].module.templates.is_empty(),
2672 "the template must not be emitted"
2673 );
2674 }
2675
2676 #[test]
2677 fn a_type_referencing_a_skipped_type_is_dropped_transitively() {
2678 let path = |name: &str| {
2683 DamlType::Ref(TypeRef {
2684 path: vec![
2685 "crate".to_string(),
2686 "pkg_1_0_0".to_string(),
2687 "Mod".to_string(),
2688 name.to_string(),
2689 ],
2690 args: vec![],
2691 })
2692 };
2693 let record = |name: &str, field: DamlType| {
2694 DataType::Record(Record {
2695 name: name.to_string(),
2696 type_params: vec![],
2697 fields: vec![Field {
2698 label: "x".to_string(),
2699 ty: field,
2700 }],
2701 })
2702 };
2703 let mut krate = Crate {
2704 packages: vec![PackageModule {
2705 name: "pkg_1_0_0".to_string(),
2706 modules: vec![NamedModule {
2707 name: "Mod".to_string(),
2708 module: Module {
2709 data_types: vec![
2711 record("Good", path("Bad")),
2712 record("Worse", path("Good")),
2713 record("Fine", DamlType::Text),
2714 ],
2715 ..Module::default()
2716 },
2717 }],
2718 }],
2719 };
2720
2721 let mut errors = Vec::new();
2722 drop_dangling_references(&mut krate, &mut errors);
2723
2724 let surviving: Vec<&str> = krate.packages[0].modules[0]
2725 .module
2726 .data_types
2727 .iter()
2728 .map(data_type_name)
2729 .collect();
2730 assert_eq!(surviving, ["Fine"], "only the self-contained type survives");
2731
2732 let reasons: Vec<String> = errors.iter().map(ToString::to_string).collect();
2733 assert!(
2734 reasons
2735 .iter()
2736 .any(|r| r.contains("`Good` references `Bad`")),
2737 "{reasons:?}"
2738 );
2739 assert!(
2740 reasons
2741 .iter()
2742 .any(|r| r.contains("`Worse` references `Good`")),
2743 "the drop must cascade: {reasons:?}"
2744 );
2745 }
2746
2747 #[test]
2748 fn a_daml_name_rust_cannot_spell_is_skipped_not_panicked_on() {
2749 assert!(is_rust_ident("Red"));
2754 assert!(is_rust_ident("_x9"));
2755 for bad in ["Red'", "", "9lives", "naïve", "a-b", "A.B"] {
2756 assert!(!is_rust_ident(bad), "{bad:?} is not a Rust identifier");
2757 }
2758
2759 let mut package = package_with(&["Colour"]);
2762 package.interned_strings.push("Red'".to_string());
2763 let constructor = i32::try_from(package.interned_strings.len() - 1).unwrap();
2764 package.modules.push(lf::Module {
2765 name_interned_dname: 0,
2766 data_types: vec![lf::DefDataType {
2767 name_interned_dname: 0,
2768 serializable: true,
2769 data_cons: Some(lf::def_data_type::DataCons::Enum(
2770 lf::def_data_type::EnumConstructors {
2771 constructors_interned_str: vec![constructor],
2772 },
2773 )),
2774 ..lf::DefDataType::default()
2775 }],
2776 ..lf::Module::default()
2777 });
2778
2779 let (krate, skipped) = lower_crate(&[("aa".to_string(), package)]);
2780 assert!(
2781 skipped
2782 .iter()
2783 .any(|s| s.to_string().contains("enum constructor `Red'`")),
2784 "{skipped:?}"
2785 );
2786 assert!(krate.packages.is_empty(), "nothing may be emitted");
2787 }
2788
2789 #[test]
2790 fn the_collision_suffix_is_never_empty() {
2791 assert_ne!(ident_prefix("€€€€"), ident_prefix("£££££"));
2794 assert!(!ident_prefix("€€€€").is_empty());
2795 assert_eq!(ident_prefix("aabbccddee"), "aabbccdd");
2796 }
2797
2798 fn qualified(name: &str) -> DamlType {
2801 DamlType::Ref(TypeRef {
2802 path: vec![
2803 "crate".to_string(),
2804 "pkg_1_0_0".to_string(),
2805 "Mod".to_string(),
2806 name.to_string(),
2807 ],
2808 args: vec![],
2809 })
2810 }
2811
2812 fn record(name: &str, fields: Vec<(&str, DamlType)>) -> DataType {
2813 DataType::Record(Record {
2814 name: name.to_string(),
2815 type_params: vec![],
2816 fields: fields
2817 .into_iter()
2818 .map(|(label, ty)| Field {
2819 label: label.to_string(),
2820 ty,
2821 })
2822 .collect(),
2823 })
2824 }
2825
2826 fn crate_of(data_types: Vec<DataType>) -> Crate {
2827 Crate {
2828 packages: vec![PackageModule {
2829 name: "pkg_1_0_0".to_string(),
2830 modules: vec![NamedModule {
2831 name: "Mod".to_string(),
2832 module: Module {
2833 data_types,
2834 ..Module::default()
2835 },
2836 }],
2837 }],
2838 }
2839 }
2840
2841 fn field_ty(krate: &Crate, type_index: usize, field_index: usize) -> &DamlType {
2842 match &krate.packages[0].modules[0].module.data_types[type_index] {
2843 DataType::Record(record) => &record.fields[field_index].ty,
2844 other => panic!("expected a record, got {other:?}"),
2845 }
2846 }
2847
2848 #[test]
2849 fn optional_self_recursion_is_boxed() {
2850 let mut krate = crate_of(vec![record(
2853 "Tree",
2854 vec![
2855 ("left", DamlType::Optional(Box::new(qualified("Tree")))),
2856 ("size", DamlType::Int64),
2857 ],
2858 )]);
2859 box_recursion(&mut krate);
2860 assert_eq!(
2861 field_ty(&krate, 0, 0),
2862 &DamlType::Optional(Box::new(DamlType::Boxed(Box::new(qualified("Tree"))))),
2863 );
2864 assert_eq!(field_ty(&krate, 0, 1), &DamlType::Int64);
2866 }
2867
2868 #[test]
2869 fn mutual_recursion_is_boxed() {
2870 let mut krate = crate_of(vec![
2873 record("A", vec![("b", qualified("B"))]),
2874 record(
2875 "B",
2876 vec![("a", DamlType::Optional(Box::new(qualified("A"))))],
2877 ),
2878 ]);
2879 box_recursion(&mut krate);
2880 assert_eq!(
2881 field_ty(&krate, 0, 0),
2882 &DamlType::Boxed(Box::new(qualified("B")))
2883 );
2884 assert_eq!(
2885 field_ty(&krate, 1, 0),
2886 &DamlType::Optional(Box::new(DamlType::Boxed(Box::new(qualified("A"))))),
2887 );
2888 }
2889
2890 #[test]
2891 fn generic_instantiation_recursion_is_boxed() {
2892 let wrap_of_t = DamlType::Ref(TypeRef {
2895 path: vec![
2896 "crate".to_string(),
2897 "pkg_1_0_0".to_string(),
2898 "Mod".to_string(),
2899 "Wrap".to_string(),
2900 ],
2901 args: vec![qualified("T")],
2902 });
2903 let mut krate = crate_of(vec![
2904 DataType::Record(Record {
2905 name: "Wrap".to_string(),
2906 type_params: vec!["a".to_string()],
2907 fields: vec![Field {
2908 label: "w".to_string(),
2909 ty: DamlType::Var("a".to_string()),
2910 }],
2911 }),
2912 record("T", vec![("w", wrap_of_t)]),
2913 ]);
2914 box_recursion(&mut krate);
2915 let DamlType::Ref(reference) = field_ty(&krate, 1, 0) else {
2916 panic!("outer Wrap reference must stay a Ref");
2917 };
2918 assert_eq!(
2919 reference.args[0],
2920 DamlType::Boxed(Box::new(qualified("T"))),
2921 "the recursive argument occurrence is boxed"
2922 );
2923 }
2924
2925 #[test]
2926 fn heap_containers_already_break_cycles() {
2927 let mut krate = crate_of(vec![record(
2929 "Tree",
2930 vec![
2931 ("children", DamlType::List(Box::new(qualified("Tree")))),
2932 ("index", DamlType::TextMap(Box::new(qualified("Tree")))),
2933 ],
2934 )]);
2935 let before = krate.clone();
2936 box_recursion(&mut krate);
2937 assert_eq!(krate, before, "heap-indirected recursion is left alone");
2938 }
2939
2940 #[test]
2941 fn lowers_a_real_dar_to_a_qualified_crate() {
2942 let Ok(path) = std::env::var("CANTON_TEST_DAR") else {
2944 eprintln!("skipping lower test: set CANTON_TEST_DAR=/path/to/x.dar");
2945 return;
2946 };
2947
2948 let dar = Dar::open(&path).expect("open DAR");
2949 let (krate, errors) = lower_dar(&dar).expect("decode + lower DAR");
2950
2951 assert!(
2952 !krate.packages.is_empty(),
2953 "a DAR should lower at least one package"
2954 );
2955
2956 let unresolved: Vec<&SkippedType> = errors
2960 .iter()
2961 .filter(|error| error.reason().contains("not in the DAR"))
2962 .collect();
2963 assert!(
2964 unresolved.is_empty(),
2965 "every cross-package reference should resolve: {unresolved:?}"
2966 );
2967
2968 let src = crate::generate_crate(&krate).expect("generate crate");
2970 syn::parse_file(&src).expect("generated source must be valid Rust");
2971
2972 let modules: usize = krate.packages.iter().map(|p| p.modules.len()).sum();
2973 let types: usize = krate
2974 .packages
2975 .iter()
2976 .flat_map(|p| &p.modules)
2977 .map(|m| m.module.data_types.len())
2978 .sum();
2979 println!(
2980 "lowered {} packages / {modules} modules / {types} data types → {} bytes of Rust ({} skipped)",
2981 krate.packages.len(),
2982 src.len(),
2983 errors.len(),
2984 );
2985 }
2986
2987 #[test]
2997 fn an_interned_type_cycle_is_refused_instead_of_overflowing_the_stack() {
2998 let mut package = package_with(&["M.T"]);
2999 package.interned_strings.push("f".to_string());
3000 let label = i32::try_from(package.interned_strings.len() - 1).unwrap();
3001 package.interned_types = vec![lf::Type {
3003 sum: Some(lf::r#type::Sum::Interned(0)),
3004 }];
3005 let mut def = record_def(0, label);
3006 if let Some(lf::def_data_type::DataCons::Record(fields)) = &mut def.data_cons {
3007 fields.fields[0].r#type = Some(lf::Type {
3008 sum: Some(lf::r#type::Sum::Interned(0)),
3009 });
3010 }
3011 package.modules = vec![lf::Module {
3012 name_interned_dname: 0,
3013 data_types: vec![def],
3014 ..lf::Module::default()
3015 }];
3016
3017 let (krate, skipped) = lower_crate(&[("aa".to_string(), package)]);
3020 assert!(
3021 skipped.iter().any(|s| s.to_string().contains("cycle")),
3022 "should be reported as a skipped type: {skipped:?}"
3023 );
3024 let emitted: usize = krate
3026 .packages
3027 .iter()
3028 .flat_map(|p| &p.modules)
3029 .map(|m| m.module.data_types.len())
3030 .sum();
3031 assert_eq!(
3032 emitted, 0,
3033 "a type that could not be resolved is not emitted"
3034 );
3035 }
3036
3037 #[test]
3041 fn an_indirect_interned_cycle_is_refused_too() {
3042 let mut package = package_with(&["M.T"]);
3043 package.interned_strings.push("f".to_string());
3044 let label = i32::try_from(package.interned_strings.len() - 1).unwrap();
3045 package.interned_types = vec![
3046 lf::Type {
3047 sum: Some(lf::r#type::Sum::Interned(1)),
3048 },
3049 lf::Type {
3050 sum: Some(lf::r#type::Sum::Interned(0)),
3051 },
3052 ];
3053 let mut def = record_def(0, label);
3054 if let Some(lf::def_data_type::DataCons::Record(fields)) = &mut def.data_cons {
3055 fields.fields[0].r#type = Some(lf::Type {
3056 sum: Some(lf::r#type::Sum::Interned(0)),
3057 });
3058 }
3059 package.modules = vec![lf::Module {
3060 name_interned_dname: 0,
3061 data_types: vec![def],
3062 ..lf::Module::default()
3063 }];
3064
3065 let (_krate, skipped) = lower_crate(&[("aa".to_string(), package)]);
3066 assert!(
3067 skipped.iter().any(|s| s.to_string().contains("cycle")),
3068 "{skipped:?}"
3069 );
3070 }
3071 #[test]
3081 fn a_name_declared_twice_is_refused_rather_than_silently_resolved() {
3082 let mut package = package_with(&["M", "T"]);
3083 package.interned_strings.push("a".to_string());
3084 let a = i32::try_from(package.interned_strings.len() - 1).unwrap();
3085 package.interned_strings.push("b".to_string());
3086 let b = i32::try_from(package.interned_strings.len() - 1).unwrap();
3087
3088 package.modules = vec![lf::Module {
3089 name_interned_dname: 0,
3090 data_types: vec![record_def(1, a), record_def(1, b)],
3091 templates: vec![lf::DefTemplate {
3092 tycon_interned_dname: 1,
3093 ..lf::DefTemplate::default()
3094 }],
3095 ..lf::Module::default()
3096 }];
3097
3098 let (krate, skipped) = lower_crate(&[("aa".to_string(), package)]);
3099 let reasons: Vec<String> = skipped.iter().map(ToString::to_string).collect();
3100 assert!(
3101 reasons
3102 .iter()
3103 .any(|r| r.contains("declared more than once")),
3104 "the duplicate must be reported: {reasons:?}"
3105 );
3106 let templates: Vec<&str> = krate
3108 .packages
3109 .iter()
3110 .flat_map(|p| &p.modules)
3111 .flat_map(|m| &m.module.templates)
3112 .flat_map(|t| t.fields.iter().map(|f| f.label.as_str()))
3113 .collect();
3114 assert!(
3115 templates.is_empty() || templates == ["a"],
3116 "a template must not silently take the second declaration's fields: {templates:?}"
3117 );
3118 }
3119
3120 #[test]
3124 fn a_module_declared_twice_is_refused() {
3125 let mut package = package_with(&["M"]);
3126 package.interned_strings.push("f".to_string());
3127 let label = i32::try_from(package.interned_strings.len() - 1).unwrap();
3128 let module = lf::Module {
3129 name_interned_dname: 0,
3130 data_types: vec![record_def(0, label)],
3131 ..lf::Module::default()
3132 };
3133 package.modules = vec![module.clone(), module];
3134
3135 let (krate, skipped) = lower_crate(&[("aa".to_string(), package)]);
3136 let reasons: Vec<String> = skipped.iter().map(ToString::to_string).collect();
3137 assert!(
3138 reasons
3139 .iter()
3140 .any(|r| r.contains("declared more than once")),
3141 "{reasons:?}"
3142 );
3143 let modules: usize = krate.packages.iter().map(|p| p.modules.len()).sum();
3144 assert_eq!(modules, 1, "only one module may be emitted");
3145 }
3146}