use crate::javascript::escape_js_ident;
use crate::rust_bindgen::RustWitFunction;
use crate::types::{
ProcessedParameter, ReturnTypeInformation, WrappedType, get_function_name, get_return_type,
ident_in_exported_interface, ident_in_exported_interface_or_global, param_refs_as_tuple,
process_parameter, to_original_func_arg_list, to_wrapped_param_refs, type_borrows_resource,
};
use crate::{EmbeddingMode, GeneratorContext, JsModuleSpec};
use anyhow::{Context, anyhow};
use heck::{ToLowerCamelCase, ToUpperCamelCase};
use proc_macro2::{Ident, Span, TokenStream};
use quote::quote;
use std::collections::BTreeMap;
use syn::{Lit, LitStr};
use wit_parser::{Function, FunctionKind, Interface, TypeId, WorldItem, WorldKey};
pub fn generate_export_impls(
context: &GeneratorContext<'_>,
js_modules: &[JsModuleSpec],
) -> anyhow::Result<()> {
let guest_impls = generate_guest_impls(context)?;
let module_defs = generate_module_defs(js_modules)?;
let world_name_lit = LitStr::new(&context.world_name, Span::call_site());
let with_block = generate_wasi_remaps(context);
let lib_tokens = quote! {
#[allow(unsafe_op_in_unsafe_fn)]
pub(crate) mod bindings {
wit_bindgen::generate!({
path: "wit",
world: #world_name_lit,
ownership: Owning,
generate_all,
#with_block
});
}
mod builtin;
mod conversions;
#[allow(unused)]
mod internal;
#[allow(unused)]
mod modules;
mod wrappers;
#module_defs
struct Component;
#(#guest_impls)*
bindings::export!(Component with_types_in bindings);
};
let lib_ast: syn::File =
syn::parse2(lib_tokens).context("failed to parse generated lib.rs tokens")?;
let lib_path = context.output.join("src").join("lib.rs");
let lib_src = prettier_please::unparse(&lib_ast);
crate::write_if_changed(&lib_path, lib_src)?;
Ok(())
}
fn generate_guest_impls(context: &GeneratorContext<'_>) -> anyhow::Result<Vec<TokenStream>> {
let mut result = Vec::new();
let world = &context.resolve.worlds[context.world];
let mut global_exports = Vec::new();
let mut interface_exports = Vec::new();
for (name, export) in &world.exports {
let name = match name {
WorldKey::Name(name) => name.clone(),
WorldKey::Interface(id) => {
let interface = &context.resolve.interfaces[*id];
interface
.name
.clone()
.ok_or_else(|| anyhow!("Interface export does not have a name"))?
}
};
match export {
WorldItem::Interface { id, .. } => {
let interface = &context.resolve.interfaces[*id];
interface_exports.push((name, interface));
}
WorldItem::Function(function) => {
global_exports.push((name, function));
}
WorldItem::Type { .. } => {}
}
}
if !global_exports.is_empty() {
result.extend(generate_guest_impl(
context,
quote! { crate::bindings::Guest },
None,
&global_exports,
)?);
}
for (name, interface) in interface_exports {
let interface_exports: Vec<_> = interface
.functions
.iter()
.map(|(name, function)| (name.clone(), function))
.collect();
result.extend(generate_guest_impl(
context,
ident_in_exported_interface(
context,
Ident::new("Guest", Span::call_site()),
&name,
interface,
),
Some((&name, interface)),
&interface_exports,
)?);
}
Ok(result)
}
fn generate_guest_impl(
context: &GeneratorContext<'_>,
guest_trait: TokenStream,
interface: Option<(&str, &Interface)>,
exports: &[(String, &Function)],
) -> anyhow::Result<Vec<TokenStream>> {
let mut func_impls = Vec::new();
let mut resource_impls = Vec::new();
let mut resource_functions = BTreeMap::new();
for (name, function) in exports {
match &function.kind {
FunctionKind::Freestanding => {
if name == "wizer-initialize" {
func_impls.push(quote! {
fn wizer_initialize() {
crate::internal::wizer_initialize();
}
});
} else {
let func_impl =
generate_exported_function_impl(context, interface, name, function)?;
func_impls.push(func_impl);
}
}
FunctionKind::AsyncFreestanding
| FunctionKind::AsyncMethod(_)
| FunctionKind::AsyncStatic(_) => {
Err(anyhow!("Async exported functions are not supported yet"))?
}
FunctionKind::Method(type_id)
| FunctionKind::Static(type_id)
| FunctionKind::Constructor(type_id) => {
resource_functions
.entry(type_id)
.or_insert_with(Vec::new)
.push((name, function));
}
}
}
let mut resource_types = Vec::new();
for (resource_type_id, resource_funcs) in resource_functions {
let typ = context
.resolve
.types
.get(*resource_type_id)
.ok_or_else(|| anyhow!("Unknown resource type id"))?;
let resource_name = typ
.name
.as_ref()
.ok_or_else(|| anyhow!("Resource type has no name"))?;
let resource_name_ident =
Ident::new(&resource_name.to_upper_camel_case(), Span::call_site());
let resource_name_borrow_ident = Ident::new(
&format!("{}Borrow", resource_name.to_upper_camel_case()),
Span::call_site(),
);
let guest_name_ident = Ident::new(
&format!("Guest{}", resource_name.to_upper_camel_case()),
Span::call_site(),
);
let guest_trait =
ident_in_exported_interface_or_global(context, guest_name_ident, interface);
let borrow_wrapper =
ident_in_exported_interface_or_global(context, resource_name_borrow_ident, interface);
let owned_wrapper =
ident_in_exported_interface_or_global(context, resource_name_ident.clone(), interface);
let mut resource_func_impls = Vec::new();
for (name, resource_function) in resource_funcs {
let func_impl = generate_exported_resource_function_impl(
context,
interface,
resource_type_id,
name,
resource_function,
)?;
resource_func_impls.push(func_impl);
}
resource_impls.push(quote! {
struct #resource_name_ident {
resource_id: usize
}
impl #guest_trait for #resource_name_ident {
#(#resource_func_impls)*
}
impl Drop for #resource_name_ident {
fn drop(&mut self) {
crate::internal::enqueue_drop_js_resource(self.resource_id);
}
}
impl<'js> rquickjs::IntoJs<'js> for #borrow_wrapper<'_> {
fn into_js(self, ctx: &rquickjs::Ctx<'js>) -> rquickjs::Result<rquickjs::Value<'js>> {
let inner: &#resource_name_ident = self.get();
let resource_table: rquickjs::Object = ctx.globals().get(crate::internal::RESOURCE_TABLE_NAME)
.expect("Failed to get the resource table");
let resource_instance: rquickjs::Object = resource_table.get(inner.resource_id.to_string())
.expect(&format!("Failed to get resource instance with id {}", inner.resource_id));
Ok(resource_instance.into_value())
}
}
impl<'js> rquickjs::IntoJs<'js> for #owned_wrapper {
fn into_js(self, ctx: &rquickjs::Ctx<'js>) -> rquickjs::Result<rquickjs::Value<'js>> {
let inner: &#resource_name_ident = self.get();
let resource_table: rquickjs::Object = ctx.globals().get(crate::internal::RESOURCE_TABLE_NAME)
.expect("Failed to get the resource table");
let resource_instance: rquickjs::Object = resource_table.get(inner.resource_id.to_string())
.expect(&format!("Failed to get resource instance with id {}", inner.resource_id));
Ok(resource_instance.into_value())
}
}
impl<'js> rquickjs::FromJs<'js> for #owned_wrapper {
fn from_js(ctx: &rquickjs::Ctx<'js>, value: rquickjs::Value<'js>) -> rquickjs::Result<Self> {
let resource = value.into_object().ok_or_else(|| {
rquickjs::Error::new_from_js_message(
"JS Resource instance",
"WASM resource instance",
"The value is not an object",
)
})?;
let already_registered = resource.contains_key(crate::internal::RESOURCE_ID_KEY)?;
let resource_id: usize = if already_registered {
resource.get(crate::internal::RESOURCE_ID_KEY)?
} else {
let resource_table: rquickjs::Object = ctx.globals().get(crate::internal::RESOURCE_TABLE_NAME)?;
let resource_id = crate::internal::get_free_resource_id();
resource_table.set(resource_id.to_string(), resource)?;
resource_id
};
Ok(#owned_wrapper::new(#resource_name_ident { resource_id }))
}
}
});
resource_types.push(quote! {
type #resource_name_ident = #resource_name_ident;
})
}
let mut guest_impls = Vec::new();
guest_impls.extend(resource_impls);
guest_impls.push(quote! {
impl #guest_trait for Component {
#(#resource_types)*
#(#func_impls)*
}
});
Ok(guest_impls)
}
fn generate_exported_function_impl(
context: &GeneratorContext<'_>,
interface: Option<(&str, &Interface)>,
name: &str,
function: &Function,
) -> anyhow::Result<TokenStream> {
let rust_fn = RustWitFunction::new(context, name, function);
let func_name = rust_fn.function_name_ident();
let param_ident_type: Vec<_> = function
.params
.iter()
.zip(rust_fn.export_parameters.clone())
.zip(rust_fn.import_parameters.clone())
.map(|((param, export_parameter), import_parameter)| {
process_parameter(
context,
¶m.name,
¶m.ty,
&export_parameter,
&import_parameter,
)
})
.collect::<anyhow::Result<Vec<_>>>()?;
let func_arg_list = to_original_func_arg_list(¶m_ident_type);
let return_types = get_return_type(context, function, name, &rust_fn)?;
let param_refs = to_wrapped_param_refs(¶m_ident_type);
let param_refs_tuple = param_refs_as_tuple(¶m_refs);
let js_func_name_str = Lit::Str(LitStr::new(
&escape_js_ident(name.to_lower_camel_case()),
func_name.span(),
));
let (js_func_path, wit_package_lit) = match interface {
Some((iface_name, iface)) => {
let if_name_str = LitStr::new(
&escape_js_ident(iface_name.to_lower_camel_case()),
func_name.span(),
);
let owner_package_name = match iface.package {
Some(package_id) => {
let package = context.resolve.packages.get(package_id).ok_or_else(|| {
anyhow!("Unknown owner package of interface: {iface_name}")
})?;
package.name.to_string()
}
None => context.root_package_name().to_string(),
};
(
quote! { &[#if_name_str, #js_func_name_str] },
Lit::Str(LitStr::new(&owner_package_name, Span::call_site())),
)
}
None => (
quote! { &[#js_func_name_str] },
Lit::Str(LitStr::new(&context.root_package_name(), Span::call_site())),
),
};
let original_result = &return_types.wit_level_ret.original_type_ref;
let wrapped_result = &return_types.wit_level_ret.wrapped_type_ref;
let unwrap = &return_types.wit_level_ret.unwrap;
let unwrap_result = unwrap.run(quote! { result });
let call = if return_types.expected_exception.is_some() {
quote! { call_js_export_returning_result }
} else {
quote! { call_js_export }
};
let func_impl = quote! {
fn #func_name(#(#func_arg_list),*) -> #original_result {
crate::internal::async_exported_function(async move {
let result: #wrapped_result = crate::internal::#call(
#wit_package_lit,
#js_func_path,
#param_refs_tuple
).await;
#unwrap_result
})
}
};
Ok(func_impl)
}
fn generate_exported_resource_function_impl(
context: &GeneratorContext<'_>,
interface: Option<(&str, &Interface)>,
resource_type_id: &TypeId,
name: &str,
function: &Function,
) -> anyhow::Result<TokenStream> {
let func_name = get_function_name(name, function)?;
let rust_fn = RustWitFunction::new(context, &func_name, function);
let func_name_ident = rust_fn.function_name_ident();
let param_ident_type: Vec<_> = function
.params
.iter()
.zip(rust_fn.export_parameters.clone())
.zip(rust_fn.import_parameters.clone())
.map(|((param, export_param), import_param)| {
let param_name = ¶m.name;
let param_type = ¶m.ty;
if matches!(
function.kind,
FunctionKind::Method(_) | FunctionKind::AsyncMethod(_)
) && type_borrows_resource(context, param_type, resource_type_id)?
{
Ok(ProcessedParameter {
ident: Ident::new(param_name, Span::call_site()),
wrapped_type: None,
export_parameter: export_param,
import_parameter: import_param,
})
} else {
process_parameter(
context,
param_name,
param_type,
&export_param,
&import_param,
)
}
})
.collect::<anyhow::Result<Vec<_>>>()?;
let func_arg_list = to_original_func_arg_list(¶m_ident_type);
let return_types = if matches!(function.kind, FunctionKind::Constructor(_)) {
ReturnTypeInformation {
wit_level_ret: WrappedType::no_wrapping(quote! { Self }),
func_ret: WrappedType::no_wrapping(quote! { Self }),
expected_exception: None,
}
} else {
get_return_type(context, function, name, &rust_fn)?
};
let param_refs = to_wrapped_param_refs(¶m_ident_type);
let resource_name = context
.resolve
.types
.get(*resource_type_id)
.ok_or_else(|| anyhow::anyhow!("Unknown resource type id"))?
.name
.as_ref()
.ok_or_else(|| anyhow::anyhow!("Resource type has no name"))?;
let js_resource_name_str = Lit::Str(LitStr::new(
&resource_name.to_upper_camel_case(),
Span::call_site(),
));
let (js_resource_path, wit_package_lit) = match interface {
Some((iface_name, iface)) => {
let if_name_str = LitStr::new(
&escape_js_ident(iface_name.to_lower_camel_case()),
Span::call_site(),
);
let owner_package_name = match iface.package {
Some(package_id) => {
let package = context.resolve.packages.get(package_id).ok_or_else(|| {
anyhow!("Unknown owner package of interface: {iface_name}")
})?;
package.name.to_string()
}
None => context.root_package_name().to_string(),
};
(
quote! { &[#if_name_str, #js_resource_name_str] },
Lit::Str(LitStr::new(&owner_package_name, Span::call_site())),
)
}
None => (
quote! { &[#js_resource_name_str] },
Lit::Str(LitStr::new(&context.root_package_name(), Span::call_site())),
),
};
let js_func_name_str = Lit::Str(LitStr::new(
&escape_js_ident(func_name.to_lower_camel_case()),
Span::call_site(),
));
let js_static_func_path = match interface {
Some((iface_name, _)) => {
let if_name_str = LitStr::new(
&escape_js_ident(iface_name.to_lower_camel_case()),
Span::call_site(),
);
quote! { &[#if_name_str, #js_resource_name_str, #js_func_name_str] }
}
None => quote! { &[#js_func_name_str] },
};
let func_impl = match &function.kind {
FunctionKind::Constructor(_) => {
let param_refs_tuple = param_refs_as_tuple(¶m_refs);
quote! {
fn #func_name_ident(#(#func_arg_list),*) -> Self {
crate::internal::async_exported_function(async move {
let resource_id = crate::internal::call_js_resource_constructor(
#wit_package_lit,
#js_resource_path,
#param_refs_tuple,
).await;
Self {
resource_id
}
})
}
}
}
FunctionKind::Method(_) => {
let param_refs = param_refs[1..].to_vec();
let param_refs_tuple = param_refs_as_tuple(¶m_refs);
let original_result = &return_types.func_ret.original_type_ref;
let wrapped_result = &return_types.func_ret.wrapped_type_ref;
let unwrap = &return_types.func_ret.unwrap;
let unwrap_result = unwrap.run(quote! { result });
let call = if return_types.expected_exception.is_some() {
quote! { call_js_resource_method_returning_result }
} else {
quote! { call_js_resource_method }
};
quote! {
fn #func_name_ident(#(#func_arg_list),*) -> #original_result {
crate::internal::async_exported_function(async move {
let result: #wrapped_result = crate::internal::#call(
#wit_package_lit,
#js_resource_path,
self.resource_id,
#js_func_name_str,
#param_refs_tuple,
).await;
#unwrap_result
})
}
}
}
FunctionKind::Static(_) => {
let param_refs_tuple = param_refs_as_tuple(¶m_refs);
let original_result = &return_types.wit_level_ret.original_type_ref;
let wrapped_result = &return_types.wit_level_ret.wrapped_type_ref;
let unwrap = &return_types.wit_level_ret.unwrap;
let unwrap_result = unwrap.run(quote! { result });
let call = if return_types.expected_exception.is_some() {
quote! { call_js_export_returning_result }
} else {
quote! { call_js_export }
};
quote! {
fn #func_name_ident(#(#func_arg_list),*) -> #original_result {
crate::internal::async_exported_function(async move {
let result: #wrapped_result = crate::internal::#call(
#wit_package_lit,
#js_static_func_path,
#param_refs_tuple,
).await;
#unwrap_result
})
}
}
}
FunctionKind::AsyncMethod(_) | FunctionKind::AsyncStatic(_) => Err(anyhow::anyhow!(
"Async exported functions are not supported yet",
))?,
FunctionKind::Freestanding | FunctionKind::AsyncFreestanding => Err(anyhow::anyhow!(
"Freestanding functions are not expected in resource methods",
))?,
};
Ok(func_impl)
}
fn generate_module_defs(js_modules: &[JsModuleSpec]) -> anyhow::Result<TokenStream> {
if let Some((export_module, additional_modules)) = js_modules.split_first() {
let export_module_name = LitStr::new(&export_module.name, Span::call_site());
let any_binary_slot = export_module.mode.is_binary_slot()
|| additional_modules.iter().any(|m| m.mode.is_binary_slot());
let slot_helper = if any_binary_slot {
quote! {
fn read_js_from_slot_bytes(slot: &[u8]) -> String {
const MAGIC: &[u8; 16] = b"WASM_RQJS_SLOT\x01\x00";
const END_MAGIC: &[u8; 16] = b"WASM_RQJS_SLTND\x00";
assert!(slot.len() >= 40, "JS injection marker is too small");
let slot_ptr = slot.as_ptr();
unsafe {
let mut magic_buf = [0u8; 16];
for i in 0..16 {
magic_buf[i] = core::ptr::read_volatile(slot_ptr.add(i));
}
assert_eq!(&magic_buf, MAGIC, "invalid JS injection marker header");
let mut offset_bytes = [0u8; 4];
for i in 0..4 {
offset_bytes[i] = core::ptr::read_volatile(slot_ptr.add(20 + i));
}
let js_offset = u32::from_le_bytes(offset_bytes) as usize;
assert!(js_offset > 0, "JS injection slot is empty — no JS has been injected. \
Use wasm-rquickjs inject-js to inject JavaScript source into the template.");
let mut end_magic_buf = [0u8; 16];
for i in 0..16 {
end_magic_buf[i] = core::ptr::read_volatile(slot_ptr.add(24 + i));
}
assert_eq!(&end_magic_buf, END_MAGIC, "JS injection marker footer is corrupted");
let mem_ptr = js_offset as *const u8;
let mut len_bytes = [0u8; 4];
for i in 0..4 {
len_bytes[i] = core::ptr::read_volatile(mem_ptr.add(i));
}
let len = u32::from_le_bytes(len_bytes) as usize;
let js_ptr = mem_ptr.add(4);
let mut payload = Vec::with_capacity(len);
for i in 0..len {
payload.push(core::ptr::read_volatile(js_ptr.add(i)));
}
String::from_utf8(payload)
.expect("injected JS source is not valid UTF-8")
}
}
}
} else {
quote! {}
};
let export_module_def = match &export_module.mode {
EmbeddingMode::BinarySlot => {
let slot_file_name = LitStr::new(
&(export_module.name.replace('/', "_") + ".slot"),
Span::call_site(),
);
quote! {
static JS_EXPORT_MODULE_NAME: &str = #export_module_name;
static JS_EXPORT_MODULE_SLOT: &[u8] = include_bytes!(#slot_file_name);
fn js_export_module() -> &'static str {
static SOURCE: std::sync::LazyLock<String> =
std::sync::LazyLock::new(|| read_js_from_slot_bytes(JS_EXPORT_MODULE_SLOT));
SOURCE.as_str()
}
}
}
_ => {
let export_module_file_name =
LitStr::new(&export_module.file_name(), Span::call_site());
quote! {
static JS_EXPORT_MODULE_NAME: &str = #export_module_name;
static JS_EXPORT_MODULE_SOURCE: &str = include_str!(#export_module_file_name);
fn js_export_module() -> &'static str {
JS_EXPORT_MODULE_SOURCE
}
}
}
};
let mut additional_module_pairs = Vec::new();
let mut additional_slot_defs = Vec::new();
for module in additional_modules {
match &module.mode {
EmbeddingMode::EmbedFile(_) => {
let name = LitStr::new(&module.name, Span::call_site());
let file_name = LitStr::new(&module.file_name(), Span::call_site());
additional_module_pairs.push(
quote! { (#name, Box::new(|| { include_str!(#file_name).to_string() })) },
);
}
EmbeddingMode::Composition => {
let name = LitStr::new(&module.name, Span::call_site());
additional_module_pairs.push(
quote! { (#name, Box::new(|| { crate::bindings::get_script().to_string() })) },
);
}
EmbeddingMode::BinarySlot => {
let name = LitStr::new(&module.name, Span::call_site());
let slot_file_name = LitStr::new(
&(module.name.replace('/', "_") + ".slot"),
Span::call_site(),
);
let sanitized = module.name.replace(['/', '-'], "_");
let static_name = Ident::new(
&format!("JS_SLOT_{}", sanitized.to_uppercase()),
Span::call_site(),
);
let fn_name =
Ident::new(&format!("read_js_from_slot_{sanitized}"), Span::call_site());
let source_name = Ident::new(
&format!("JS_SLOT_SOURCE_{}", sanitized.to_uppercase()),
Span::call_site(),
);
additional_slot_defs.push(quote! {
static #static_name: &[u8] = include_bytes!(#slot_file_name);
fn #fn_name() -> String {
read_js_from_slot_bytes(#static_name)
}
});
additional_module_pairs.push(quote! {
(#name, Box::new(|| {
static #source_name: std::sync::LazyLock<String> =
std::sync::LazyLock::new(#fn_name);
#source_name.clone()
}))
});
}
}
}
Ok(quote! {
#slot_helper
#export_module_def
#(#additional_slot_defs)*
static JS_ADDITIONAL_MODULES: std::sync::LazyLock<Vec<(&str, Box<dyn (Fn() -> String) + Send + Sync>)>> =
std::sync::LazyLock::new(|| { vec![
#(#additional_module_pairs),*
]});
})
} else {
Err(anyhow!("No JS modules provided."))?
}
}
fn generate_wasi_remaps(context: &GeneratorContext<'_>) -> TokenStream {
static WASI_REMAPS: &[(&str, &str)] = &[
("wasi:cli/environment", "wasip2::cli::environment"),
("wasi:cli/exit", "wasip2::cli::exit"),
("wasi:cli/stderr", "wasip2::cli::stderr"),
("wasi:cli/stdin", "wasip2::cli::stdin"),
("wasi:cli/stdout", "wasip2::cli::stdout"),
("wasi:cli/terminal-input", "wasip2::cli::terminal_input"),
("wasi:cli/terminal-output", "wasip2::cli::terminal_output"),
("wasi:cli/terminal-stderr", "wasip2::cli::terminal_stderr"),
("wasi:cli/terminal-stdin", "wasip2::cli::terminal_stdin"),
("wasi:cli/terminal-stdout", "wasip2::cli::terminal_stdout"),
(
"wasi:clocks/monotonic-clock",
"wasip2::clocks::monotonic_clock",
),
("wasi:clocks/wall-clock", "wasip2::clocks::wall_clock"),
("wasi:filesystem/preopens", "wasip2::filesystem::preopens"),
("wasi:filesystem/types", "wasip2::filesystem::types"),
(
"wasi:http/outgoing-handler",
"wasip2::http::outgoing_handler",
),
("wasi:http/types", "wasip2::http::types"),
("wasi:io/error", "wasip2::io::error"),
("wasi:io/poll", "wasip2::io::poll"),
("wasi:io/streams", "wasip2::io::streams"),
("wasi:random/insecure", "wasip2::random::insecure"),
("wasi:random/insecure-seed", "wasip2::random::insecure_seed"),
("wasi:random/random", "wasip2::random::random"),
(
"wasi:sockets/instance-network",
"wasip2::sockets::instance_network",
),
(
"wasi:sockets/ip-name-lookup",
"wasip2::sockets::ip_name_lookup",
),
("wasi:sockets/network", "wasip2::sockets::network"),
("wasi:sockets/tcp", "wasip2::sockets::tcp"),
(
"wasi:sockets/tcp-create-socket",
"wasip2::sockets::tcp_create_socket",
),
("wasi:sockets/udp", "wasip2::sockets::udp"),
(
"wasi:sockets/udp-create-socket",
"wasip2::sockets::udp_create_socket",
),
];
let world = &context.resolve.worlds[context.world];
let mut used_interfaces = std::collections::BTreeMap::<String, String>::new();
for (key, _) in world.imports.iter().chain(world.exports.iter()) {
if let WorldKey::Interface(id) = key {
let interface = &context.resolve.interfaces[*id];
if let Some(ref name) = interface.name
&& let Some(package_id) = interface.package
{
let package = &context.resolve.packages[package_id];
let unversioned =
format!("{}:{}/{}", package.name.namespace, package.name.name, name);
let versioned = package.name.interface_id(name);
used_interfaces.insert(unversioned, versioned);
}
}
}
let mut entries = Vec::new();
for (wit_name, rust_path) in WASI_REMAPS {
if let Some(versioned_name) = used_interfaces.get(*wit_name) {
let wit_lit = LitStr::new(versioned_name, Span::call_site());
let rust_path: syn::Path = syn::parse_str(rust_path)
.unwrap_or_else(|_| panic!("Invalid Rust path: {rust_path}"));
entries.push(quote! { #wit_lit: #rust_path });
}
}
if entries.is_empty() {
quote! {}
} else {
quote! {
with: {
#(#entries),*
},
}
}
}