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.values_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)]
320pub struct State<'a, 'b, M = Mod> {
321 pub root_mod: &'a mut M,
323 pub mod_cursor: Vec<Ident>,
326 pub cur_trait: Option<Ident>,
330 pub cur_helper_mod: Option<Ident>,
336 pub is_helper: bool,
341 pub bound_vars: &'a mut VecDeque<BoundedTyvar<'b>>,
344 pub var_offset: usize,
350 pub origin: Vec<ImportExport<'b>>,
356 pub cur_needs_vars: Option<&'a mut BTreeSet<u32>>,
359 pub vars_needs_vars: &'a mut VecDeque<BTreeSet<u32>>,
363 pub import_param_var: Option<Ident>,
366 pub self_param_var: Option<TokenStream>,
396 pub positivity_param: Option<TokenStream>,
400 pub is_impl: bool,
403 pub root_component_name: Option<(TokenStream, &'a str)>,
406 pub is_guest: bool,
409 pub is_wasmtime_guest: bool,
413 pub colliding_import_names: HashSet<String>,
418 pub cur_instance: Option<String>,
422}
423
424pub fn run_state<'b, F: for<'a> FnMut(&mut State<'a, 'b>)>(
428 is_guest: bool,
429 is_wasmtime_guest: bool,
430 mut f: F,
431) -> TokenStream {
432 let mut root_mod = Mod::empty();
433 let mut bound_vars = std::collections::VecDeque::new();
434 let mut vars_needs_vars = std::collections::VecDeque::new();
435 {
436 let mut state = State::new(
437 &mut root_mod,
438 &mut bound_vars,
439 &mut vars_needs_vars,
440 is_guest,
441 is_wasmtime_guest,
442 );
443 f(&mut state);
444 }
445 root_mod.into_tokens()
446}
447
448impl<'a, 'b, M> State<'a, 'b, M> {
449 pub fn new(
450 root_mod: &'a mut M,
451 bound_vars: &'a mut VecDeque<BoundedTyvar<'b>>,
452 vars_needs_vars: &'a mut VecDeque<BTreeSet<u32>>,
453 is_guest: bool,
454 is_wasmtime_guest: bool,
455 ) -> Self {
456 Self {
457 root_mod,
458 mod_cursor: Vec::new(),
459 cur_trait: None,
460 cur_helper_mod: None,
461 is_helper: false,
462 bound_vars,
463 var_offset: 0,
464 origin: Vec::new(),
465 cur_needs_vars: None,
466 vars_needs_vars,
467 import_param_var: None,
468 self_param_var: None,
469 positivity_param: None,
470 is_impl: false,
471 root_component_name: None,
472 is_guest,
473 is_wasmtime_guest,
474 colliding_import_names: HashSet::new(),
475 cur_instance: None,
476 }
477 }
478 pub fn clone<'c>(&'c mut self) -> State<'c, 'b, M> {
479 State {
480 root_mod: self.root_mod,
481 mod_cursor: self.mod_cursor.clone(),
482 cur_trait: self.cur_trait.clone(),
483 cur_helper_mod: self.cur_helper_mod.clone(),
484 is_helper: self.is_helper,
485 bound_vars: self.bound_vars,
486 var_offset: self.var_offset,
487 origin: self.origin.clone(),
488 cur_needs_vars: self.cur_needs_vars.as_deref_mut(),
489 vars_needs_vars: self.vars_needs_vars,
490 import_param_var: self.import_param_var.clone(),
491 positivity_param: self.positivity_param.clone(),
492 self_param_var: self.self_param_var.clone(),
493 is_impl: self.is_impl,
494 root_component_name: self.root_component_name.clone(),
495 is_guest: self.is_guest,
496 is_wasmtime_guest: self.is_wasmtime_guest,
497 colliding_import_names: self.colliding_import_names.clone(),
498 cur_instance: self.cur_instance.clone(),
499 }
500 }
501 pub fn resolve_bound_var(&self, n: u32) -> ResolvedBoundVar<'b> {
510 let noff = self.var_offset as u32 + n;
511 match &self.bound_vars[noff as usize].bound {
512 TypeBound::Eq(Defined::Handleable(Handleable::Var(Tyvar::Bound(nn)))) => {
513 self.resolve_bound_var(n + 1 + nn)
514 }
515 TypeBound::Eq(t) => ResolvedBoundVar::Definite {
516 final_bound_var: n,
517 ty: t.clone(),
518 },
519 TypeBound::SubResource => ResolvedBoundVar::Resource { rtidx: noff },
520 }
521 }
522}
523
524impl<'a, 'b> State<'a, 'b> {
525 pub fn cur_mod<'c>(&'c mut self) -> &'c mut Mod {
528 let mut m: &'c mut Mod = self.root_mod;
529 for i in &self.mod_cursor {
530 m = m.submod(i.clone());
531 }
532 if self.is_helper {
533 m = m.submod(self.cur_helper_mod.clone().unwrap());
534 }
535 m
536 }
537 pub fn cur_mod_immut<'c>(&'c self) -> &'c Mod {
542 let mut m: &'c Mod = self.root_mod;
543 for i in &self.mod_cursor {
544 m = m.submod_immut(i.clone());
545 }
546 if self.is_helper {
547 m = m.submod_immut(self.cur_helper_mod.clone().unwrap());
548 }
549 m
550 }
551 pub fn with_cursor<'c>(&'c mut self, cursor: Vec<Ident>) -> State<'c, 'b> {
554 let mut s = self.clone();
555 s.mod_cursor = cursor;
556 s
557 }
558 pub fn with_instance<'c>(&'c mut self, wn: &WitName) -> State<'c, 'b> {
561 let mut s = self.with_cursor(wn.namespace_idents());
562 s.cur_instance = Some(wn.kebab.to_string());
563 s
564 }
565 pub fn with_needs_vars<'c>(&'c mut self, needs_vars: &'c mut BTreeSet<u32>) -> State<'c, 'b> {
569 let mut s = self.clone();
570 s.cur_needs_vars = Some(needs_vars);
571 s
572 }
573 pub fn for_var_effects_only<F: for<'c> FnOnce(&mut State<'c, 'b>)>(&mut self, f: F) {
578 let mut new_mod = self.root_mod.clone();
579 let mut s = self.clone();
580 s.root_mod = &mut new_mod;
581 f(&mut s);
582 }
583
584 pub fn need_noff_var(&mut self, n: u32) {
587 self.cur_needs_vars.as_mut().map(|vs| vs.insert(n));
588 }
589 pub fn record_needs_vars(&mut self, n: u32) {
593 let un = n as usize;
594 if self.vars_needs_vars.len() < un + 1 {
595 self.vars_needs_vars.resize(un + 1, BTreeSet::new());
596 }
597 let Some(ref mut cnvs) = self.cur_needs_vars else {
598 return;
599 };
600 tracing::debug!("debug varref: recording {:?} for var {:?}", cnvs.iter(), un);
601 self.vars_needs_vars[un].extend(cnvs.iter());
602 }
603 pub fn get_noff_var_refs(&mut self, n: u32) -> BTreeSet<u32> {
606 let un = n as usize;
607 if self.vars_needs_vars.len() < un + 1 {
608 return BTreeSet::new();
609 };
610 tracing::debug!(
611 "debug varref: looking up {:?} for var {:?}",
612 self.vars_needs_vars[un].iter(),
613 un
614 );
615 self.vars_needs_vars[un].clone()
616 }
617 pub fn noff_var_id(&self, n: u32) -> Ident {
621 let origin = &self.bound_vars[n as usize].origin;
622 let Some(name) = origin.last_name() else {
623 panic!("missing origin on tyvar in rust emit")
624 };
625 let wn = split_wit_name(name);
626 if origin.is_imported() {
627 let (tn, _) = import_member_names(&wn, &self.colliding_import_names);
628 tn
629 } else {
630 kebab_to_type(wn.name)
631 }
632 }
633 pub fn helper<'c>(&'c mut self) -> State<'c, 'b> {
636 let mut s = self.clone();
637 s.is_helper = true;
638 s
639 }
640 pub fn root_path(&self) -> TokenStream {
643 if self.is_impl {
644 return TokenStream::new();
645 }
646 let mut s = self
647 .mod_cursor
648 .iter()
649 .map(|_| quote! { super })
650 .collect::<Vec<_>>();
651 if self.is_helper {
652 s.push(quote! { super });
653 }
654 quote! { #(#s::)* }
655 }
656 pub fn helper_path(&self) -> TokenStream {
659 if self.is_impl {
660 let c = &self.mod_cursor;
661 let helper = self.cur_helper_mod.clone().unwrap();
662 let h = if !self.is_helper {
663 quote! { #helper:: }
664 } else {
665 TokenStream::new()
666 };
667 quote! { #(#c::)*#h }
668 } else if self.is_helper {
669 quote! { self:: }
670 } else {
671 let helper = self.cur_helper_mod.clone().unwrap();
672 quote! { #helper:: }
673 }
674 }
675 pub fn cur_trait_path(&self) -> TokenStream {
678 let tns = &self.mod_cursor;
679 let tid = self.cur_trait.clone().unwrap();
680 quote! { #(#tns::)* #tid }
681 }
682 pub fn add_helper_supertrait(&mut self, r: Ident) {
686 let (Some(t), Some(hm)) = (self.cur_trait.clone(), &self.cur_helper_mod.clone()) else {
687 panic!("invariant violation")
688 };
689 self.cur_mod()
690 .r#trait(t)
691 .supertraits
692 .insert(vec![hm.clone(), r], TokenStream::new());
693 }
694 pub fn cur_trait<'c>(&'c mut self) -> &'c mut Trait {
700 let n = self.cur_trait.as_ref().unwrap().clone();
701 self.cur_mod().r#trait(n)
702 }
703 pub fn cur_trait_immut<'c>(&'c self) -> &'c Trait {
710 let n = self.cur_trait.as_ref().unwrap().clone();
711 self.cur_mod_immut().trait_immut(n)
712 }
713 pub fn r#trait<'c>(&'c mut self, namespace: &'c [Ident], name: Ident) -> &'c mut Trait {
717 let mut m: &'c mut Mod = self.root_mod;
718 for i in namespace {
719 m = m.submod(i.clone());
720 }
721 m.r#trait(name)
722 }
723 pub fn push_origin<'c>(&'c mut self, origin_was_export: bool, name: &'b str) -> State<'c, 'b> {
738 let mut s = self.clone();
739 s.origin.push(if origin_was_export {
740 ImportExport::Export(name)
741 } else {
742 ImportExport::Import(name)
743 });
744 s
745 }
746 pub fn is_var_defn(&self, t: &Defined<'b>) -> Option<(u32, TypeBound<'b>)> {
750 match t {
751 Defined::Handleable(Handleable::Var(tv)) => match tv {
752 Tyvar::Bound(n) => {
753 let bv = &self.bound_vars[self.var_offset + (*n as usize)];
754 tracing::debug!("checking an origin {:?} {:?}", bv.origin, self.origin);
755 if bv.origin.matches(self.origin.iter()) {
756 Some((*n, bv.bound.clone()))
757 } else {
758 None
759 }
760 }
761 Tyvar::Free(_) => panic!("free tyvar in finished type"),
762 },
763 _ => None,
764 }
765 }
766 pub fn is_noff_var_local<'c>(
769 &'c self,
770 n: u32,
771 ) -> Option<(Vec<ImportExport<'c>>, TypeBound<'a>)> {
772 let bv = &self.bound_vars[n as usize];
773 bv.origin
774 .is_local(self.origin.iter())
775 .map(|path| (path, bv.bound.clone()))
776 }
777 pub fn resolve_trait_immut(&self, absolute: bool, path: &[Ident]) -> &Trait {
783 tracing::debug!("resolving trait {:?} {:?}", absolute, path);
784 let mut m = if absolute {
785 &*self.root_mod
786 } else {
787 self.cur_mod_immut()
788 };
789 for x in &path[0..path.len() - 1] {
790 m = &m.submods[x];
791 }
792 &m.traits[&path[path.len() - 1]]
793 }
794 pub fn adjust_vars(&mut self, n: u32) {
801 self.vars_needs_vars
802 .iter_mut()
803 .enumerate()
804 .for_each(|(i, vs)| {
805 *vs = vs.iter().map(|v| v + n).collect();
806 tracing::debug!("updated {:?} to {:?}", i, *vs);
807 });
808 for _ in 0..n {
809 self.vars_needs_vars.push_front(BTreeSet::new());
810 }
811 self.root_mod.adjust_vars(n);
812 }
813
814 pub fn resource_trait_path(&self, r: Ident) -> Vec<Ident> {
817 let mut path = self.mod_cursor.clone();
818 let helper = self
819 .cur_helper_mod
820 .as_ref()
821 .expect("There should always be a helper mod to hold a resource trait")
822 .clone();
823 path.push(helper);
824 path.push(r);
825 path
826 }
827}
828
829#[derive(Debug, Clone)]
832pub struct WitName<'a> {
833 pub kebab: &'a str,
835 pub namespaces: Vec<&'a str>,
836 pub name: &'a str,
837 pub _version: Vec<&'a str>,
838}
839impl<'a> WitName<'a> {
840 pub fn namespace_idents(&self) -> Vec<Ident> {
843 self.namespaces
844 .iter()
845 .map(|x| kebab_to_namespace(x))
846 .collect::<Vec<_>>()
847 }
848 pub fn namespace_path(&self) -> TokenStream {
851 let ns = self.namespace_idents();
852 quote! { #(#ns)::* }
853 }
854}
855pub fn split_wit_name(n: &str) -> WitName<'_> {
857 let mut namespaces = Vec::new();
858 let mut colon_components = n.split(':').rev();
859 let last = colon_components.next().unwrap();
860 namespaces.extend(colon_components.rev());
861 let mut slash_components = last.split('/').rev();
862 let mut versioned_name = slash_components.next().unwrap().split('@');
863 let name = versioned_name.next().unwrap();
864 namespaces.extend(slash_components.rev());
865 WitName {
866 kebab: n,
867 namespaces,
868 name,
869 _version: versioned_name.collect(),
870 }
871}
872
873fn kebab_to_snake(n: &str) -> Ident {
874 if n == "self" {
875 return format_ident!("self_");
876 }
877 let mut ret = String::new();
878 for c in n.chars() {
879 if c == '-' {
880 ret.push('_');
881 continue;
882 }
883 ret.push(c);
884 }
885 format_ident!("r#{}", ret)
886}
887
888fn kebab_to_camel(n: &str) -> Ident {
889 let mut word_start = true;
890 let mut ret = String::new();
891 for c in n.chars() {
892 if c == '-' {
893 word_start = true;
894 continue;
895 }
896 if word_start {
897 ret.extend(c.to_uppercase())
898 } else {
899 ret.push(c)
900 };
901 word_start = false;
902 }
903 format_ident!("{}", ret)
904}
905
906pub fn kebab_to_var(n: &str) -> Ident {
909 kebab_to_snake(n)
910}
911pub fn kebab_to_cons(n: &str) -> Ident {
914 kebab_to_camel(n)
915}
916pub fn kebab_to_getter(n: &str) -> Ident {
919 kebab_to_snake(n)
920}
921pub fn kebab_to_type(n: &str) -> Ident {
923 kebab_to_camel(n)
924}
925pub fn kebab_to_namespace(n: &str) -> Ident {
928 kebab_to_snake(n)
929}
930pub fn kebab_to_imports_name(trait_name: &str) -> Ident {
933 format_ident!("{}Imports", kebab_to_type(trait_name))
934}
935pub fn kebab_to_exports_name(trait_name: &str) -> Ident {
938 format_ident!("{}Exports", kebab_to_type(trait_name))
939}
940pub fn kebab_to_flags_const(n: &str) -> Ident {
943 let s: String = n
944 .chars()
945 .map(|c| {
946 if c == '-' {
947 '_'
948 } else {
949 c.to_ascii_uppercase()
950 }
951 })
952 .collect();
953 format_ident!("{}", s)
954}
955
956pub enum ResourceItemName {
959 Constructor,
960 Method(Ident),
961 Static(Ident),
962}
963
964pub enum FnName {
966 Associated(Ident, ResourceItemName),
967 Plain(Ident),
968}
969pub fn kebab_to_fn(n: &str) -> FnName {
972 if let Some(n) = n.strip_prefix("[constructor]") {
973 return FnName::Associated(kebab_to_type(n), ResourceItemName::Constructor);
974 }
975 if let Some(n) = n.strip_prefix("[method]") {
976 let mut i = n.split('.');
977 let r = i.next().unwrap();
978 let n = i.next().unwrap();
979 return FnName::Associated(
980 kebab_to_type(r),
981 ResourceItemName::Method(kebab_to_snake(n)),
982 );
983 }
984 if let Some(n) = n.strip_prefix("[static]") {
985 let mut i = n.split('.');
986 let r = i.next().unwrap();
987 let n = i.next().unwrap();
988 return FnName::Associated(
989 kebab_to_type(r),
990 ResourceItemName::Static(kebab_to_snake(n)),
991 );
992 }
993 FnName::Plain(kebab_to_snake(n))
994}
995
996#[cfg(test)]
997mod tests {
998 use super::*;
999 use crate::etypes::{ExternDecl, ExternDesc, Instance};
1000
1001 fn instance_decl(kebab_name: &str) -> ExternDecl<'_> {
1003 ExternDecl {
1004 kebab_name,
1005 desc: ExternDesc::Instance(Instance {
1006 exports: Vec::new(),
1007 }),
1008 }
1009 }
1010
1011 fn func_decl(kebab_name: &str) -> ExternDecl<'_> {
1013 ExternDecl {
1014 kebab_name,
1015 desc: ExternDesc::Func(crate::etypes::Func {
1016 params: Vec::new(),
1017 result: None,
1018 }),
1019 }
1020 }
1021
1022 #[test]
1025 fn split_wit_name_simple() {
1026 let wn = split_wit_name("my-interface");
1027 assert_eq!(wn.name, "my-interface");
1028 assert!(wn.namespaces.is_empty());
1029 }
1030
1031 #[test]
1032 fn split_wit_name_with_package() {
1033 let wn = split_wit_name("wasi:http/types");
1034 assert_eq!(wn.name, "types");
1035 assert_eq!(wn.namespaces, vec!["wasi", "http"]);
1036 }
1037
1038 #[test]
1039 fn split_wit_name_with_version() {
1040 let wn = split_wit_name("wasi:http/types@0.2.0");
1041 assert_eq!(wn.name, "types");
1042 assert_eq!(wn.namespaces, vec!["wasi", "http"]);
1043 }
1044
1045 #[test]
1048 fn no_collisions_with_distinct_names() {
1049 let imports = vec![
1050 instance_decl("wasi:http/types"),
1051 instance_decl("wasi:filesystem/preopens"),
1052 ];
1053 let collisions = find_colliding_import_names(&imports);
1054 assert_eq!(collisions.len(), 0);
1055 }
1056
1057 #[test]
1058 fn detects_collision_on_same_short_name() {
1059 let imports = vec![
1060 instance_decl("wasi:http/types"),
1061 instance_decl("wasi:filesystem/types"),
1062 ];
1063 let collisions = find_colliding_import_names(&imports);
1064 assert_eq!(collisions.len(), 1);
1065 assert!(collisions.contains("types"));
1066 }
1067
1068 #[test]
1069 fn no_collision_for_non_instance_decls() {
1070 let imports = vec![instance_decl("wasi:http/types"), func_decl("types")];
1071 let collisions = find_colliding_import_names(&imports);
1072 assert_eq!(collisions.len(), 0);
1073 }
1074
1075 #[test]
1076 fn multiple_collisions() {
1077 let imports = vec![
1078 instance_decl("a:foo/types"),
1079 instance_decl("b:bar/types"),
1080 instance_decl("a:foo/handler"),
1081 instance_decl("c:baz/handler"),
1082 ];
1083 let collisions = find_colliding_import_names(&imports);
1084 assert_eq!(collisions.len(), 2);
1085 assert!(collisions.contains("types"));
1086 assert!(collisions.contains("handler"));
1087 }
1088
1089 #[test]
1090 fn single_import_no_collision() {
1091 let imports = vec![instance_decl("wasi:http/types")];
1092 let collisions = find_colliding_import_names(&imports);
1093 assert_eq!(collisions.len(), 0);
1094 }
1095
1096 #[test]
1097 fn empty_imports_no_collision() {
1098 let collisions = find_colliding_import_names(&[]);
1099 assert_eq!(collisions.len(), 0);
1100 }
1101
1102 #[test]
1105 fn no_collision_uses_short_name() {
1106 let wn = split_wit_name("wasi:http/types");
1107 let collisions = HashSet::new();
1108 let (ty, getter) = import_member_names(&wn, &collisions);
1109 assert_eq!(ty.to_string(), "Types");
1110 assert_eq!(getter.to_string(), "r#types");
1111 }
1112
1113 #[test]
1114 fn collision_prepends_parent_namespace() {
1115 let wn = split_wit_name("wasi:http/types");
1116 let collisions = find_colliding_import_names(&[
1117 instance_decl("wasi:http/types"),
1118 instance_decl("wasi:filesystem/types"),
1119 ]);
1120 let (ty, getter) = import_member_names(&wn, &collisions);
1121 assert_eq!(ty.to_string(), "WasiHttpTypes");
1122 assert_eq!(getter.to_string(), "r#wasi_http_types");
1123 }
1124
1125 #[test]
1126 fn collision_different_parents_produce_different_names() {
1127 let collisions = find_colliding_import_names(&[
1128 instance_decl("wasi:http/types"),
1129 instance_decl("wasi:filesystem/types"),
1130 ]);
1131
1132 let wn_http = split_wit_name("wasi:http/types");
1133 let (ty_http, getter_http) = import_member_names(&wn_http, &collisions);
1134
1135 let wn_fs = split_wit_name("wasi:filesystem/types");
1136 let (ty_fs, getter_fs) = import_member_names(&wn_fs, &collisions);
1137
1138 assert_eq!(ty_http.to_string(), "WasiHttpTypes");
1139 assert_eq!(ty_fs.to_string(), "WasiFilesystemTypes");
1140 assert_eq!(getter_http.to_string(), "r#wasi_http_types");
1141 assert_eq!(getter_fs.to_string(), "r#wasi_filesystem_types");
1142 }
1143
1144 #[test]
1145 fn collision_same_parent_different_package_produces_different_names() {
1146 let collisions = find_colliding_import_names(&[
1147 instance_decl("a:pkg/types"),
1148 instance_decl("b:pkg/types"),
1149 ]);
1150
1151 let wn_a = split_wit_name("a:pkg/types");
1152 let (ty_a, getter_a) = import_member_names(&wn_a, &collisions);
1153
1154 let wn_b = split_wit_name("b:pkg/types");
1155 let (ty_b, getter_b) = import_member_names(&wn_b, &collisions);
1156
1157 assert_eq!(ty_a.to_string(), "APkgTypes");
1158 assert_eq!(getter_a.to_string(), "r#a_pkg_types");
1159 assert_eq!(ty_b.to_string(), "BPkgTypes");
1160 assert_eq!(getter_b.to_string(), "r#b_pkg_types");
1161 }
1162
1163 #[test]
1164 fn colliding_bare_import_keeps_short_name() {
1165 let wn = split_wit_name("types");
1166 let collisions =
1167 find_colliding_import_names(&[instance_decl("types"), instance_decl("pkg:types")]);
1168 let (ty, getter) = import_member_names(&wn, &collisions);
1169 assert_eq!(ty.to_string(), "Types");
1170 assert_eq!(getter.to_string(), "r#types");
1171 }
1172
1173 #[test]
1174 fn versioned_collision_adds_version_after_namespace() {
1175 let collisions = find_colliding_import_names(&[
1176 instance_decl("a:pkg/types@1.0.0"),
1177 instance_decl("a:pkg/types@2.0.0"),
1178 ]);
1179
1180 let wn_v1 = split_wit_name("a:pkg/types@1.0.0");
1181 let (ty_v1, getter_v1) = import_member_names(&wn_v1, &collisions);
1182
1183 let wn_v2 = split_wit_name("a:pkg/types@2.0.0");
1184 let (ty_v2, getter_v2) = import_member_names(&wn_v2, &collisions);
1185
1186 assert_eq!(ty_v1.to_string(), "APkgTypesV100");
1187 assert_eq!(ty_v2.to_string(), "APkgTypesV200");
1188 assert_eq!(getter_v1.to_string(), "r#a_pkg_types_v1_0_0");
1189 assert_eq!(getter_v2.to_string(), "r#a_pkg_types_v2_0_0");
1190 }
1191
1192 #[test]
1193 fn version_is_added_for_colliding_versioned_imports() {
1194 let collisions = find_colliding_import_names(&[
1195 instance_decl("a:pkg/types@1.0.0"),
1196 instance_decl("b:pkg/types@1.0.0"),
1197 ]);
1198
1199 let wn = split_wit_name("a:pkg/types@1.0.0");
1200 let (ty, getter) = import_member_names(&wn, &collisions);
1201
1202 assert_eq!(ty.to_string(), "APkgTypesV100");
1203 assert_eq!(getter.to_string(), "r#a_pkg_types_v1_0_0");
1204 }
1205
1206 #[test]
1207 fn hyphenated_namespace_components_produce_distinct_type_names() {
1208 let collisions = find_colliding_import_names(&[
1212 instance_decl("a:b-c/types"),
1213 instance_decl("a-b:c/types"),
1214 ]);
1215
1216 let wn1 = split_wit_name("a:b-c/types");
1217 let wn2 = split_wit_name("a-b:c/types");
1218 let (ty1, getter1) = import_member_names(&wn1, &collisions);
1219 let (ty2, getter2) = import_member_names(&wn2, &collisions);
1220
1221 assert_eq!(ty1.to_string(), "AB_cTypes");
1222 assert_eq!(getter1.to_string(), "r#a_b_c_types");
1223 assert_eq!(ty2.to_string(), "AbCTypes");
1224 assert_eq!(getter2.to_string(), "r#ab_c_types");
1225 }
1226
1227 #[test]
1228 fn plain_and_hyphenated_namespace_components_produce_distinct_type_names() {
1229 let collisions = find_colliding_import_names(&[
1230 instance_decl("a:bc/types"),
1231 instance_decl("a:b-c/types"),
1232 ]);
1233
1234 let wn_plain = split_wit_name("a:bc/types");
1235 let wn_hyphenated = split_wit_name("a:b-c/types");
1236 let (ty_plain, getter_plain) = import_member_names(&wn_plain, &collisions);
1237 let (ty_hyphenated, getter_hyphenated) = import_member_names(&wn_hyphenated, &collisions);
1238
1239 assert_eq!(ty_plain.to_string(), "ABcTypes");
1240 assert_eq!(getter_plain.to_string(), "r#a_bc_types");
1241 assert_eq!(ty_hyphenated.to_string(), "AB_cTypes");
1242 assert_eq!(getter_hyphenated.to_string(), "r#a_b_c_types");
1243 }
1244}