Skip to main content

hyperlight_component_util/
emit.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2025 The Hyperlight Authors.
3
4//! A bunch of utilities used by the actual code emit functions
5use 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
30/// Scan a list of import extern decls for interface name collisions.
31/// Returns short import names that need disambiguating.
32pub 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
47/// Return `(type_name, getter_name)` for an import instance.
48///
49/// Colliding interface names are emitted with their namespace-qualified name,
50/// plus the version suffix when present:
51/// `wasi:http/types` -> `WasiHttpTypes` / `wasi_http_types`.
52pub 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        // Preserve package hyphens as `_` so `a:bc/types` and
73        // `a:b-c/types` don't collide.
74        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        // Use the same boundary-preserving approach for type names.
83        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
106/// Capitalize only the first letter of a kebab component, removing hyphens
107/// without capitalizing subsequent sub-words.
108fn 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/// A representation of a trait definition that we will eventually
123/// emit. This is used to allow easily adding onto the trait each time
124/// we see an extern decl.
125#[derive(Clone, Debug, Default)]
126pub struct Trait {
127    /// A set of supertrait constraints, each associated with a
128    /// bindings module path
129    pub supertraits: BTreeMap<Vec<Ident>, TokenStream>,
130    /// Keep track for each type variable of:
131    /// - The identifier that we use for it in the generated source
132    /// - Whether it comes from a component type variable, and if so,
133    ///   which one. (Most do; the I: Imports on the main component
134    ///   trait is the main one that doesn't).
135    /// - Whether there are any bounds on it
136    pub tvs: BTreeMap<Ident, (Option<u32>, TokenStream)>,
137    /// Raw tokens of the contents of the trait
138    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    /// Collect the component tyvar indices that correspond to the
149    /// type variables on this trait.
150    ///
151    /// Precondition: all of the type
152    /// variables on this trait do correspond to component variables.
153    pub fn tv_idxs(&self) -> Vec<u32> {
154        self.tvs.iter().map(|(_, (n, _))| n.unwrap()).collect()
155    }
156    /// See [`State::adjust_vars`].
157    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    /// Build a token stream of all type variables and trait bounds on
165    /// them, e.g. what you would put "inside" the <> in trait T<...>.
166    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    /// Build a token stream for the type variable part of the trait
182    /// declaration
183    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    /// Build a token stream for this entire trait definition
193    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/// A representation of a module definition that we will eventually
215/// emit. This is used to allow easily adding onto the module each time
216/// we see a relevant decl.
217#[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    /// Get a reference to a sub-module, creating it if necessary
234    pub fn submod<'a>(&'a mut self, i: Ident) -> &'a mut Self {
235        self.submods.entry(i).or_insert(Self::empty())
236    }
237    /// Get an immutable reference to a sub-module
238    ///
239    /// Precondition: the named submodule must already exist
240    pub fn submod_immut<'a>(&'a self, i: Ident) -> &'a Self {
241        &self.submods[&i]
242    }
243    /// Get a reference to a trait definition in this module, creating
244    /// it if necessary
245    pub fn r#trait<'a>(&'a mut self, i: Ident) -> &'a mut Trait {
246        self.traits.entry(i).or_default()
247    }
248    /// Get an immutable reference to a trait definition in this module
249    ///
250    /// Precondition: the named trait must already exist
251    pub fn trait_immut<'a>(&'a self, i: Ident) -> &'a Trait {
252        &self.traits[&i]
253    }
254    /// Get a reference to an impl block that is in this module,
255    /// creating it if necessary.
256    ///
257    /// Currently, we don't track much information about these, so
258    /// it's just a mutable token stream.
259    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    /// See [`State::adjust_vars`].
263    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    /// Build a token stream for this entire module
274    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
295/// Unlike [`tv::ResolvedTyvar`], which is mostly concerned with free
296/// variables and leaves bound variables alone, this tells us the most
297/// information that we have at codegen time for a top level bound
298/// variable.
299pub enum ResolvedBoundVar<'a> {
300    Definite {
301        /// The final variable offset (relative to s.var_offset) that
302        /// we followed to get to this definite type, used
303        /// occasionally to name things.
304        final_bound_var: u32,
305        /// The actual definite type that this resolved to
306        ty: Defined<'a>,
307    },
308    Resource {
309        /// A resource-type index. Currently a resource-type index is
310        /// the same as the de Bruijn index of the tyvar that
311        /// introduced the resource type, but is never affected by
312        /// e.g. s.var_offset.
313        rtidx: u32,
314    },
315}
316
317/// A whole grab-bag of useful state to have while emitting bindings.
318/// `M` holds the output. [`Mod`] holds Rust output.
319#[derive(Debug)]
320pub struct State<'a, 'b, M = Mod> {
321    /// A pointer to the output that everything we emit will end up in
322    pub root_mod: &'a mut M,
323    /// A cursor to the current submodule (under [`State::root_mod`]),
324    /// where decls that we are looking at right now should end up
325    pub mod_cursor: Vec<Ident>,
326    /// If we are currently processing decls that should end up inside
327    /// a trait (representing an instance or a resource), this names
328    /// the trait where they should end up.
329    pub cur_trait: Option<Ident>,
330    /// We use a "helper module" for auxiliary definitions: for
331    /// example, an instance represented by `InstanceTrait` would end
332    /// up with nominal definitions for its nontrivial types in
333    /// `instance_trait::Type`.  This keeps track of the name of that
334    /// module, if it presently exists.
335    pub cur_helper_mod: Option<Ident>,
336    /// Whether the trait/type definition that we are currently
337    /// emitting is in the helper module or the main module
338    /// corresponding directly to the wit package. This is important
339    /// to get references to other types correct.
340    pub is_helper: bool,
341    /// All the bound variables in the component type that we are
342    /// currently processing
343    pub bound_vars: &'a mut VecDeque<BoundedTyvar<'b>>,
344    /// An offset into bound_vars from which any variable indices we
345    /// see in the source component type will be resolved; used to
346    /// deal with the fact that when we recurse down into a type in
347    /// the Eq bound of a type variable, its variables are offset from
348    /// ours (since we use de Bruijn indices).
349    pub var_offset: usize,
350    /// A path through instance import/export names from the root
351    /// component type to the type we are currently processing. This
352    /// is used with [`crate::etypes::TyvarOrigin`] to decide whether
353    /// a type variable we encounter is "locally defined", i.e. should
354    /// have a type definition emitted for it in this module.
355    pub origin: Vec<ImportExport<'b>>,
356    /// A set of type variables that we encountered while emitting the
357    /// type bound for a type variable.
358    pub cur_needs_vars: Option<&'a mut BTreeSet<u32>>,
359    /// A map from type variables to the type variables used in their
360    /// bounds, used to ensure that we are parametrized over the
361    /// things we need to be
362    pub vars_needs_vars: &'a mut VecDeque<BTreeSet<u32>>,
363    /// The Rust type parameter used to represent the type that
364    /// implements the imports of a component
365    pub import_param_var: Option<Ident>,
366    /// The Rust type parameter used to represent the current Rust
367    /// state type. In particular, this should let one find a `Self`
368    /// which Rust understands to be an impl of the instance trait
369    /// that `s.origin` refers to.
370    ///
371    /// # Note \[Origin paths and self parameters in impl codegen for higher-order components\]
372    ///
373    /// When extending impl (host.rs/guest.rs, as opposed to
374    /// rtypes.rs) codegen to higher-order components, it's not
375    /// entirely clear whether we should have this/the origin path
376    /// always refer to the true root component, or just to the
377    /// nearest component in which we are currently working.  At first
378    /// glance, updating the origin path at all during impl codegen
379    /// seems like a bad idea, since the impl should be instantiating
380    /// the Rust tyvars and can't generate new ones for
381    /// non-locally-defined types the way that rtypes codegen
382    /// can. However, it may be the case that referring to a tyvar
383    /// that does not follow our local definedness rules at the
384    /// granularity of components will be impossible due to the
385    /// `outer_boundary` rules! If this does turn out to be the case,
386    /// then updating `origin` on every instance the way that rtypes
387    /// does will still be a bad idea, but we might want to consider
388    /// updating it once per /component/.
389    ///
390    /// Whether or not `origin` gets updated, the trait bounds in
391    /// `self_param_var` need to match this. Currently, code in
392    /// host.rs/guest.rs does not update `origin`, but does update
393    /// `self_param_var`, which will need to be fixed when extending
394    /// higher-order component bindings generation to impls.
395    pub self_param_var: Option<TokenStream>,
396    /// The Rust type parameter used to represent the type that
397    /// provides the positivity of the (eventual use of the) current
398    /// component
399    pub positivity_param: Option<TokenStream>,
400    /// Whether we are emitting an implementation of the component
401    /// interfaces, or just the types of the interface
402    pub is_impl: bool,
403    /// A namespace path and a name representing the Rust trait
404    /// generated for the root component that we started codegen from
405    pub root_component_name: Option<(TokenStream, &'a str)>,
406    /// Whether we are generating code for the Hyperlight host or the
407    /// Hyperlight guest
408    pub is_guest: bool,
409    /// A temporary hack to enable some special cases used by the
410    /// wasmtime guest emit. When that is refactored to use the host
411    /// guest emit, this can go away.
412    pub is_wasmtime_guest: bool,
413    /// Set of interface names that collide across different packages
414    /// (e.g. "types" appears in both wasi:filesystem/types and wasi:http/types).
415    /// When a name is in this set, the parent namespace is prepended to
416    /// disambiguate the trait member name.
417    pub colliding_import_names: HashSet<String>,
418    /// Full kebab name of the instance whose functions are being
419    /// emitted, e.g. `wasi:http/types@0.2.0`. Wire function names are
420    /// derived from it.
421    pub cur_instance: Option<String>,
422}
423
424/// Create a State with all of its &mut references pointing to
425/// sensible things, run a function that emits code into the state,
426/// and then generate a token stream representing everything emitted
427pub 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    /// Resolve a type variable as far as possible: either this ends
502    /// up with a definition, in which case, let's get that, or it
503    /// ends up with a resource type, in which case we return the
504    /// resource index
505    ///
506    /// Distinct from [`Ctx::resolve_tv`], which is mostly concerned
507    /// with free variables, because this is concerned entirely with
508    /// bound variables.
509    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    /// Obtain a reference to the [`Mod`] that we are currently
526    /// generating code in, creating it if necessary
527    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    /// Obtain an immutable reference to the [`Mod`] that we are
538    /// currently generating code in.
539    ///
540    /// Precondition: the module must already exist
541    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    /// Copy the state, changing its module cursor to emit code into a
552    /// different module
553    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    /// Copy the state to emit the functions of the instance `wn`,
559    /// moving the module cursor to its namespace.
560    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    /// Copy the state, replacing its [`State::cur_needs_vars`] reference,
566    /// allowing a caller to capture the vars referenced by any emit
567    /// run with the resultant state
568    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    /// Copy the state, replacing its [`State::root_mod`] reference,
574    /// allowing a caller to capture _only_ the effects on
575    /// [`State::cur_needs_vars`]/[`State::vars_needs_vars`] of an
576    /// emit run with the resultant state
577    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    /// Record that an emit sequence needed a var, given an absolute
585    /// index for the var (i.e. ignoring [`State::var_offset`])
586    pub fn need_noff_var(&mut self, n: u32) {
587        self.cur_needs_vars.as_mut().map(|vs| vs.insert(n));
588    }
589    /// Use the [`State::cur_needs_vars`] map to populate
590    /// [`State::vars_needs_vars`] for a var that we presumably just
591    /// finished emitting a bound for
592    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    /// Get a list of all the variables needed by a var, given its absolute
604    /// index (i.e. ignoring [`State::var_offset`])
605    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    /// Find the exported name which gave rise to a component type
618    /// variable, given its absolute index (i.e. ignoring
619    /// [`State::var_offset`])
620    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    /// Copy the state, changing it to emit into the helper module of
634    /// the current trait
635    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    /// Construct a namespace token stream that can be emitted in the
641    /// current module to refer to a name in the root module
642    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    /// Construct a namespace token stream that can be emitted in the
657    /// current module to refer to a name in the helper module
658    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    /// Emit a namespace token stream that can be emitted in the root
676    /// module to refer to the current trait
677    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    /// Add a supertrait constraint referring to a trait in the helper
683    /// module; primarily used to add a constraint for the trait
684    /// representing a resource type.
685    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    /// Obtain a reference to the [`Trait`] that we are currently
695    /// generating code in, creating it if necessary.
696    ///
697    /// Precondition: we are currently generating code in a trait
698    /// (i.e. [`State::cur_trait`] is not [`None`])
699    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    /// Obtain an immutable reference to the [`Trait`] that we are
704    /// currently generating code in.
705    ///
706    /// Precondition: we are currently generating code in a trait
707    /// (i.e. [`State::cur_trait`] is not [`None`]), and that trait has
708    /// already been created
709    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    /// Obtain a reference to the trait at the given module path and
714    /// name from the root module, creating it and any named modules
715    /// if necessary
716    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    /// Add an import/export to [`State::origin`], reflecting that we are now
724    /// looking at code underneath it
725    ///
726    /// origin_was_export does not keep track of whether the item
727    /// overall was imported or exported from the root component
728    /// (taking into account positivity); it just checks if this
729    /// particular extern_decl was imported or exported from its
730    /// parent instance (and so e.g. an export of an instance that is
731    /// imported by the root component has origin_was_export).  Any
732    /// decisions that depend on positivity from the root component
733    /// should be made part of the
734    /// [`hyperlight_common::component::Positivity`] trait, which
735    /// correctly handles the fact that the same interface trait may
736    /// be used in both positive and negative positions.
737    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    /// Find out if a [`Defined`] type is actually a reference to a
747    /// locally defined type variable, returning its index and bound
748    /// if it is
749    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    /// Find out if a variable is locally-defined given its absolute
767    /// index, returning its origin and bound if it is
768    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    /// Obtain an immutable reference to the trait at the specified
778    /// namespace path, either from the root module (if `absolute`)
779    /// is true, or from the current module
780    ///
781    /// Precondition: all named traits/modules must exist
782    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    /// Shift all of the type variable indices over, because we have
795    /// gone under some binders.  Used when we switch from looking at
796    /// a component's import types (where type idxs are de Bruijn into
797    /// the component's uvar list) to a component's export types
798    /// (where type idx are de Bruijn first into the evar list and
799    /// then the uvar list, as we go under the existential binders).
800    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    /// Construct a namespace path referring to the resource trait for
815    /// a resource with the given name
816    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/// A parsed representation of a WIT name, containing package
830/// namespaces, an actual name, and possibly a SemVer version
831#[derive(Debug, Clone)]
832pub struct WitName<'a> {
833    /// The unparsed name
834    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    /// Extract a list of Rust module names corresponding to the WIT
841    /// namespace/package
842    pub fn namespace_idents(&self) -> Vec<Ident> {
843        self.namespaces
844            .iter()
845            .map(|x| kebab_to_namespace(x))
846            .collect::<Vec<_>>()
847    }
848    /// Extract a token stream representing the Rust namespace path
849    /// corresponding to the WIT namespace/package
850    pub fn namespace_path(&self) -> TokenStream {
851        let ns = self.namespace_idents();
852        quote! { #(#ns)::* }
853    }
854}
855/// Parse a kebab-name as a WIT name
856pub 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
906/// Convert a kebab name to something suitable for use as a
907/// (value-level) variable
908pub fn kebab_to_var(n: &str) -> Ident {
909    kebab_to_snake(n)
910}
911/// Convert a kebab name to something suitable for use as a
912/// type constructor
913pub fn kebab_to_cons(n: &str) -> Ident {
914    kebab_to_camel(n)
915}
916/// Convert a kebab name to something suitable for use as a getter
917/// function name
918pub fn kebab_to_getter(n: &str) -> Ident {
919    kebab_to_snake(n)
920}
921/// Convert a kebab name to something suitable for use as a type name
922pub fn kebab_to_type(n: &str) -> Ident {
923    kebab_to_camel(n)
924}
925/// Convert a kebab name to something suitable for use as a module
926/// name/namespace path entry
927pub fn kebab_to_namespace(n: &str) -> Ident {
928    kebab_to_snake(n)
929}
930/// From a kebab name for a Component, derive something suitable for
931/// use as the name of the imports trait for that component
932pub fn kebab_to_imports_name(trait_name: &str) -> Ident {
933    format_ident!("{}Imports", kebab_to_type(trait_name))
934}
935/// From a kebab name for a Component, derive something suitable for
936/// use as the name of the imports trait for that component
937pub fn kebab_to_exports_name(trait_name: &str) -> Ident {
938    format_ident!("{}Exports", kebab_to_type(trait_name))
939}
940/// Convert a kebab name to a SCREAMING_SNAKE_CASE identifier suitable
941/// for use as a constant in a `wasmtime::component::flags!` invocation.
942pub 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
956/// The kinds of names that a function associated with a resource in
957/// WIT can have
958pub enum ResourceItemName {
959    Constructor,
960    Method(Ident),
961    Static(Ident),
962}
963
964/// The kinds of names that a function in WIT can have
965pub enum FnName {
966    Associated(Ident, ResourceItemName),
967    Plain(Ident),
968}
969/// Parse a kebab-name as a WIT function name, figuring out if it is
970/// associated with a resource
971pub 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    /// Helper to build a minimal `ExternDecl` whose desc is an Instance.
1002    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    /// Helper to build a minimal `ExternDecl` whose desc is a Func (not an Instance).
1012    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    // --- split_wit_name tests ---
1023
1024    #[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    // --- find_colliding_import_names tests ---
1046
1047    #[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    // --- import_member_names tests ---
1103
1104    #[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        // "a:b-c/types" has a hyphenated package component, while
1209        // "a-b:c/types" has a hyphenated namespace component. Preserving the
1210        // package hyphen as `_` keeps the generated names distinct.
1211        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}