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.iter_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 Rust
318#[derive(Debug)]
319pub struct State<'a, 'b> {
320    /// A pointer to a [`Mod`] that everything we emit will end up in
321    pub root_mod: &'a mut Mod,
322    /// A cursor to the current submodule (under [`State::root_mod`]),
323    /// where decls that we are looking at right now should end up
324    pub mod_cursor: Vec<Ident>,
325    /// If we are currently processing decls that should end up inside
326    /// a trait (representing an instance or a resource), this names
327    /// the trait where they should end up.
328    pub cur_trait: Option<Ident>,
329    /// We use a "helper module" for auxiliary definitions: for
330    /// example, an instance represented by `InstanceTrait` would end
331    /// up with nominal definitions for its nontrivial types in
332    /// `instance_trait::Type`.  This keeps track of the name of that
333    /// module, if it presently exists.
334    pub cur_helper_mod: Option<Ident>,
335    /// Whether the trait/type definition that we are currently
336    /// emitting is in the helper module or the main module
337    /// corresponding directly to the wit package. This is important
338    /// to get references to other types correct.
339    pub is_helper: bool,
340    /// All the bound variables in the component type that we are
341    /// currently processing
342    pub bound_vars: &'a mut VecDeque<BoundedTyvar<'b>>,
343    /// An offset into bound_vars from which any variable indices we
344    /// see in the source component type will be resolved; used to
345    /// deal with the fact that when we recurse down into a type in
346    /// the Eq bound of a type variable, its variables are offset from
347    /// ours (since we use de Bruijn indices).
348    pub var_offset: usize,
349    /// A path through instance import/export names from the root
350    /// component type to the type we are currently processing. This
351    /// is used with [`crate::etypes::TyvarOrigin`] to decide whether
352    /// a type variable we encounter is "locally defined", i.e. should
353    /// have a type definition emitted for it in this module.
354    pub origin: Vec<ImportExport<'b>>,
355    /// A set of type variables that we encountered while emitting the
356    /// type bound for a type variable.
357    pub cur_needs_vars: Option<&'a mut BTreeSet<u32>>,
358    /// A map from type variables to the type variables used in their
359    /// bounds, used to ensure that we are parametrized over the
360    /// things we need to be
361    pub vars_needs_vars: &'a mut VecDeque<BTreeSet<u32>>,
362    /// The Rust type parameter used to represent the type that
363    /// implements the imports of a component
364    pub import_param_var: Option<Ident>,
365    /// The Rust type parameter used to represent the current Rust
366    /// state type. In particular, this should let one find a `Self`
367    /// which Rust understands to be an impl of the instance trait
368    /// that `s.origin` refers to.
369    ///
370    /// # Note \[Origin paths and self parameters in impl codegen for higher-order components\]
371    ///
372    /// When extending impl (host.rs/guest.rs, as opposed to
373    /// rtypes.rs) codegen to higher-order components, it's not
374    /// entirely clear whether we should have this/the origin path
375    /// always refer to the true root component, or just to the
376    /// nearest component in which we are currently working.  At first
377    /// glance, updating the origin path at all during impl codegen
378    /// seems like a bad idea, since the impl should be instantiating
379    /// the Rust tyvars and can't generate new ones for
380    /// non-locally-defined types the way that rtypes codegen
381    /// can. However, it may be the case that referring to a tyvar
382    /// that does not follow our local definedness rules at the
383    /// granularity of components will be impossible due to the
384    /// `outer_boundary` rules! If this does turn out to be the case,
385    /// then updating `origin` on every instance the way that rtypes
386    /// does will still be a bad idea, but we might want to consider
387    /// updating it once per /component/.
388    ///
389    /// Whether or not `origin` gets updated, the trait bounds in
390    /// `self_param_var` need to match this. Currently, code in
391    /// host.rs/guest.rs does not update `origin`, but does update
392    /// `self_param_var`, which will need to be fixed when extending
393    /// higher-order component bindings generation to impls.
394    pub self_param_var: Option<TokenStream>,
395    /// The Rust type parameter used to represent the type that
396    /// provides the positivity of the (eventual use of the) current
397    /// component
398    pub positivity_param: Option<TokenStream>,
399    /// Whether we are emitting an implementation of the component
400    /// interfaces, or just the types of the interface
401    pub is_impl: bool,
402    /// A namespace path and a name representing the Rust trait
403    /// generated for the root component that we started codegen from
404    pub root_component_name: Option<(TokenStream, &'a str)>,
405    /// Whether we are generating code for the Hyperlight host or the
406    /// Hyperlight guest
407    pub is_guest: bool,
408    /// A temporary hack to enable some special cases used by the
409    /// wasmtime guest emit. When that is refactored to use the host
410    /// guest emit, this can go away.
411    pub is_wasmtime_guest: bool,
412    /// Set of interface names that collide across different packages
413    /// (e.g. "types" appears in both wasi:filesystem/types and wasi:http/types).
414    /// When a name is in this set, the parent namespace is prepended to
415    /// disambiguate the trait member name.
416    pub colliding_import_names: HashSet<String>,
417}
418
419/// Create a State with all of its &mut references pointing to
420/// sensible things, run a function that emits code into the state,
421/// and then generate a token stream representing everything emitted
422pub 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    /// Obtain a reference to the [`Mod`] that we are currently
495    /// generating code in, creating it if necessary
496    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    /// Obtain an immutable reference to the [`Mod`] that we are
507    /// currently generating code in.
508    ///
509    /// Precondition: the module must already exist
510    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    /// Copy the state, changing its module cursor to emit code into a
521    /// different module
522    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    /// Copy the state, replacing its [`State::cur_needs_vars`] reference,
528    /// allowing a caller to capture the vars referenced by any emit
529    /// run with the resultant state
530    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    /// Copy the state, replacing its [`State::root_mod`] reference,
536    /// allowing a caller to capture _only_ the effects on
537    /// [`State::cur_needs_vars`]/[`State::vars_needs_vars`] of an
538    /// emit run with the resultant state
539    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    /// Record that an emit sequence needed a var, given an absolute
547    /// index for the var (i.e. ignoring [`State::var_offset`])
548    pub fn need_noff_var(&mut self, n: u32) {
549        self.cur_needs_vars.as_mut().map(|vs| vs.insert(n));
550    }
551    /// Use the [`State::cur_needs_vars`] map to populate
552    /// [`State::vars_needs_vars`] for a var that we presumably just
553    /// finished emitting a bound for
554    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    /// Get a list of all the variables needed by a var, given its absolute
566    /// index (i.e. ignoring [`State::var_offset`])
567    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    /// Find the exported name which gave rise to a component type
580    /// variable, given its absolute index (i.e. ignoring
581    /// [`State::var_offset`])
582    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    /// Copy the state, changing it to emit into the helper module of
596    /// the current trait
597    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    /// Construct a namespace token stream that can be emitted in the
603    /// current module to refer to a name in the root module
604    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    /// Construct a namespace token stream that can be emitted in the
619    /// current module to refer to a name in the helper module
620    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    /// Emit a namespace token stream that can be emitted in the root
638    /// module to refer to the current trait
639    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    /// Add a supertrait constraint referring to a trait in the helper
645    /// module; primarily used to add a constraint for the trait
646    /// representing a resource type.
647    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    /// Obtain a reference to the [`Trait`] that we are currently
657    /// generating code in, creating it if necessary.
658    ///
659    /// Precondition: we are currently generating code in a trait
660    /// (i.e. [`State::cur_trait`] is not [`None`])
661    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    /// Obtain an immutable reference to the [`Trait`] that we are
666    /// currently generating code in.
667    ///
668    /// Precondition: we are currently generating code in a trait
669    /// (i.e. [`State::cur_trait`] is not [`None`]), and that trait has
670    /// already been created
671    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    /// Obtain a reference to the trait at the given module path and
676    /// name from the root module, creating it and any named modules
677    /// if necessary
678    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    /// Add an import/export to [`State::origin`], reflecting that we are now
686    /// looking at code underneath it
687    ///
688    /// origin_was_export does not keep track of whether the item
689    /// overall was imported or exported from the root component
690    /// (taking into account positivity); it just checks if this
691    /// particular extern_decl was imported or exported from its
692    /// parent instance (and so e.g. an export of an instance that is
693    /// imported by the root component has origin_was_export).  Any
694    /// decisions that depend on positivity from the root component
695    /// should be made part of the
696    /// [`hyperlight_common::component::Positivity`] trait, which
697    /// correctly handles the fact that the same interface trait may
698    /// be used in both positive and negative positions.
699    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    /// Find out if a [`Defined`] type is actually a reference to a
709    /// locally defined type variable, returning its index and bound
710    /// if it is
711    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    /// Find out if a variable is locally-defined given its absolute
729    /// index, returning its origin and bound if it is
730    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    /// Obtain an immutable reference to the trait at the specified
740    /// namespace path, either from the root module (if `absolute`)
741    /// is true, or from the current module
742    ///
743    /// Precondition: all named traits/modules must exist
744    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    /// Shift all of the type variable indices over, because we have
757    /// gone under some binders.  Used when we switch from looking at
758    /// a component's import types (where type idxs are de Bruijn into
759    /// the component's uvar list) to a component's export types
760    /// (where type idx are de Bruijn first into the evar list and
761    /// then the uvar list, as we go under the existential binders).
762    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    /// Resolve a type variable as far as possible: either this ends
776    /// up with a definition, in which case, let's get that, or it
777    /// ends up with a resource type, in which case we return the
778    /// resource index
779    ///
780    /// Distinct from [`Ctx::resolve_tv`], which is mostly concerned
781    /// with free variables, because this is concerned entirely with
782    /// bound variables.
783    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    /// Construct a namespace path referring to the resource trait for
798    /// a resource with the given name
799    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/// A parsed representation of a WIT name, containing package
813/// namespaces, an actual name, and possibly a SemVer version
814#[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    /// Extract a list of Rust module names corresponding to the WIT
822    /// namespace/package
823    pub fn namespace_idents(&self) -> Vec<Ident> {
824        self.namespaces
825            .iter()
826            .map(|x| kebab_to_namespace(x))
827            .collect::<Vec<_>>()
828    }
829    /// Extract a token stream representing the Rust namespace path
830    /// corresponding to the WIT namespace/package
831    pub fn namespace_path(&self) -> TokenStream {
832        let ns = self.namespace_idents();
833        quote! { #(#ns)::* }
834    }
835}
836/// Parse a kebab-name as a WIT name
837pub 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
886/// Convert a kebab name to something suitable for use as a
887/// (value-level) variable
888pub fn kebab_to_var(n: &str) -> Ident {
889    kebab_to_snake(n)
890}
891/// Convert a kebab name to something suitable for use as a
892/// type constructor
893pub fn kebab_to_cons(n: &str) -> Ident {
894    kebab_to_camel(n)
895}
896/// Convert a kebab name to something suitable for use as a getter
897/// function name
898pub fn kebab_to_getter(n: &str) -> Ident {
899    kebab_to_snake(n)
900}
901/// Convert a kebab name to something suitable for use as a type name
902pub fn kebab_to_type(n: &str) -> Ident {
903    kebab_to_camel(n)
904}
905/// Convert a kebab name to something suitable for use as a module
906/// name/namespace path entry
907pub fn kebab_to_namespace(n: &str) -> Ident {
908    kebab_to_snake(n)
909}
910/// From a kebab name for a Component, derive something suitable for
911/// use as the name of the imports trait for that component
912pub fn kebab_to_imports_name(trait_name: &str) -> Ident {
913    format_ident!("{}Imports", kebab_to_type(trait_name))
914}
915/// From a kebab name for a Component, derive something suitable for
916/// use as the name of the imports trait for that component
917pub fn kebab_to_exports_name(trait_name: &str) -> Ident {
918    format_ident!("{}Exports", kebab_to_type(trait_name))
919}
920/// Convert a kebab name to a SCREAMING_SNAKE_CASE identifier suitable
921/// for use as a constant in a `wasmtime::component::flags!` invocation.
922pub 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
936/// The kinds of names that a function associated with a resource in
937/// WIT can have
938pub enum ResourceItemName {
939    Constructor,
940    Method(Ident),
941    Static(Ident),
942}
943
944/// The kinds of names that a function in WIT can have
945pub enum FnName {
946    Associated(Ident, ResourceItemName),
947    Plain(Ident),
948}
949/// Parse a kebab-name as a WIT function name, figuring out if it is
950/// associated with a resource
951pub 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    /// Helper to build a minimal `ExternDecl` whose desc is an Instance.
982    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    /// Helper to build a minimal `ExternDecl` whose desc is a Func (not an Instance).
992    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    // --- split_wit_name tests ---
1003
1004    #[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    // --- find_colliding_import_names tests ---
1026
1027    #[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    // --- import_member_names tests ---
1083
1084    #[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        // "a:b-c/types" has a hyphenated package component, while
1189        // "a-b:c/types" has a hyphenated namespace component. Preserving the
1190        // package hyphen as `_` keeps the generated names distinct.
1191        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}