use std::collections::{BTreeMap, BTreeSet, VecDeque};
use std::vec::Vec;
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use syn::Ident;
use crate::emit::{
FnName, ResourceItemName, State, WitName, kebab_to_cons, kebab_to_exports_name, kebab_to_fn,
kebab_to_getter, kebab_to_imports_name, kebab_to_namespace, kebab_to_type, kebab_to_var,
split_wit_name,
};
use crate::etypes::{
self, Component, Defined, ExternDecl, ExternDesc, Func, Handleable, ImportExport, Instance,
Param, TypeBound, Tyvar, Value,
};
fn emit_tvis(s: &mut State, tvs: Vec<u32>) -> TokenStream {
let tvs = tvs
.iter()
.map(|tv| emit_var_ref(s, &Tyvar::Bound(*tv)))
.collect::<Vec<_>>();
if !tvs.is_empty() {
quote! { <#(#tvs),*> }
} else {
TokenStream::new()
}
}
fn emit_resource_ref(s: &mut State, n: u32, path: Vec<ImportExport>) -> TokenStream {
if s.is_guest && s.is_impl {
let id = format_ident!("HostResource{}", n);
return quote! { #id };
}
let rtrait = kebab_to_type(path[path.len() - 1].name());
if path.len() == 1 {
let helper = s.cur_helper_mod.clone().unwrap();
let rtrait = kebab_to_type(path[0].name());
let t = s.resolve_trait_immut(false, &[helper.clone(), rtrait.clone()]);
let tvis = emit_tvis(s, t.tv_idxs());
let mut sv = quote! { Self };
if let Some(s) = &s.self_param_var {
sv = quote! { #s };
};
return quote! { <#sv as #helper::#rtrait #tvis>::T };
};
let instance = path[path.len() - 2].name();
let iwn = split_wit_name(instance);
let extras = path[0..path.len() - 2]
.iter()
.map(|p| {
let wn = split_wit_name(p.name());
kebab_to_type(wn.name)
})
.collect::<Vec<_>>();
let extras = quote! { #(#extras::)* };
let rp = s.root_path();
let tns = iwn.namespace_path();
let instance_mod = kebab_to_namespace(iwn.name);
let instance_type = kebab_to_type(iwn.name);
let mut sv = quote! { Self };
if path[path.len() - 2].imported() {
if let Some(iv) = &s.import_param_var {
sv = quote! { #iv }
};
} else if let Some(s) = &s.self_param_var {
sv = quote! { #s }
};
let mut trait_path = Vec::new();
trait_path.extend(iwn.namespace_idents());
trait_path.push(instance_mod.clone());
trait_path.push(rtrait.clone());
let t = s.resolve_trait_immut(true, &trait_path);
let tvis = emit_tvis(s, t.tv_idxs());
quote! { <#sv::#extras #instance_type as #rp #tns::#instance_mod::#rtrait #tvis>::T }
}
fn try_find_local_var_id(
s: &mut State,
n: u32,
) -> Option<TokenStream> {
if let Some((path, bound)) = s.is_noff_var_local(n) {
let var_is_helper = match bound {
TypeBound::Eq(_) => true,
TypeBound::SubResource => false,
};
if !var_is_helper {
if s.is_helper {
if path.len() == 1 && s.cur_trait == Some(kebab_to_type(path[0].name())) {
return Some(quote! { Self::T });
}
return None;
} else {
let mut path_strs = vec!["".to_string(); path.len()];
for (i, p) in path.iter().enumerate() {
path_strs[i] = p.name().to_string();
}
let path = path
.into_iter()
.enumerate()
.map(|(i, p)| match p {
ImportExport::Import(_) => ImportExport::Import(&path_strs[i]),
ImportExport::Export(_) => ImportExport::Export(&path_strs[i]),
})
.collect::<Vec<_>>();
return Some(emit_resource_ref(s, n, path));
}
}
tracing::debug!("path is {:?}\n", path);
let mut path = path.iter().rev();
let name = kebab_to_type(path.next().unwrap().name());
let owner = path.next();
if let Some(owner) = owner {
let wn = split_wit_name(owner.name());
let rp = s.root_path();
let tns = wn.namespace_path();
let helper = kebab_to_namespace(wn.name);
Some(quote! { #rp #tns::#helper::#name })
} else {
let hp = s.helper_path();
Some(quote! { #hp #name })
}
} else {
None
}
}
pub fn emit_var_ref(s: &mut State, tv: &Tyvar) -> TokenStream {
let Tyvar::Bound(n) = tv else {
panic!("free tyvar in rust emit")
};
emit_var_ref_noff(s, n + s.var_offset as u32, false)
}
pub fn emit_var_ref_value(s: &mut State, tv: &Tyvar) -> TokenStream {
let Tyvar::Bound(n) = tv else {
panic!("free tyvar in rust emit")
};
emit_var_ref_noff(s, n + s.var_offset as u32, true)
}
pub fn emit_var_ref_noff(s: &mut State, n: u32, is_value: bool) -> TokenStream {
tracing::debug!("var_ref {:?} {:?}", &s.bound_vars[n as usize], s.origin);
let id = try_find_local_var_id(s, n);
let id = match id {
Some(id) => {
let vs = s.get_noff_var_refs(n);
let vs = vs
.iter()
.map(|n| emit_var_ref_noff(s, *n, false))
.collect::<Vec<_>>();
let vs_toks = if !vs.is_empty() {
if is_value {
quote! { ::<#(#vs),*> }
} else {
quote! { <#(#vs),*> }
}
} else {
TokenStream::new()
};
quote! { #id #vs_toks }
}
None => {
s.need_noff_var(n);
let id = s.noff_var_id(n);
quote! { #id }
}
};
quote! { #id }
}
pub fn numeric_rtype(vt: &Value) -> (Ident, u8) {
match vt {
Value::S(w) => (format_ident!("i{}", w.width()), w.width()),
Value::U(w) => (format_ident!("u{}", w.width()), w.width()),
Value::F(w) => (format_ident!("f{}", w.width()), w.width()),
_ => panic!("numeric_rtype: internal invariant violation"),
}
}
pub fn emit_value(s: &mut State, vt: &Value) -> TokenStream {
match vt {
Value::Bool => quote! { bool },
Value::S(_) | Value::U(_) | Value::F(_) => {
let (id, _) = numeric_rtype(vt);
quote! { #id }
}
Value::Char => quote! { char },
Value::String => quote! { alloc::string::String },
Value::List(vt) => {
let vt = emit_value(s, vt);
quote! { alloc::vec::Vec<#vt> }
}
Value::FixList(vt, size) => {
let vt = emit_value(s, vt);
let size = *size as usize;
quote! { [#vt; #size] }
}
Value::Record(_) => panic!("record not at top level of valtype"),
Value::Tuple(vts) => {
let vts = vts.iter().map(|vt| emit_value(s, vt)).collect::<Vec<_>>();
quote! { (#(#vts),*) }
}
Value::Flags(_) => panic!("flags not at top level of valtype"),
Value::Variant(_) => panic!("flags not at top level of valtype"),
Value::Enum(_) => panic!("enum not at top level of valtype"),
Value::Option(vt) => {
let vt = emit_value(s, vt);
quote! { ::core::option::Option<#vt> }
}
Value::Result(vt1, vt2) => {
let unit = Value::Tuple(Vec::new());
let vt1 = emit_value(s, vt1.as_ref().as_ref().unwrap_or(&unit));
let vt2 = emit_value(s, vt2.as_ref().as_ref().unwrap_or(&unit));
quote! { ::core::result::Result<#vt1, #vt2> }
}
Value::Own(ht) => match ht {
Handleable::Resource(_) => panic!("bare resource in type"),
Handleable::Var(tv) => {
if s.is_guest {
let wrap = if s.is_wasmtime_guest {
|toks| quote! { ::wasmtime::component::Resource<#toks> }
} else {
|toks| toks
};
if !s.is_impl {
wrap(emit_var_ref(s, tv))
} else {
let n = crate::hl::resolve_handleable_to_resource(s, ht);
tracing::debug!("resolved ht to r (4) {:?} {:?}", ht, n);
let id = format_ident!("HostResource{}", n);
wrap(quote! { #id })
}
} else {
emit_var_ref(s, tv)
}
}
},
Value::Borrow(ht) => match ht {
Handleable::Resource(_) => panic!("bare resource in type"),
Handleable::Var(tv) => {
if s.is_guest {
let wrap = if s.is_wasmtime_guest {
|toks| quote! { ::wasmtime::component::Resource<#toks> }
} else {
|toks| quote! { &#toks }
};
if !s.is_impl {
wrap(emit_var_ref(s, tv))
} else {
let n = crate::hl::resolve_handleable_to_resource(s, ht);
tracing::debug!("resolved ht to r (5) {:?} {:?}", ht, n);
let id = format_ident!("HostResource{}", n);
wrap(quote! { #id })
}
} else {
let vr = emit_var_ref(s, tv);
if s.is_export {
quote! { &#vr }
} else {
quote! { ::hyperlight_common::resource::BorrowedResourceGuard<#vr> }
}
}
}
},
Value::Var(Some(tv), _) => emit_var_ref(s, tv),
Value::Var(None, _) => panic!("value type with recorded but unknown var"),
}
}
fn emit_value_toplevel(s: &mut State, v: Option<u32>, id: Ident, vt: &Value) -> TokenStream {
let is_wasmtime_guest = s.is_wasmtime_guest;
match vt {
Value::Record(rfs) => {
let (vs, toks) = gather_needed_vars(s, v, |s| {
let rfs = rfs
.iter()
.map(|rf| {
let orig_name = rf.name.name;
let id = kebab_to_var(orig_name);
let derives = if s.is_wasmtime_guest {
quote! { #[component(name = #orig_name)] }
} else {
TokenStream::new()
};
let ty = emit_value(s, &rf.ty);
quote! { #derives pub #id: #ty }
})
.collect::<Vec<_>>();
quote! { #(#rfs),* }
});
let vs = emit_type_defn_var_list(s, vs);
let derives = if s.is_wasmtime_guest {
quote! {
#[derive(::wasmtime::component::ComponentType)]
#[derive(::wasmtime::component::Lift)]
#[derive(::wasmtime::component::Lower)]
#[component(record)]
}
} else {
TokenStream::new()
};
quote! {
#derives
#[derive(Debug)]
pub struct #id #vs { #toks }
}
}
Value::Flags(ns) => {
let (vs, toks) = gather_needed_vars(s, v, |_| {
let ns = ns
.iter()
.map(|n| {
let orig_name = n.name;
let id = kebab_to_var(orig_name);
quote! { pub #id: bool }
})
.collect::<Vec<_>>();
quote! { #(#ns),* }
});
let vs = emit_type_defn_var_list(s, vs);
quote! {
#[derive(Debug, Clone, PartialEq)]
pub struct #id #vs { #toks }
}
}
Value::Variant(vcs) => {
let (vs, toks) = gather_needed_vars(s, v, |s| {
let vcs = vcs
.iter()
.map(|vc| {
let orig_name = vc.name.name;
let id = kebab_to_cons(orig_name);
let derives = if s.is_wasmtime_guest {
quote! { #[component(name = #orig_name)] }
} else {
TokenStream::new()
};
match &vc.ty {
Some(ty) => {
let ty = emit_value(s, ty);
quote! { #derives #id(#ty) }
}
None => quote! { #derives #id },
}
})
.collect::<Vec<_>>();
quote! { #(#vcs),* }
});
let vs = emit_type_defn_var_list(s, vs);
let derives = if s.is_wasmtime_guest {
quote! {
#[derive(::wasmtime::component::ComponentType)]
#[derive(::wasmtime::component::Lift)]
#[derive(::wasmtime::component::Lower)]
#[component(variant)]
}
} else {
TokenStream::new()
};
quote! {
#derives
#[derive(Debug)]
pub enum #id #vs { #toks }
}
}
Value::Enum(ns) => {
let (vs, toks) = gather_needed_vars(s, v, |_| {
let ns = ns
.iter()
.map(|n| {
let orig_name = n.name;
let id = kebab_to_cons(orig_name);
let derives = if is_wasmtime_guest {
quote! { #[component(name = #orig_name)] }
} else {
TokenStream::new()
};
quote! { #derives #id }
})
.collect::<Vec<_>>();
quote! { #(#ns),* }
});
let vs = emit_type_defn_var_list(s, vs);
let derives = if s.is_wasmtime_guest {
quote! {
#[derive(::wasmtime::component::ComponentType)]
#[derive(::wasmtime::component::Lift)]
#[derive(::wasmtime::component::Lower)]
#[component(enum)]
#[repr(u8)] }
} else {
TokenStream::new()
};
quote! {
#derives
#[derive(Debug, Copy, Clone, PartialEq)]
pub enum #id #vs { #toks }
}
}
_ => emit_type_alias(s, v, id, |s| emit_value(s, vt)),
}
}
fn emit_defined(s: &mut State, v: Option<u32>, id: Ident, dt: &Defined) -> TokenStream {
match dt {
Defined::Instance(_) | Defined::Component(_) => TokenStream::new(),
Defined::Handleable(Handleable::Resource(_)) => panic!("bare resource in type"),
Defined::Handleable(Handleable::Var(tv)) => {
emit_type_alias(s, v, id, |s| emit_var_ref(s, tv))
}
Defined::Value(vt) => emit_value_toplevel(s, v, id, vt),
Defined::Func(ft) => emit_type_alias(s, v, id, |s| emit_func(s, ft)),
}
}
pub fn emit_func_param(s: &mut State, p: &Param) -> TokenStream {
let name = kebab_to_var(p.name.name);
let ty = emit_value(s, &p.ty);
quote! { #name: #ty }
}
pub fn emit_func_result(s: &mut State, r: &etypes::Result<'_>) -> TokenStream {
match r {
Some(vt) => emit_value(s, vt),
None => quote! { () },
}
}
fn emit_func(s: &mut State, ft: &Func) -> TokenStream {
let params = ft
.params
.iter()
.map(|p| emit_func_param(s, p))
.collect::<Vec<_>>();
let result = emit_func_result(s, &ft.result);
quote! { fn(#(#params),*) -> #result }
}
fn gather_needed_vars<F: Fn(&mut State) -> TokenStream>(
s: &mut State,
v: Option<u32>,
f: F,
) -> (BTreeSet<u32>, TokenStream) {
let mut needs_vars = BTreeSet::new();
let mut sv = s.with_needs_vars(&mut needs_vars);
let toks = f(&mut sv);
if let Some(vn) = v {
sv.record_needs_vars(vn);
}
drop(sv);
(needs_vars, toks)
}
fn emit_type_defn_var_list(s: &mut State, vs: BTreeSet<u32>) -> TokenStream {
if vs.is_empty() {
TokenStream::new()
} else {
let vs = vs
.iter()
.map(|n| {
if s.is_guest {
let t = s.noff_var_id(*n);
quote! { #t: 'static }
} else {
let t = s.noff_var_id(*n);
quote! { #t }
}
})
.collect::<Vec<_>>();
quote! { <#(#vs),*> }
}
}
fn emit_type_alias<F: Fn(&mut State) -> TokenStream>(
s: &mut State,
v: Option<u32>,
id: Ident,
f: F,
) -> TokenStream {
let (vs, toks) = gather_needed_vars(s, v, f);
let vs = emit_type_defn_var_list(s, vs);
quote! { pub type #id #vs = #toks; }
}
fn emit_extern_decl<'a, 'b, 'c>(
origin_was_export: bool,
s: &'c mut State<'a, 'b>,
ed: &'c ExternDecl<'b>,
) -> TokenStream {
tracing::debug!(" emitting decl {:?}", ed.kebab_name);
match &ed.desc {
ExternDesc::CoreModule(_) => panic!("core module (im/ex)ports are not supported"),
ExternDesc::Func(ft) => {
let mut s = s.push_origin(origin_was_export, ed.kebab_name);
match kebab_to_fn(ed.kebab_name) {
FnName::Plain(n) => {
let params = ft
.params
.iter()
.map(|p| emit_func_param(&mut s, p))
.collect::<Vec<_>>();
let result = emit_func_result(&mut s, &ft.result);
quote! {
fn #n(&mut self, #(#params),*) -> #result;
}
}
FnName::Associated(r, n) => {
let mut s = s.helper();
s.cur_trait = Some(r.clone());
let mut needs_vars = BTreeSet::new();
let mut sv = s.with_needs_vars(&mut needs_vars);
let params = ft
.params
.iter()
.map(|p| emit_func_param(&mut sv, p))
.collect::<Vec<_>>();
match n {
ResourceItemName::Constructor => {
sv.cur_trait().items.extend(quote! {
fn new(&mut self, #(#params),*) -> Self::T;
});
}
ResourceItemName::Method(n) => {
let result = emit_func_result(&mut sv, &ft.result);
sv.cur_trait().items.extend(quote! {
fn #n(&mut self, #(#params),*) -> #result;
});
}
ResourceItemName::Static(n) => {
let result = emit_func_result(&mut sv, &ft.result);
sv.cur_trait().items.extend(quote! {
fn #n(&mut self, #(#params),*) -> #result;
});
}
}
for v in needs_vars {
let id = s.noff_var_id(v);
s.cur_trait().tvs.insert(id, (Some(v), TokenStream::new()));
}
quote! {}
}
}
}
ExternDesc::Type(t) => {
fn go_defined<'a, 'b, 'c>(
s: &'c mut State<'a, 'b>,
ed: &'c ExternDecl<'b>,
t: &'c Defined<'b>,
v: Option<u32>,
) -> TokenStream {
let id = kebab_to_type(ed.kebab_name);
let mut s = s.helper();
let t = emit_defined(&mut s, v, id, t);
s.cur_mod().items.extend(t);
TokenStream::new()
}
let edn: &'b str = ed.kebab_name;
let mut s: State<'_, 'b> = s.push_origin(origin_was_export, edn);
if let Some((n, bound)) = s.is_var_defn(t) {
match bound {
TypeBound::Eq(t) => {
let noff = s.var_offset as u32 + n;
s.var_offset += n as usize + 1;
go_defined(&mut s, ed, &t, Some(noff))
}
TypeBound::SubResource => {
let rn = kebab_to_type(ed.kebab_name);
s.add_helper_supertrait(rn.clone());
let mut s = s.helper();
s.cur_trait = Some(rn.clone());
s.cur_trait().items.extend(quote! {
type T: ::core::marker::Send;
});
quote! {}
}
}
} else {
go_defined(&mut s, ed, t, None)
}
}
ExternDesc::Instance(it) => {
let mut s = s.push_origin(origin_was_export, ed.kebab_name);
let wn = split_wit_name(ed.kebab_name);
emit_instance(&mut s, wn.clone(), it);
let nsids = wn.namespace_idents();
let repr = s.r#trait(&nsids, kebab_to_type(wn.name));
let vs = if !repr.tvs.is_empty() {
let vs = repr.tvs.clone();
let tvs = vs
.iter()
.map(|(_, (tv, _))| emit_var_ref(&mut s, &Tyvar::Bound(tv.unwrap())));
quote! { <#(#tvs),*> }
} else {
TokenStream::new()
};
let getter = kebab_to_getter(wn.name);
let rp = s.root_path();
let tns = wn.namespace_path();
let tn = kebab_to_type(wn.name);
quote! {
type #tn: #rp #tns::#tn #vs;
fn #getter(&mut self) -> impl ::core::borrow::BorrowMut<Self::#tn>;
}
}
ExternDesc::Component(_) => {
panic!("nested components not yet supported in rust bindings");
}
}
}
fn emit_instance<'a, 'b, 'c>(s: &'c mut State<'a, 'b>, wn: WitName, it: &'c Instance<'b>) {
tracing::debug!("emitting instance {:?}", wn);
let mut s = s.with_cursor(wn.namespace_idents());
let name = kebab_to_type(wn.name);
s.cur_helper_mod = Some(kebab_to_namespace(wn.name));
s.cur_trait = Some(name.clone());
if !s.cur_trait().items.is_empty() {
return;
}
let mut needs_vars = BTreeSet::new();
let mut sv = s.with_needs_vars(&mut needs_vars);
let exports = it
.exports
.iter()
.map(|ed| emit_extern_decl(true, &mut sv, ed))
.collect::<Vec<_>>();
let mut stvs = BTreeMap::new();
let _ = sv.cur_trait(); let t = sv.cur_trait_immut();
for (ti, _) in t.supertraits.iter() {
let t = sv.resolve_trait_immut(false, ti);
stvs.insert(ti.clone(), t.tv_idxs());
}
sv.origin.push(ImportExport::Export("self"));
let mut stis = BTreeMap::new();
for (id, tvs) in stvs.into_iter() {
stis.insert(id, emit_tvis(&mut sv, tvs));
}
for (id, ts) in stis.into_iter() {
sv.cur_trait().supertraits.get_mut(&id).unwrap().extend(ts);
}
drop(sv);
tracing::debug!("after exports, ncur_needs_vars is {:?}", needs_vars);
for v in needs_vars {
let id = s.noff_var_id(v);
s.cur_trait().tvs.insert(id, (Some(v), TokenStream::new()));
}
s.cur_trait().items.extend(quote! { #(#exports)* });
}
fn emit_component<'a, 'b, 'c>(s: &'c mut State<'a, 'b>, wn: WitName, ct: &'c Component<'b>) {
let mut s = s.with_cursor(wn.namespace_idents());
let base_name = kebab_to_type(wn.name);
s.cur_helper_mod = Some(kebab_to_namespace(wn.name));
let import_name = kebab_to_imports_name(wn.name);
*s.bound_vars = ct
.uvars
.iter()
.rev()
.map(Clone::clone)
.collect::<VecDeque<_>>();
s.cur_trait = Some(import_name.clone());
let imports = ct
.imports
.iter()
.map(|ed| emit_extern_decl(false, &mut s, ed))
.collect::<Vec<TokenStream>>();
s.cur_trait().items.extend(quote! { #(#imports)* });
s.adjust_vars(ct.instance.evars.len() as u32);
s.import_param_var = Some(format_ident!("I"));
s.is_export = true;
let export_name = kebab_to_exports_name(wn.name);
*s.bound_vars = ct
.instance
.evars
.iter()
.rev()
.chain(ct.uvars.iter().rev())
.map(Clone::clone)
.collect::<VecDeque<_>>();
s.cur_trait = Some(export_name.clone());
let exports = ct
.instance
.unqualified
.exports
.iter()
.map(|ed| emit_extern_decl(true, &mut s, ed))
.collect::<Vec<_>>();
s.cur_trait().tvs.insert(
format_ident!("I"),
(None, quote! { #import_name + ::core::marker::Send }),
);
s.cur_trait().items.extend(quote! { #(#exports)* });
s.cur_helper_mod = None;
s.cur_trait = None;
s.cur_mod().items.extend(quote! {
pub trait #base_name {
type Exports<I: #import_name + ::core::marker::Send>: #export_name<I>;
fn instantiate<I: #import_name + ::core::marker::Send + 'static>(self, imports: I) -> Self::Exports<I>;
}
});
}
pub fn emit_toplevel<'a, 'b, 'c>(s: &'c mut State<'a, 'b>, n: &str, ct: &'c Component<'b>) {
let wn = split_wit_name(n);
emit_component(s, wn, ct);
}