1use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet, VecDeque};
6use std::vec::Vec;
7
8use proc_macro2::TokenStream;
9use quote::{format_ident, quote};
10use syn::Ident;
11
12use crate::etypes::{
13 BoundedTyvar, Defined, ExternDecl, ExternDesc, Handleable, ImportExport, TypeBound, Tyvar,
14};
15
16fn version_to_kebab(version: &[&str]) -> String {
17 version
18 .join("-")
19 .chars()
20 .map(|c| {
21 if c.is_ascii_alphanumeric() {
22 c.to_ascii_lowercase()
23 } else {
24 '-'
25 }
26 })
27 .collect()
28}
29
30pub fn find_colliding_import_names(imports: &[ExternDecl]) -> HashSet<String> {
33 let mut counts = HashMap::<String, usize>::new();
34 for ed in imports {
35 if let ExternDesc::Instance(_) = &ed.desc {
36 let wn = split_wit_name(ed.kebab_name);
37 *counts.entry(wn.name.to_string()).or_default() += 1;
38 }
39 }
40 counts
41 .into_iter()
42 .filter(|(_, c)| *c > 1)
43 .map(|(n, _)| n)
44 .collect()
45}
46
47pub fn import_member_names(wn: &WitName, collisions: &HashSet<String>) -> (Ident, Ident) {
53 if collisions.contains(wn.name) {
54 if wn.namespaces.is_empty() {
55 let mut type_name = component_first_camel(wn.name);
56 let mut getter = wn.name.to_string();
57 if !wn._version.is_empty() {
58 let v = version_to_kebab(&wn._version);
59 getter.push_str("-v");
60 getter.push_str(&v);
61 type_name.push('V');
62 type_name.push_str(&component_first_camel(&v));
63 }
64 return (format_ident!("{}", type_name), kebab_to_getter(&getter));
65 }
66
67 let (package, namespaces) = wn
68 .namespaces
69 .split_last()
70 .expect("colliding qualified imports have a package component");
71
72 let mut getter_components = namespaces
75 .iter()
76 .map(|ns| ns.replace('-', ""))
77 .collect::<Vec<_>>();
78 getter_components.push(package.replace('-', "_"));
79 let getter_prefix = getter_components.join("_");
80 let mut qualified_getter = format!("{}-{}", getter_prefix, wn.name);
81
82 let mut type_prefix: String = namespaces
84 .iter()
85 .map(|ns| component_first_camel(ns))
86 .collect();
87 type_prefix.push_str(&component_first_camel(&package.replace('-', "_")));
88 let mut type_name = format!("{}{}", type_prefix, component_first_camel(wn.name));
89
90 if !wn._version.is_empty() {
91 let v = version_to_kebab(&wn._version);
92 qualified_getter.push_str("-v");
93 qualified_getter.push_str(&v);
94 type_name.push('V');
95 type_name.push_str(&component_first_camel(&v));
96 }
97 (
98 format_ident!("{}", type_name),
99 kebab_to_getter(&qualified_getter),
100 )
101 } else {
102 (kebab_to_type(wn.name), kebab_to_getter(wn.name))
103 }
104}
105
106fn component_first_camel(s: &str) -> String {
109 let mut result = String::new();
110 let mut chars = s.chars();
111 if let Some(first) = chars.next() {
112 result.extend(first.to_uppercase());
113 }
114 for c in chars {
115 if c != '-' {
116 result.push(c);
117 }
118 }
119 result
120}
121
122#[derive(Clone, Debug, Default)]
126pub struct Trait {
127 pub supertraits: BTreeMap<Vec<Ident>, TokenStream>,
130 pub tvs: BTreeMap<Ident, (Option<u32>, TokenStream)>,
137 pub items: TokenStream,
139}
140impl Trait {
141 pub fn new() -> Self {
142 Self {
143 supertraits: BTreeMap::new(),
144 tvs: BTreeMap::new(),
145 items: TokenStream::new(),
146 }
147 }
148 pub fn tv_idxs(&self) -> Vec<u32> {
154 self.tvs.iter().map(|(_, (n, _))| n.unwrap()).collect()
155 }
156 pub fn adjust_vars(&mut self, n: u32) {
158 for (_, (v, _)) in self.tvs.iter_mut() {
159 if let Some(v) = v.as_mut() {
160 *v += n;
161 }
162 }
163 }
164 pub fn tv_toks_inner(&mut self) -> TokenStream {
167 let tvs = self
168 .tvs
169 .iter()
170 .map(|(k, (_, v))| {
171 let colon = if v.is_empty() {
172 quote! {}
173 } else {
174 quote! { : }
175 };
176 quote! { #k #colon #v }
177 })
178 .collect::<Vec<_>>();
179 quote! { #(#tvs),* }
180 }
181 pub fn tv_toks(&mut self) -> TokenStream {
184 let p = quote! { P: ::hyperlight_common::component::Positivity };
185 if !self.tvs.is_empty() {
186 let toks = self.tv_toks_inner();
187 quote! { <#p, #toks> }
188 } else {
189 quote! { <#p> }
190 }
191 }
192 pub fn into_tokens(&mut self, n: Ident) -> TokenStream {
194 let trait_colon = if !self.supertraits.is_empty() {
195 quote! { : }
196 } else {
197 quote! {}
198 };
199 let supertraits = self
200 .supertraits
201 .iter()
202 .map(|(is, ts)| {
203 quote! { #(#is)::*#ts }
204 })
205 .collect::<Vec<_>>();
206 let tvs = self.tv_toks();
207 let items = &self.items;
208 quote! {
209 pub trait #n #tvs #trait_colon #(#supertraits)+* { #items }
210 }
211 }
212}
213
214#[derive(Clone, Debug, Default)]
218pub struct Mod {
219 pub submods: BTreeMap<Ident, Mod>,
220 pub items: TokenStream,
221 pub traits: BTreeMap<Ident, Trait>,
222 pub impls: BTreeMap<(Vec<Ident>, Ident), (TokenStream, TokenStream)>,
223}
224impl Mod {
225 pub fn empty() -> Self {
226 Self {
227 submods: BTreeMap::new(),
228 items: TokenStream::new(),
229 traits: BTreeMap::new(),
230 impls: BTreeMap::new(),
231 }
232 }
233 pub fn submod<'a>(&'a mut self, i: Ident) -> &'a mut Self {
235 self.submods.entry(i).or_insert(Self::empty())
236 }
237 pub fn submod_immut<'a>(&'a self, i: Ident) -> &'a Self {
241 &self.submods[&i]
242 }
243 pub fn r#trait<'a>(&'a mut self, i: Ident) -> &'a mut Trait {
246 self.traits.entry(i).or_default()
247 }
248 pub fn trait_immut<'a>(&'a self, i: Ident) -> &'a Trait {
252 &self.traits[&i]
253 }
254 pub fn r#impl<'a>(&'a mut self, t: Vec<Ident>, i: Ident) -> &'a mut (TokenStream, TokenStream) {
260 self.impls.entry((t, i)).or_default()
261 }
262 pub fn adjust_vars(&mut self, n: u32) {
264 self.submods
265 .iter_mut()
266 .map(|(_, m)| m.adjust_vars(n))
267 .for_each(drop);
268 self.traits
269 .iter_mut()
270 .map(|(_, t)| t.adjust_vars(n))
271 .for_each(drop);
272 }
273 pub fn into_tokens(self) -> TokenStream {
275 let mut tt = TokenStream::new();
276 for (k, v) in self.submods {
277 let vt = v.into_tokens();
278 tt.extend(quote! {
279 pub mod #k { #vt }
280 });
281 }
282 for (n, mut t) in self.traits {
283 tt.extend(t.into_tokens(n));
284 }
285 tt.extend(self.items);
286 for ((ns, i), (tvi, t)) in self.impls {
287 tt.extend(quote! {
288 impl #(#ns)::* #tvi for #i { #t }
289 })
290 }
291 tt
292 }
293}
294
295pub enum ResolvedBoundVar<'a> {
300 Definite {
301 final_bound_var: u32,
305 ty: Defined<'a>,
307 },
308 Resource {
309 rtidx: u32,
314 },
315}
316
317#[derive(Debug)]
319pub struct State<'a, 'b> {
320 pub root_mod: &'a mut Mod,
322 pub mod_cursor: Vec<Ident>,
325 pub cur_trait: Option<Ident>,
329 pub cur_helper_mod: Option<Ident>,
335 pub is_helper: bool,
340 pub bound_vars: &'a mut VecDeque<BoundedTyvar<'b>>,
343 pub var_offset: usize,
349 pub origin: Vec<ImportExport<'b>>,
355 pub cur_needs_vars: Option<&'a mut BTreeSet<u32>>,
358 pub vars_needs_vars: &'a mut VecDeque<BTreeSet<u32>>,
362 pub import_param_var: Option<Ident>,
365 pub self_param_var: Option<TokenStream>,
395 pub positivity_param: Option<TokenStream>,
399 pub is_impl: bool,
402 pub root_component_name: Option<(TokenStream, &'a str)>,
405 pub is_guest: bool,
408 pub is_wasmtime_guest: bool,
412 pub colliding_import_names: HashSet<String>,
417}
418
419pub fn run_state<'b, F: for<'a> FnMut(&mut State<'a, 'b>)>(
423 is_guest: bool,
424 is_wasmtime_guest: bool,
425 mut f: F,
426) -> TokenStream {
427 let mut root_mod = Mod::empty();
428 let mut bound_vars = std::collections::VecDeque::new();
429 let mut vars_needs_vars = std::collections::VecDeque::new();
430 {
431 let mut state = State::new(
432 &mut root_mod,
433 &mut bound_vars,
434 &mut vars_needs_vars,
435 is_guest,
436 is_wasmtime_guest,
437 );
438 f(&mut state);
439 }
440 root_mod.into_tokens()
441}
442
443impl<'a, 'b> State<'a, 'b> {
444 pub fn new(
445 root_mod: &'a mut Mod,
446 bound_vars: &'a mut VecDeque<BoundedTyvar<'b>>,
447 vars_needs_vars: &'a mut VecDeque<BTreeSet<u32>>,
448 is_guest: bool,
449 is_wasmtime_guest: bool,
450 ) -> Self {
451 Self {
452 root_mod,
453 mod_cursor: Vec::new(),
454 cur_trait: None,
455 cur_helper_mod: None,
456 is_helper: false,
457 bound_vars,
458 var_offset: 0,
459 origin: Vec::new(),
460 cur_needs_vars: None,
461 vars_needs_vars,
462 import_param_var: None,
463 self_param_var: None,
464 positivity_param: None,
465 is_impl: false,
466 root_component_name: None,
467 is_guest,
468 is_wasmtime_guest,
469 colliding_import_names: HashSet::new(),
470 }
471 }
472 pub fn clone<'c>(&'c mut self) -> State<'c, 'b> {
473 State {
474 root_mod: self.root_mod,
475 mod_cursor: self.mod_cursor.clone(),
476 cur_trait: self.cur_trait.clone(),
477 cur_helper_mod: self.cur_helper_mod.clone(),
478 is_helper: self.is_helper,
479 bound_vars: self.bound_vars,
480 var_offset: self.var_offset,
481 origin: self.origin.clone(),
482 cur_needs_vars: self.cur_needs_vars.as_deref_mut(),
483 vars_needs_vars: self.vars_needs_vars,
484 import_param_var: self.import_param_var.clone(),
485 positivity_param: self.positivity_param.clone(),
486 self_param_var: self.self_param_var.clone(),
487 is_impl: self.is_impl,
488 root_component_name: self.root_component_name.clone(),
489 is_guest: self.is_guest,
490 is_wasmtime_guest: self.is_wasmtime_guest,
491 colliding_import_names: self.colliding_import_names.clone(),
492 }
493 }
494 pub fn cur_mod<'c>(&'c mut self) -> &'c mut Mod {
497 let mut m: &'c mut Mod = self.root_mod;
498 for i in &self.mod_cursor {
499 m = m.submod(i.clone());
500 }
501 if self.is_helper {
502 m = m.submod(self.cur_helper_mod.clone().unwrap());
503 }
504 m
505 }
506 pub fn cur_mod_immut<'c>(&'c self) -> &'c Mod {
511 let mut m: &'c Mod = self.root_mod;
512 for i in &self.mod_cursor {
513 m = m.submod_immut(i.clone());
514 }
515 if self.is_helper {
516 m = m.submod_immut(self.cur_helper_mod.clone().unwrap());
517 }
518 m
519 }
520 pub fn with_cursor<'c>(&'c mut self, cursor: Vec<Ident>) -> State<'c, 'b> {
523 let mut s = self.clone();
524 s.mod_cursor = cursor;
525 s
526 }
527 pub fn with_needs_vars<'c>(&'c mut self, needs_vars: &'c mut BTreeSet<u32>) -> State<'c, 'b> {
531 let mut s = self.clone();
532 s.cur_needs_vars = Some(needs_vars);
533 s
534 }
535 pub fn for_var_effects_only<F: for<'c> FnOnce(&mut State<'c, 'b>)>(&mut self, f: F) {
540 let mut new_mod = self.root_mod.clone();
541 let mut s = self.clone();
542 s.root_mod = &mut new_mod;
543 f(&mut s);
544 }
545
546 pub fn need_noff_var(&mut self, n: u32) {
549 self.cur_needs_vars.as_mut().map(|vs| vs.insert(n));
550 }
551 pub fn record_needs_vars(&mut self, n: u32) {
555 let un = n as usize;
556 if self.vars_needs_vars.len() < un + 1 {
557 self.vars_needs_vars.resize(un + 1, BTreeSet::new());
558 }
559 let Some(ref mut cnvs) = self.cur_needs_vars else {
560 return;
561 };
562 tracing::debug!("debug varref: recording {:?} for var {:?}", cnvs.iter(), un);
563 self.vars_needs_vars[un].extend(cnvs.iter());
564 }
565 pub fn get_noff_var_refs(&mut self, n: u32) -> BTreeSet<u32> {
568 let un = n as usize;
569 if self.vars_needs_vars.len() < un + 1 {
570 return BTreeSet::new();
571 };
572 tracing::debug!(
573 "debug varref: looking up {:?} for var {:?}",
574 self.vars_needs_vars[un].iter(),
575 un
576 );
577 self.vars_needs_vars[un].clone()
578 }
579 pub fn noff_var_id(&self, n: u32) -> Ident {
583 let origin = &self.bound_vars[n as usize].origin;
584 let Some(name) = origin.last_name() else {
585 panic!("missing origin on tyvar in rust emit")
586 };
587 let wn = split_wit_name(name);
588 if origin.is_imported() {
589 let (tn, _) = import_member_names(&wn, &self.colliding_import_names);
590 tn
591 } else {
592 kebab_to_type(wn.name)
593 }
594 }
595 pub fn helper<'c>(&'c mut self) -> State<'c, 'b> {
598 let mut s = self.clone();
599 s.is_helper = true;
600 s
601 }
602 pub fn root_path(&self) -> TokenStream {
605 if self.is_impl {
606 return TokenStream::new();
607 }
608 let mut s = self
609 .mod_cursor
610 .iter()
611 .map(|_| quote! { super })
612 .collect::<Vec<_>>();
613 if self.is_helper {
614 s.push(quote! { super });
615 }
616 quote! { #(#s::)* }
617 }
618 pub fn helper_path(&self) -> TokenStream {
621 if self.is_impl {
622 let c = &self.mod_cursor;
623 let helper = self.cur_helper_mod.clone().unwrap();
624 let h = if !self.is_helper {
625 quote! { #helper:: }
626 } else {
627 TokenStream::new()
628 };
629 quote! { #(#c::)*#h }
630 } else if self.is_helper {
631 quote! { self:: }
632 } else {
633 let helper = self.cur_helper_mod.clone().unwrap();
634 quote! { #helper:: }
635 }
636 }
637 pub fn cur_trait_path(&self) -> TokenStream {
640 let tns = &self.mod_cursor;
641 let tid = self.cur_trait.clone().unwrap();
642 quote! { #(#tns::)* #tid }
643 }
644 pub fn add_helper_supertrait(&mut self, r: Ident) {
648 let (Some(t), Some(hm)) = (self.cur_trait.clone(), &self.cur_helper_mod.clone()) else {
649 panic!("invariant violation")
650 };
651 self.cur_mod()
652 .r#trait(t)
653 .supertraits
654 .insert(vec![hm.clone(), r], TokenStream::new());
655 }
656 pub fn cur_trait<'c>(&'c mut self) -> &'c mut Trait {
662 let n = self.cur_trait.as_ref().unwrap().clone();
663 self.cur_mod().r#trait(n)
664 }
665 pub fn cur_trait_immut<'c>(&'c self) -> &'c Trait {
672 let n = self.cur_trait.as_ref().unwrap().clone();
673 self.cur_mod_immut().trait_immut(n)
674 }
675 pub fn r#trait<'c>(&'c mut self, namespace: &'c [Ident], name: Ident) -> &'c mut Trait {
679 let mut m: &'c mut Mod = self.root_mod;
680 for i in namespace {
681 m = m.submod(i.clone());
682 }
683 m.r#trait(name)
684 }
685 pub fn push_origin<'c>(&'c mut self, origin_was_export: bool, name: &'b str) -> State<'c, 'b> {
700 let mut s = self.clone();
701 s.origin.push(if origin_was_export {
702 ImportExport::Export(name)
703 } else {
704 ImportExport::Import(name)
705 });
706 s
707 }
708 pub fn is_var_defn(&self, t: &Defined<'b>) -> Option<(u32, TypeBound<'b>)> {
712 match t {
713 Defined::Handleable(Handleable::Var(tv)) => match tv {
714 Tyvar::Bound(n) => {
715 let bv = &self.bound_vars[self.var_offset + (*n as usize)];
716 tracing::debug!("checking an origin {:?} {:?}", bv.origin, self.origin);
717 if bv.origin.matches(self.origin.iter()) {
718 Some((*n, bv.bound.clone()))
719 } else {
720 None
721 }
722 }
723 Tyvar::Free(_) => panic!("free tyvar in finished type"),
724 },
725 _ => None,
726 }
727 }
728 pub fn is_noff_var_local<'c>(
731 &'c self,
732 n: u32,
733 ) -> Option<(Vec<ImportExport<'c>>, TypeBound<'a>)> {
734 let bv = &self.bound_vars[n as usize];
735 bv.origin
736 .is_local(self.origin.iter())
737 .map(|path| (path, bv.bound.clone()))
738 }
739 pub fn resolve_trait_immut(&self, absolute: bool, path: &[Ident]) -> &Trait {
745 tracing::debug!("resolving trait {:?} {:?}", absolute, path);
746 let mut m = if absolute {
747 &*self.root_mod
748 } else {
749 self.cur_mod_immut()
750 };
751 for x in &path[0..path.len() - 1] {
752 m = &m.submods[x];
753 }
754 &m.traits[&path[path.len() - 1]]
755 }
756 pub fn adjust_vars(&mut self, n: u32) {
763 self.vars_needs_vars
764 .iter_mut()
765 .enumerate()
766 .for_each(|(i, vs)| {
767 *vs = vs.iter().map(|v| v + n).collect();
768 tracing::debug!("updated {:?} to {:?}", i, *vs);
769 });
770 for _ in 0..n {
771 self.vars_needs_vars.push_front(BTreeSet::new());
772 }
773 self.root_mod.adjust_vars(n);
774 }
775 pub fn resolve_bound_var(&self, n: u32) -> ResolvedBoundVar<'b> {
784 let noff = self.var_offset as u32 + n;
785 match &self.bound_vars[noff as usize].bound {
786 TypeBound::Eq(Defined::Handleable(Handleable::Var(Tyvar::Bound(nn)))) => {
787 self.resolve_bound_var(n + 1 + nn)
788 }
789 TypeBound::Eq(t) => ResolvedBoundVar::Definite {
790 final_bound_var: n,
791 ty: t.clone(),
792 },
793 TypeBound::SubResource => ResolvedBoundVar::Resource { rtidx: noff },
794 }
795 }
796
797 pub fn resource_trait_path(&self, r: Ident) -> Vec<Ident> {
800 let mut path = self.mod_cursor.clone();
801 let helper = self
802 .cur_helper_mod
803 .as_ref()
804 .expect("There should always be a helper mod to hold a resource trait")
805 .clone();
806 path.push(helper);
807 path.push(r);
808 path
809 }
810}
811
812#[derive(Debug, Clone)]
815pub struct WitName<'a> {
816 pub namespaces: Vec<&'a str>,
817 pub name: &'a str,
818 pub _version: Vec<&'a str>,
819}
820impl<'a> WitName<'a> {
821 pub fn namespace_idents(&self) -> Vec<Ident> {
824 self.namespaces
825 .iter()
826 .map(|x| kebab_to_namespace(x))
827 .collect::<Vec<_>>()
828 }
829 pub fn namespace_path(&self) -> TokenStream {
832 let ns = self.namespace_idents();
833 quote! { #(#ns)::* }
834 }
835}
836pub fn split_wit_name(n: &str) -> WitName<'_> {
838 let mut namespaces = Vec::new();
839 let mut colon_components = n.split(':').rev();
840 let last = colon_components.next().unwrap();
841 namespaces.extend(colon_components.rev());
842 let mut slash_components = last.split('/').rev();
843 let mut versioned_name = slash_components.next().unwrap().split('@');
844 let name = versioned_name.next().unwrap();
845 namespaces.extend(slash_components.rev());
846 WitName {
847 namespaces,
848 name,
849 _version: versioned_name.collect(),
850 }
851}
852
853fn kebab_to_snake(n: &str) -> Ident {
854 if n == "self" {
855 return format_ident!("self_");
856 }
857 let mut ret = String::new();
858 for c in n.chars() {
859 if c == '-' {
860 ret.push('_');
861 continue;
862 }
863 ret.push(c);
864 }
865 format_ident!("r#{}", ret)
866}
867
868fn kebab_to_camel(n: &str) -> Ident {
869 let mut word_start = true;
870 let mut ret = String::new();
871 for c in n.chars() {
872 if c == '-' {
873 word_start = true;
874 continue;
875 }
876 if word_start {
877 ret.extend(c.to_uppercase())
878 } else {
879 ret.push(c)
880 };
881 word_start = false;
882 }
883 format_ident!("{}", ret)
884}
885
886pub fn kebab_to_var(n: &str) -> Ident {
889 kebab_to_snake(n)
890}
891pub fn kebab_to_cons(n: &str) -> Ident {
894 kebab_to_camel(n)
895}
896pub fn kebab_to_getter(n: &str) -> Ident {
899 kebab_to_snake(n)
900}
901pub fn kebab_to_type(n: &str) -> Ident {
903 kebab_to_camel(n)
904}
905pub fn kebab_to_namespace(n: &str) -> Ident {
908 kebab_to_snake(n)
909}
910pub fn kebab_to_imports_name(trait_name: &str) -> Ident {
913 format_ident!("{}Imports", kebab_to_type(trait_name))
914}
915pub fn kebab_to_exports_name(trait_name: &str) -> Ident {
918 format_ident!("{}Exports", kebab_to_type(trait_name))
919}
920pub fn kebab_to_flags_const(n: &str) -> Ident {
923 let s: String = n
924 .chars()
925 .map(|c| {
926 if c == '-' {
927 '_'
928 } else {
929 c.to_ascii_uppercase()
930 }
931 })
932 .collect();
933 format_ident!("{}", s)
934}
935
936pub enum ResourceItemName {
939 Constructor,
940 Method(Ident),
941 Static(Ident),
942}
943
944pub enum FnName {
946 Associated(Ident, ResourceItemName),
947 Plain(Ident),
948}
949pub fn kebab_to_fn(n: &str) -> FnName {
952 if let Some(n) = n.strip_prefix("[constructor]") {
953 return FnName::Associated(kebab_to_type(n), ResourceItemName::Constructor);
954 }
955 if let Some(n) = n.strip_prefix("[method]") {
956 let mut i = n.split('.');
957 let r = i.next().unwrap();
958 let n = i.next().unwrap();
959 return FnName::Associated(
960 kebab_to_type(r),
961 ResourceItemName::Method(kebab_to_snake(n)),
962 );
963 }
964 if let Some(n) = n.strip_prefix("[static]") {
965 let mut i = n.split('.');
966 let r = i.next().unwrap();
967 let n = i.next().unwrap();
968 return FnName::Associated(
969 kebab_to_type(r),
970 ResourceItemName::Static(kebab_to_snake(n)),
971 );
972 }
973 FnName::Plain(kebab_to_snake(n))
974}
975
976#[cfg(test)]
977mod tests {
978 use super::*;
979 use crate::etypes::{ExternDecl, ExternDesc, Instance};
980
981 fn instance_decl(kebab_name: &str) -> ExternDecl<'_> {
983 ExternDecl {
984 kebab_name,
985 desc: ExternDesc::Instance(Instance {
986 exports: Vec::new(),
987 }),
988 }
989 }
990
991 fn func_decl(kebab_name: &str) -> ExternDecl<'_> {
993 ExternDecl {
994 kebab_name,
995 desc: ExternDesc::Func(crate::etypes::Func {
996 params: Vec::new(),
997 result: None,
998 }),
999 }
1000 }
1001
1002 #[test]
1005 fn split_wit_name_simple() {
1006 let wn = split_wit_name("my-interface");
1007 assert_eq!(wn.name, "my-interface");
1008 assert!(wn.namespaces.is_empty());
1009 }
1010
1011 #[test]
1012 fn split_wit_name_with_package() {
1013 let wn = split_wit_name("wasi:http/types");
1014 assert_eq!(wn.name, "types");
1015 assert_eq!(wn.namespaces, vec!["wasi", "http"]);
1016 }
1017
1018 #[test]
1019 fn split_wit_name_with_version() {
1020 let wn = split_wit_name("wasi:http/types@0.2.0");
1021 assert_eq!(wn.name, "types");
1022 assert_eq!(wn.namespaces, vec!["wasi", "http"]);
1023 }
1024
1025 #[test]
1028 fn no_collisions_with_distinct_names() {
1029 let imports = vec![
1030 instance_decl("wasi:http/types"),
1031 instance_decl("wasi:filesystem/preopens"),
1032 ];
1033 let collisions = find_colliding_import_names(&imports);
1034 assert_eq!(collisions.len(), 0);
1035 }
1036
1037 #[test]
1038 fn detects_collision_on_same_short_name() {
1039 let imports = vec![
1040 instance_decl("wasi:http/types"),
1041 instance_decl("wasi:filesystem/types"),
1042 ];
1043 let collisions = find_colliding_import_names(&imports);
1044 assert_eq!(collisions.len(), 1);
1045 assert!(collisions.contains("types"));
1046 }
1047
1048 #[test]
1049 fn no_collision_for_non_instance_decls() {
1050 let imports = vec![instance_decl("wasi:http/types"), func_decl("types")];
1051 let collisions = find_colliding_import_names(&imports);
1052 assert_eq!(collisions.len(), 0);
1053 }
1054
1055 #[test]
1056 fn multiple_collisions() {
1057 let imports = vec![
1058 instance_decl("a:foo/types"),
1059 instance_decl("b:bar/types"),
1060 instance_decl("a:foo/handler"),
1061 instance_decl("c:baz/handler"),
1062 ];
1063 let collisions = find_colliding_import_names(&imports);
1064 assert_eq!(collisions.len(), 2);
1065 assert!(collisions.contains("types"));
1066 assert!(collisions.contains("handler"));
1067 }
1068
1069 #[test]
1070 fn single_import_no_collision() {
1071 let imports = vec![instance_decl("wasi:http/types")];
1072 let collisions = find_colliding_import_names(&imports);
1073 assert_eq!(collisions.len(), 0);
1074 }
1075
1076 #[test]
1077 fn empty_imports_no_collision() {
1078 let collisions = find_colliding_import_names(&[]);
1079 assert_eq!(collisions.len(), 0);
1080 }
1081
1082 #[test]
1085 fn no_collision_uses_short_name() {
1086 let wn = split_wit_name("wasi:http/types");
1087 let collisions = HashSet::new();
1088 let (ty, getter) = import_member_names(&wn, &collisions);
1089 assert_eq!(ty.to_string(), "Types");
1090 assert_eq!(getter.to_string(), "r#types");
1091 }
1092
1093 #[test]
1094 fn collision_prepends_parent_namespace() {
1095 let wn = split_wit_name("wasi:http/types");
1096 let collisions = find_colliding_import_names(&[
1097 instance_decl("wasi:http/types"),
1098 instance_decl("wasi:filesystem/types"),
1099 ]);
1100 let (ty, getter) = import_member_names(&wn, &collisions);
1101 assert_eq!(ty.to_string(), "WasiHttpTypes");
1102 assert_eq!(getter.to_string(), "r#wasi_http_types");
1103 }
1104
1105 #[test]
1106 fn collision_different_parents_produce_different_names() {
1107 let collisions = find_colliding_import_names(&[
1108 instance_decl("wasi:http/types"),
1109 instance_decl("wasi:filesystem/types"),
1110 ]);
1111
1112 let wn_http = split_wit_name("wasi:http/types");
1113 let (ty_http, getter_http) = import_member_names(&wn_http, &collisions);
1114
1115 let wn_fs = split_wit_name("wasi:filesystem/types");
1116 let (ty_fs, getter_fs) = import_member_names(&wn_fs, &collisions);
1117
1118 assert_eq!(ty_http.to_string(), "WasiHttpTypes");
1119 assert_eq!(ty_fs.to_string(), "WasiFilesystemTypes");
1120 assert_eq!(getter_http.to_string(), "r#wasi_http_types");
1121 assert_eq!(getter_fs.to_string(), "r#wasi_filesystem_types");
1122 }
1123
1124 #[test]
1125 fn collision_same_parent_different_package_produces_different_names() {
1126 let collisions = find_colliding_import_names(&[
1127 instance_decl("a:pkg/types"),
1128 instance_decl("b:pkg/types"),
1129 ]);
1130
1131 let wn_a = split_wit_name("a:pkg/types");
1132 let (ty_a, getter_a) = import_member_names(&wn_a, &collisions);
1133
1134 let wn_b = split_wit_name("b:pkg/types");
1135 let (ty_b, getter_b) = import_member_names(&wn_b, &collisions);
1136
1137 assert_eq!(ty_a.to_string(), "APkgTypes");
1138 assert_eq!(getter_a.to_string(), "r#a_pkg_types");
1139 assert_eq!(ty_b.to_string(), "BPkgTypes");
1140 assert_eq!(getter_b.to_string(), "r#b_pkg_types");
1141 }
1142
1143 #[test]
1144 fn colliding_bare_import_keeps_short_name() {
1145 let wn = split_wit_name("types");
1146 let collisions =
1147 find_colliding_import_names(&[instance_decl("types"), instance_decl("pkg:types")]);
1148 let (ty, getter) = import_member_names(&wn, &collisions);
1149 assert_eq!(ty.to_string(), "Types");
1150 assert_eq!(getter.to_string(), "r#types");
1151 }
1152
1153 #[test]
1154 fn versioned_collision_adds_version_after_namespace() {
1155 let collisions = find_colliding_import_names(&[
1156 instance_decl("a:pkg/types@1.0.0"),
1157 instance_decl("a:pkg/types@2.0.0"),
1158 ]);
1159
1160 let wn_v1 = split_wit_name("a:pkg/types@1.0.0");
1161 let (ty_v1, getter_v1) = import_member_names(&wn_v1, &collisions);
1162
1163 let wn_v2 = split_wit_name("a:pkg/types@2.0.0");
1164 let (ty_v2, getter_v2) = import_member_names(&wn_v2, &collisions);
1165
1166 assert_eq!(ty_v1.to_string(), "APkgTypesV100");
1167 assert_eq!(ty_v2.to_string(), "APkgTypesV200");
1168 assert_eq!(getter_v1.to_string(), "r#a_pkg_types_v1_0_0");
1169 assert_eq!(getter_v2.to_string(), "r#a_pkg_types_v2_0_0");
1170 }
1171
1172 #[test]
1173 fn version_is_added_for_colliding_versioned_imports() {
1174 let collisions = find_colliding_import_names(&[
1175 instance_decl("a:pkg/types@1.0.0"),
1176 instance_decl("b:pkg/types@1.0.0"),
1177 ]);
1178
1179 let wn = split_wit_name("a:pkg/types@1.0.0");
1180 let (ty, getter) = import_member_names(&wn, &collisions);
1181
1182 assert_eq!(ty.to_string(), "APkgTypesV100");
1183 assert_eq!(getter.to_string(), "r#a_pkg_types_v1_0_0");
1184 }
1185
1186 #[test]
1187 fn hyphenated_namespace_components_produce_distinct_type_names() {
1188 let collisions = find_colliding_import_names(&[
1192 instance_decl("a:b-c/types"),
1193 instance_decl("a-b:c/types"),
1194 ]);
1195
1196 let wn1 = split_wit_name("a:b-c/types");
1197 let wn2 = split_wit_name("a-b:c/types");
1198 let (ty1, getter1) = import_member_names(&wn1, &collisions);
1199 let (ty2, getter2) = import_member_names(&wn2, &collisions);
1200
1201 assert_eq!(ty1.to_string(), "AB_cTypes");
1202 assert_eq!(getter1.to_string(), "r#a_b_c_types");
1203 assert_eq!(ty2.to_string(), "AbCTypes");
1204 assert_eq!(getter2.to_string(), "r#ab_c_types");
1205 }
1206
1207 #[test]
1208 fn plain_and_hyphenated_namespace_components_produce_distinct_type_names() {
1209 let collisions = find_colliding_import_names(&[
1210 instance_decl("a:bc/types"),
1211 instance_decl("a:b-c/types"),
1212 ]);
1213
1214 let wn_plain = split_wit_name("a:bc/types");
1215 let wn_hyphenated = split_wit_name("a:b-c/types");
1216 let (ty_plain, getter_plain) = import_member_names(&wn_plain, &collisions);
1217 let (ty_hyphenated, getter_hyphenated) = import_member_names(&wn_hyphenated, &collisions);
1218
1219 assert_eq!(ty_plain.to_string(), "ABcTypes");
1220 assert_eq!(getter_plain.to_string(), "r#a_bc_types");
1221 assert_eq!(ty_hyphenated.to_string(), "AB_cTypes");
1222 assert_eq!(getter_hyphenated.to_string(), "r#a_b_c_types");
1223 }
1224}