use crate::GeneratorContext;
use crate::rust_bindgen::{RustFunctionParameter, RustType, RustWitFunction, escape_rust_ident};
use anyhow::{Context, anyhow};
use heck::{ToSnakeCase, ToUpperCamelCase};
use proc_macro2::{Ident, Span, TokenStream};
use quote::quote;
use syn::{Lit, LitInt};
use wit_parser::{
Function, FunctionKind, Handle, Interface, PackageId, Type, TypeDef, TypeDefKind, TypeId,
TypeOwner,
};
pub fn to_type_ref(context: &GeneratorContext<'_>, typ: &Type) -> anyhow::Result<TokenStream> {
match typ {
Type::Bool => Ok(quote! { bool }),
Type::U8 => Ok(quote! { u8 }),
Type::U16 => Ok(quote! { u16 }),
Type::U32 => Ok(quote! { u32 }),
Type::U64 => Ok(quote! { u64 }),
Type::S8 => Ok(quote! { i8 }),
Type::S16 => Ok(quote! { i16 }),
Type::S32 => Ok(quote! { i32 }),
Type::S64 => Ok(quote! { i64 }),
Type::F32 => Ok(quote! { f32 }),
Type::F64 => Ok(quote! { f64 }),
Type::Char => Ok(quote! { char }),
Type::String => Ok(quote! { String }),
Type::ErrorContext => Ok(quote! { wit_bindgen_rt::async_support::ErrorContext }),
Type::Id(type_id) => {
context.record_visited_type(*type_id);
type_id_to_type_ref(context, *type_id)
}
}
}
pub fn type_id_to_type_ref(
context: &GeneratorContext,
type_id: TypeId,
) -> anyhow::Result<TokenStream> {
let typ = context.typ(type_id)?;
match &typ.kind {
TypeDefKind::Option(inner) => {
let inner_ref = to_type_ref(context, inner)?;
Ok(quote! { Option<#inner_ref> })
}
TypeDefKind::Result(inner) => {
let ok = inner
.ok
.as_ref()
.map(|ok| to_type_ref(context, ok))
.transpose()?
.unwrap_or(quote! { () });
let err = inner
.err
.as_ref()
.map(|err| to_type_ref(context, err))
.transpose()?
.unwrap_or(quote! { () });
Ok(quote! { Result<#ok, #err> })
}
TypeDefKind::List(inner) => {
let inner_ref = to_type_ref(context, inner)?;
Ok(quote! { Vec<#inner_ref> })
}
TypeDefKind::FixedSizeList(inner, n) => {
let inner_ref = to_type_ref(context, inner)?;
Ok(quote! { &[#inner_ref; #n] })
}
TypeDefKind::Tuple(tuple) => {
let item_refs: Vec<_> = tuple
.types
.iter()
.map(|item| to_type_ref(context, item))
.collect::<anyhow::Result<Vec<_>>>()?;
Ok(quote! { (#(#item_refs),*) })
}
TypeDefKind::Future(_) => Err(anyhow!("Future types are not supported yet"))?,
TypeDefKind::Stream(_) => Err(anyhow!("Stream types are not supported yet"))?,
TypeDefKind::Handle(handle) => match handle {
Handle::Own(resource_type_id) => owned_resource_ref(context, resource_type_id),
Handle::Borrow(resource_type_id) => borrowed_resource_ref(context, resource_type_id),
},
_ => {
record_visited_inner_types(context, typ)?;
let name = typ
.name
.as_ref()
.ok_or_else(|| anyhow!("Type {typ:?} has no name"))?;
let name_ident = Ident::new(&name.to_upper_camel_case(), Span::call_site());
match typ.owner {
TypeOwner::World(world_id) => {
if world_id == context.world {
Ok(quote! { crate::bindings::#name_ident })
} else {
Err(anyhow!("Type {name} is owned by a different world"))?
}
}
TypeOwner::Interface(interface_id) => {
let interface = context
.resolve
.interfaces
.get(interface_id)
.ok_or_else(|| anyhow!("Unknown interface id: {interface_id:?}"))?;
if context.is_exported_interface(interface_id) {
Ok(ident_in_exported_interface(
context,
name_ident,
interface
.name
.as_ref()
.ok_or_else(|| anyhow!("Interface export does not have a name"))?,
interface,
))
} else {
Ok(ident_in_imported_interface(
context,
name_ident,
interface
.name
.as_ref()
.ok_or_else(|| anyhow!("Interface does not have a name"))?,
interface,
))
}
}
TypeOwner::None => Err(anyhow!("Type {name} has no owner"))?,
}
}
}
}
pub fn ident_in_exported_interface_or_global(
context: &GeneratorContext<'_>,
ident: Ident,
interface: Option<(&str, &Interface)>,
) -> TokenStream {
if let Some((interface_name, interface)) = interface {
ident_in_exported_interface(context, ident, interface_name, interface)
} else {
quote! { crate::bindings::#ident }
}
}
fn add_package_to_path(
context: &GeneratorContext<'_>,
path: &mut Vec<TokenStream>,
package_id: &Option<PackageId>,
) {
if let Some(package_id) = package_id {
let package = &context.resolve.packages[*package_id];
let ns_ident = Ident::new(
&escape_rust_ident(&package.name.namespace.to_snake_case()),
Span::call_site(),
);
let name_ident = Ident::new(
&escape_rust_ident(&package.name.name.to_snake_case()),
Span::call_site(),
);
path.push(quote! { #ns_ident });
path.push(quote! { #name_ident });
}
}
pub fn ident_in_exported_interface(
context: &GeneratorContext<'_>,
ident: Ident,
interface_name: &str,
interface: &Interface,
) -> TokenStream {
let name_ident = Ident::new(
&escape_rust_ident(&interface_name.to_snake_case()),
Span::call_site(),
);
let mut path = Vec::new();
path.push(quote! { crate });
path.push(quote! { bindings });
path.push(quote! { exports });
add_package_to_path(context, &mut path, &interface.package);
path.push(quote! { #name_ident });
path.push(quote! { #ident });
quote! { #(#path)::* }
}
pub fn ident_in_imported_interface_or_global(
context: &GeneratorContext<'_>,
ident: Ident,
interface: Option<(&str, &Interface)>,
) -> TokenStream {
if let Some((interface_name, interface)) = interface {
ident_in_imported_interface(context, ident, interface_name, interface)
} else {
quote! { crate::bindings::#ident }
}
}
pub fn ident_in_imported_interface(
context: &GeneratorContext<'_>,
ident: Ident,
interface_name: &str,
interface: &Interface,
) -> TokenStream {
let name_ident = Ident::new(
&escape_rust_ident(&interface_name.to_snake_case()),
Span::call_site(),
);
let mut path = Vec::new();
path.push(quote! { crate });
path.push(quote! { bindings });
add_package_to_path(context, &mut path, &interface.package);
path.push(quote! { #name_ident });
path.push(quote! { #ident });
quote! { #(#path)::* }
}
pub fn type_borrows_resource(
context: &GeneratorContext<'_>,
typ: &Type,
resource_type_id: &TypeId,
) -> anyhow::Result<bool> {
match typ {
Type::Id(type_id) => {
let typ = context.typ(*type_id)?;
match &typ.kind {
TypeDefKind::Handle(Handle::Borrow(id)) if id == resource_type_id => Ok(true),
_ => Ok(false),
}
}
_ => Ok(false),
}
}
pub enum TokenStreamWrapper {
F(Box<dyn Fn(TokenStream) -> TokenStream>),
Ref,
Identity,
AsStr,
AsSlice,
}
impl TokenStreamWrapper {
pub fn run(&self, t: TokenStream) -> TokenStream {
match self {
TokenStreamWrapper::F(run) => run(t),
TokenStreamWrapper::Ref => quote! { &#t },
TokenStreamWrapper::Identity => t,
TokenStreamWrapper::AsStr => quote! { #t.as_str() },
TokenStreamWrapper::AsSlice => quote! { #t.as_slice() },
}
}
pub fn new(f: impl Fn(TokenStream) -> TokenStream + 'static) -> Self {
TokenStreamWrapper::F(Box::new(f))
}
pub fn identity() -> Self {
TokenStreamWrapper::Identity
}
pub fn reference() -> Self {
TokenStreamWrapper::Ref
}
pub fn as_str() -> Self {
TokenStreamWrapper::AsStr
}
pub fn as_slice() -> Self {
TokenStreamWrapper::AsSlice
}
}
impl From<Box<dyn Fn(TokenStream) -> TokenStream>> for TokenStreamWrapper {
fn from(f: Box<dyn Fn(TokenStream) -> TokenStream>) -> Self {
TokenStreamWrapper::F(f)
}
}
pub struct WrappedType {
pub wrap: TokenStreamWrapper,
pub unwrap: TokenStreamWrapper,
pub original_type_ref: TokenStream,
pub wrapped_type_ref: TokenStream,
}
impl WrappedType {
pub fn no_wrapping(original_type_ref: TokenStream) -> Self {
WrappedType {
wrap: TokenStreamWrapper::identity(),
unwrap: TokenStreamWrapper::identity(),
original_type_ref: original_type_ref.clone(),
wrapped_type_ref: original_type_ref,
}
}
pub fn unit() -> Self {
Self::no_wrapping(quote! { () })
}
}
pub fn get_wrapped_type(
context: &GeneratorContext<'_>,
import_rust_type: &RustType,
export_rust_type: &RustType,
typ: &Type,
) -> anyhow::Result<WrappedType> {
get_wrapped_type_internal(
context,
import_rust_type,
export_rust_type,
typ,
false,
false,
)
}
pub fn get_wrapped_type_internal(
context: &GeneratorContext<'_>,
import_rust_type: &RustType,
export_rust_type: &RustType,
typ: &Type,
in_tuple: bool,
in_iter: bool,
) -> anyhow::Result<WrappedType> {
let original_type_ref = to_type_ref(context, typ)?;
let ctx = GetWrappedTypeContext {
context,
original_type_ref,
in_tuple,
forced_ref: in_iter,
};
match typ {
Type::Id(type_id) => {
let typ = context.typ(*type_id)?;
match &typ.kind {
TypeDefKind::Tuple(tuple) => {
get_wrapped_type_tuple(ctx, import_rust_type, export_rust_type, tuple)
}
TypeDefKind::List(Type::U8) => {
Ok(WrappedType {
wrap: TokenStreamWrapper::new(
move |ts| quote! { crate::wrappers::UInt8Array(#ts) },
),
unwrap: TokenStreamWrapper::new(move |ts| quote! { #ts.0 }),
wrapped_type_ref: quote! { crate::wrappers::UInt8Array },
original_type_ref: quote! { Vec<u8> },
})
}
TypeDefKind::List(elem_type) => {
get_wrapped_type_list(ctx, import_rust_type, export_rust_type, elem_type)
}
TypeDefKind::Option(elem_type) => {
get_wrapped_type_option(ctx, import_rust_type, export_rust_type, elem_type)
}
TypeDefKind::Result(result) => {
get_wrapped_type_result(ctx, import_rust_type, export_rust_type, result)
}
TypeDefKind::Record(_) | TypeDefKind::Variant(_) => get_wrapped_type_adt(ctx),
TypeDefKind::Handle(Handle::Borrow(resource_type_id)) => {
get_wrapped_type_borrow_handle(ctx, resource_type_id)
}
TypeDefKind::Type(inner) => {
let inner = get_wrapped_type_internal(
context,
import_rust_type,
export_rust_type,
inner,
in_tuple,
in_iter,
)?;
Ok(WrappedType {
wrap: inner.wrap,
unwrap: inner.unwrap,
original_type_ref: ctx.original_type_ref,
wrapped_type_ref: inner.wrapped_type_ref,
})
}
_ => get_wrapped_type_default(ctx),
}
}
Type::String => get_wrapped_type_string(ctx),
Type::S64 | Type::U64 => {
let original_type_ref = ctx.original_type_ref;
Ok(WrappedType {
wrap: TokenStreamWrapper::new(
move |ts| quote! { crate::wrappers::BigIntWrapper(#ts) },
),
unwrap: TokenStreamWrapper::new(move |ts| quote! { #ts.0 }),
wrapped_type_ref: quote! { crate::wrappers::BigIntWrapper<#original_type_ref> },
original_type_ref,
})
}
_ => get_wrapped_type_default(ctx),
}
}
struct GetWrappedTypeContext<'a> {
context: &'a GeneratorContext<'a>,
original_type_ref: TokenStream,
in_tuple: bool,
forced_ref: bool,
}
impl<'a> GetWrappedTypeContext<'a> {}
fn get_wrapped_type_default(ctx: GetWrappedTypeContext<'_>) -> anyhow::Result<WrappedType> {
Ok(WrappedType::no_wrapping(ctx.original_type_ref))
}
fn get_wrapped_type_option(
ctx: GetWrappedTypeContext<'_>,
import_rust_type: &RustType,
export_rust_type: &RustType,
elem_type: &Type,
) -> anyhow::Result<WrappedType> {
let RustType::Option {
inner: import_inner,
} = import_rust_type
else {
Err(anyhow!("Type mismatch in option"))?
};
let RustType::Option {
inner: export_inner,
} = export_rust_type
else {
Err(anyhow!("Type mismatch in option"))?
};
if import_rust_type.cannot_into_iter() {
let inner = get_wrapped_type_internal(
ctx.context,
import_inner,
export_inner,
elem_type,
false,
true,
)?;
let inner_wrapped_type_ref = inner.wrapped_type_ref;
let wrapped_v = inner.wrap.run(quote! { v });
let unwrapped_v = inner.unwrap.run(quote! { v });
Ok(WrappedType {
wrap: TokenStreamWrapper::new(move |ts| quote! { #ts.map( |v| #wrapped_v) }),
unwrap: TokenStreamWrapper::new(
move |ts| quote! { #ts.as_ref().map( |v| #unwrapped_v) },
),
original_type_ref: ctx.original_type_ref,
wrapped_type_ref: quote! { Option<#inner_wrapped_type_ref> },
})
} else {
let inner = get_wrapped_type_internal(
ctx.context,
import_inner,
export_inner,
elem_type,
false,
ctx.forced_ref,
)?;
let inner_wrapped_type_ref = inner.wrapped_type_ref;
let wrapped_v = inner.wrap.run(quote! { v });
let unwrapped_v = inner.unwrap.run(quote! { v });
Ok(WrappedType {
wrap: TokenStreamWrapper::new(move |ts| quote! { #ts.map( |v| #wrapped_v) }),
unwrap: TokenStreamWrapper::new(move |ts| quote! { #ts.map( |v| #unwrapped_v) }),
original_type_ref: ctx.original_type_ref,
wrapped_type_ref: quote! { Option<#inner_wrapped_type_ref> },
})
}
}
fn get_wrapped_type_tuple(
ctx: GetWrappedTypeContext<'_>,
import_rust_type: &RustType,
export_rust_type: &RustType,
tuple: &wit_parser::Tuple,
) -> anyhow::Result<WrappedType> {
let RustType::Tuple {
items: import_items,
} = import_rust_type
else {
Err(anyhow!("Type mismatch in tuple"))?
};
let RustType::Tuple {
items: export_items,
} = export_rust_type
else {
Err(anyhow!("Type mismatch in tuple"))?
};
let inner_wrappers = tuple
.types
.iter()
.zip(import_items)
.zip(export_items)
.map(|((ty, import_rust_type), export_rust_type)| {
get_wrapped_type_internal(
ctx.context,
import_rust_type,
export_rust_type,
ty,
true,
false, )
})
.collect::<Result<Vec<_>, _>>()?;
let mut element_wraps = Vec::new();
let mut element_unwraps = Vec::new();
let mut element_wrapped_type_refs = Vec::new();
for element in inner_wrappers {
element_wraps.push(element.wrap);
element_unwraps.push(element.unwrap);
element_wrapped_type_refs.push(element.wrapped_type_ref);
}
let wrap = TokenStreamWrapper::new(move |ts| {
let wrapped_fields = element_wraps
.iter()
.enumerate()
.map(|(idx, wrap)| {
let field = Lit::from(LitInt::new(&idx.to_string(), Span::call_site()));
wrap.run(quote! { #ts.#field })
})
.collect::<Vec<_>>();
quote! {
rquickjs::convert::List((#(#wrapped_fields),*))
}
});
let unwrap = match (export_rust_type, import_rust_type) {
(RustType::Tuple { items: from_items }, RustType::Tuple { items: to_items })
if to_items.len() == from_items.len() =>
{
let mut conversions = Vec::new();
for (from_item, to_item) in from_items.iter().zip(to_items.iter()) {
let conversion = from_item.conversion_into_type(to_item);
conversions.push(conversion);
}
TokenStreamWrapper::new(move |ts| {
let mut converted_items = Vec::new();
for ((idx, unwrap), conversion) in
element_unwraps.iter().enumerate().zip(&conversions)
{
let field = Lit::from(LitInt::new(&idx.to_string(), Span::call_site()));
let unwrap = unwrap.run(quote! { #ts.0.#field});
let conversion = conversion.run(quote! { #unwrap });
converted_items.push(conversion);
}
quote! {
(#(#converted_items),*)
}
})
}
_ => Err(anyhow!("Type mismatch in tuple"))?,
};
Ok(WrappedType {
wrap,
unwrap,
original_type_ref: ctx.original_type_ref,
wrapped_type_ref: quote! { rquickjs::convert::List<(#(#element_wrapped_type_refs),*)> },
})
}
fn get_wrapped_type_list(
ctx: GetWrappedTypeContext<'_>,
import_rust_type: &RustType,
export_rust_type: &RustType,
elem_type: &Type,
) -> anyhow::Result<WrappedType> {
let import_elem_rust_type = match import_rust_type {
RustType::Vec { element } => element,
RustType::Slice { element, .. } => element,
_ => {
return Err(anyhow!(
"Type mismatch in list, expected Vec or Slice, got {:?}",
import_rust_type
));
}
};
let export_elem_rust_type = match export_rust_type {
RustType::Vec { element } => element,
RustType::Slice { element, .. } => element,
_ => {
return Err(anyhow!(
"Type mismatch in list, expected Vec or Slice, got {:?}",
import_rust_type
));
}
};
if import_rust_type.cannot_into_iter() {
let inner = get_wrapped_type_internal(
ctx.context,
import_elem_rust_type,
export_elem_rust_type,
elem_type,
false,
true,
)?;
let inner_wrapped_type_ref = inner.wrapped_type_ref;
let wrapped_v = inner.wrap.run(quote! { v });
let unwrapped_v = inner.unwrap.run(quote! { v });
Ok(WrappedType {
wrap: TokenStreamWrapper::new(
move |ts| quote! { #ts.into_iter().map(|v| #wrapped_v).collect::<Vec<_>>() },
),
unwrap: TokenStreamWrapper::new(
move |ts| quote! { #ts.iter().map(|v| #unwrapped_v).collect::<Vec<_>>() },
),
original_type_ref: ctx.original_type_ref,
wrapped_type_ref: quote! { Vec<#inner_wrapped_type_ref> },
})
} else {
let inner = get_wrapped_type_internal(
ctx.context,
import_elem_rust_type,
export_elem_rust_type,
elem_type,
false,
ctx.forced_ref,
)?;
let inner_wrapped_type_ref = inner.wrapped_type_ref;
let wrapped_v = inner.wrap.run(quote! { v });
let unwrapped_v = inner.unwrap.run(quote! { v });
Ok(WrappedType {
wrap: TokenStreamWrapper::new(
move |ts| quote! { #ts.into_iter().map(|v| #wrapped_v).collect::<Vec<_>>() },
),
unwrap: TokenStreamWrapper::new(
move |ts| quote! { #ts.into_iter().map(|v| #unwrapped_v).collect::<Vec<_>>() },
),
original_type_ref: ctx.original_type_ref,
wrapped_type_ref: quote! { Vec<#inner_wrapped_type_ref> },
})
}
}
fn get_wrapped_type_result(
ctx: GetWrappedTypeContext<'_>,
import_rust_type: &RustType,
export_rust_type: &RustType,
result: &wit_parser::Result_,
) -> anyhow::Result<WrappedType> {
let RustType::Result {
ok: import_ok_rust_type,
err: import_err_rust_type,
} = import_rust_type
else {
Err(anyhow!("Type mismatch in result"))?
};
let RustType::Result {
ok: export_ok_rust_type,
err: export_err_rust_type,
} = export_rust_type
else {
Err(anyhow!("Type mismatch in result"))?
};
let ok = result
.ok
.as_ref()
.map(|ok| {
get_wrapped_type_internal(
ctx.context,
import_ok_rust_type,
export_ok_rust_type,
ok,
false,
ctx.forced_ref,
)
})
.transpose()?
.unwrap_or(WrappedType::unit());
let err = result
.err
.as_ref()
.map(|err| {
get_wrapped_type_internal(
ctx.context,
import_err_rust_type,
export_err_rust_type,
err,
false,
ctx.forced_ref,
)
})
.transpose()?
.unwrap_or(WrappedType::unit());
let wrapped_ok = ok.wrapped_type_ref;
let wrapped_err = err.wrapped_type_ref;
let wrap_ok = ok.wrap.run(quote! { v });
let wrap_err = err.wrap.run(quote! { v });
Ok(WrappedType {
wrap: TokenStreamWrapper::new(move |ts| {
quote! {
crate::wrappers::JsResult(
match #ts {
Ok(v) => Ok(#wrap_ok),
Err(v) => Err(#wrap_err),
}
)
}
}),
unwrap: TokenStreamWrapper::new(|ts| quote! { #ts.0 }),
original_type_ref: ctx.original_type_ref,
wrapped_type_ref: quote! { crate::wrappers::JsResult<#wrapped_ok, #wrapped_err> },
})
}
fn get_wrapped_type_borrow_handle(
ctx: GetWrappedTypeContext<'_>,
resource_type_id: &TypeId,
) -> anyhow::Result<WrappedType> {
if ctx.context.is_exported_type(*resource_type_id) {
Ok(WrappedType::no_wrapping(ctx.original_type_ref))
} else {
let borrowed_resource_ref = borrowed_resource_ref(ctx.context, resource_type_id)?;
Ok(WrappedType {
original_type_ref: ctx.original_type_ref,
wrapped_type_ref: borrowed_resource_ref.clone(),
wrap: TokenStreamWrapper::new(move |ts| {
quote! { #borrowed_resource_ref(#ts) }
}),
unwrap: if ctx.forced_ref {
TokenStreamWrapper::new(|ts| {
quote! { &#ts.0 }
})
} else {
TokenStreamWrapper::new(|ts| {
quote! { #ts.0 }
})
},
})
}
}
fn get_wrapped_type_adt(ctx: GetWrappedTypeContext<'_>) -> anyhow::Result<WrappedType> {
Ok(WrappedType::no_wrapping(ctx.original_type_ref))
}
fn get_wrapped_type_string(ctx: GetWrappedTypeContext<'_>) -> anyhow::Result<WrappedType> {
if ctx.in_tuple {
Ok(WrappedType {
wrap: TokenStreamWrapper::identity(),
unwrap: TokenStreamWrapper::new(|s| quote! { #s.clone() }),
original_type_ref: ctx.original_type_ref.clone(),
wrapped_type_ref: ctx.original_type_ref,
})
} else if ctx.forced_ref {
Ok(WrappedType {
wrap: TokenStreamWrapper::identity(),
unwrap: TokenStreamWrapper::reference(),
original_type_ref: ctx.original_type_ref.clone(),
wrapped_type_ref: ctx.original_type_ref,
})
} else {
Ok(WrappedType::no_wrapping(ctx.original_type_ref))
}
}
fn owned_resource_ref(
context: &GeneratorContext<'_>,
resource_type_id: &TypeId,
) -> anyhow::Result<TokenStream> {
let resource_type = context
.resolve
.types
.get(*resource_type_id)
.ok_or_else(|| anyhow!("Unknown resource type id: {resource_type_id:?}"))?;
let resource_name = resource_type
.name
.as_ref()
.ok_or_else(|| anyhow!("Resource type {resource_type:?} has no name"))?;
let (interface, is_export) =
analyse_resource_type_owner(context, resource_name, resource_type)?;
let handle_ident = Ident::new(&resource_name.to_upper_camel_case(), Span::call_site());
let handle_path = if is_export {
ident_in_exported_interface_or_global(context, handle_ident, interface)
} else {
ident_in_imported_interface_or_global(context, handle_ident, interface)
};
Ok(quote! { #handle_path })
}
fn follow_type_paths(context: &GeneratorContext<'_>, type_id: TypeId) -> anyhow::Result<TypeDef> {
let mut current_type_id = type_id;
loop {
let typ = context.typ(current_type_id)?;
match &typ.kind {
TypeDefKind::Type(Type::Id(inner_type_id)) => {
current_type_id = *inner_type_id;
continue;
}
_ => return Ok(typ.clone()),
}
}
}
fn analyse_resource_type_owner<'a>(
context: &'a GeneratorContext<'a>,
resource_name: &str,
resource_type: &TypeDef,
) -> anyhow::Result<(Option<(&'a str, &'a Interface)>, bool)> {
match &resource_type.owner {
TypeOwner::World(world_id) => {
if world_id == &context.world {
Ok((None, true))
} else {
Err(anyhow!(
"Resource type {resource_name} is owned by a different world"
))
}
}
TypeOwner::Interface(interface_id) => {
let interface = context
.resolve
.interfaces
.get(*interface_id)
.ok_or_else(|| anyhow!("Unknown interface id: {interface_id:?}"))?;
let interface_name = interface
.name
.as_ref()
.ok_or_else(|| anyhow!("Interface export does not have a name"))?;
Ok((
Some((interface_name.as_str(), interface)),
context.is_exported_interface(*interface_id),
))
}
TypeOwner::None => Err(anyhow!("Resource type {resource_name} has no owner")),
}
}
fn borrowed_resource_ref(
context: &GeneratorContext<'_>,
resource_type_id: &TypeId,
) -> anyhow::Result<TokenStream> {
let resource_type = follow_type_paths(context, *resource_type_id)?;
let resource_name = resource_type
.name
.as_ref()
.ok_or_else(|| anyhow!("Resource type {resource_type:?} has no name"))?;
let (interface, is_export) =
analyse_resource_type_owner(context, resource_name, &resource_type)?;
if is_export {
let borrow_handle_ident = Ident::new(
&format!("{}Borrow", resource_name.to_upper_camel_case()),
Span::call_site(),
);
let borrow_handle_path =
ident_in_exported_interface_or_global(context, borrow_handle_ident, interface);
Ok(quote! { #borrow_handle_path<'_> })
} else {
let borrow_handle_ident = Ident::new(
&format!("Borrow{}Wrapper", resource_name.to_upper_camel_case()),
Span::call_site(),
);
let module_path = if let Some((interface_name, interface)) = interface {
let package_id = interface
.package
.ok_or_else(|| anyhow::anyhow!("Interface does not have a package"))?;
let package = context
.resolve
.packages
.get(package_id)
.ok_or_else(|| anyhow::anyhow!("Unknown package id: {package_id:?}"))?;
let module_name = format!(
"{}_{}",
package.name.to_string().to_snake_case(),
interface_name.to_snake_case()
);
let module_ident = Ident::new(&module_name, Span::call_site());
quote! { crate::modules::#module_ident }
} else {
quote! { crate::modules }
};
Ok(quote! { #module_path::#borrow_handle_ident })
}
}
fn record_visited_inner_types(context: &GeneratorContext<'_>, typ: &TypeDef) -> anyhow::Result<()> {
match &typ.kind {
TypeDefKind::Record(record) => {
for field in &record.fields {
let _ = to_type_ref(context, &field.ty)?;
}
}
TypeDefKind::Variant(variant) => {
for variant_case in &variant.cases {
if let Some(ty) = &variant_case.ty {
let _ = to_type_ref(context, ty)?;
}
}
}
TypeDefKind::Type(Type::Id(type_id)) => {
let _ = type_id_to_type_ref(context, *type_id)?;
}
_ => {}
}
Ok(())
}
pub struct ProcessedParameter {
pub ident: Ident,
pub wrapped_type: Option<WrappedType>,
pub(crate) export_parameter: RustFunctionParameter,
pub(crate) import_parameter: RustFunctionParameter,
}
pub fn process_parameter(
context: &GeneratorContext<'_>,
param_name: &str,
param_type: &Type,
export_parameter: &RustFunctionParameter,
import_parameter: &RustFunctionParameter,
) -> anyhow::Result<ProcessedParameter> {
let wrapped_type = get_wrapped_type(
context,
&import_parameter.typ,
&export_parameter.typ,
param_type,
)
.context(format!("Failed to encode parameter {param_name}'s type"))?;
let param_name = &export_parameter.name;
Ok(ProcessedParameter {
ident: Ident::new(param_name, Span::call_site()),
wrapped_type: Some(wrapped_type),
export_parameter: export_parameter.clone(),
import_parameter: import_parameter.clone(),
})
}
pub fn to_wrapped_param_refs(processed_parameters: &[ProcessedParameter]) -> Vec<TokenStream> {
processed_parameters
.iter()
.map(|param| {
let name = ¶m.ident;
let unwrapped = quote! { #name };
match ¶m.wrapped_type {
Some(wrapped) => {
let wrap = &wrapped.wrap;
wrap.run(unwrapped)
}
None => unwrapped,
}
})
.collect::<Vec<_>>()
}
pub fn to_unwrapped_param_refs(processed_parameters: &[ProcessedParameter]) -> Vec<TokenStream> {
processed_parameters
.iter()
.map(|param| {
let name = ¶m.ident;
let wrapped = quote! { #name };
match ¶m.wrapped_type {
Some(wrapped_type) => param
.export_parameter
.typ
.conversion_into_type(¶m.import_parameter.typ)
.run(wrapped_type.unwrap.run(wrapped)),
None => wrapped,
}
})
.collect::<Vec<_>>()
}
pub fn to_original_func_arg_list(processed_parameters: &[ProcessedParameter]) -> Vec<TokenStream> {
processed_parameters
.iter()
.map(|param| {
let name = ¶m.ident;
let typ = ¶m.wrapped_type;
match typ {
Some(wrapped) => {
let typ = &wrapped.original_type_ref;
quote! { #name: #typ }
}
None => quote! { &#name },
}
})
.collect()
}
pub fn to_wrapped_func_arg_list(processed_parameters: &[ProcessedParameter]) -> Vec<TokenStream> {
processed_parameters
.iter()
.map(|param| {
let name = ¶m.ident;
let typ = ¶m.wrapped_type;
match typ {
Some(wrapped) => {
let typ = &wrapped.wrapped_type_ref;
quote! { #name: #typ }
}
None => quote! { &#name },
}
})
.collect()
}
pub fn param_refs_as_tuple(param_refs: &[TokenStream]) -> TokenStream {
if param_refs.len() == 1 {
let item = ¶m_refs[0];
quote! { (#item,) }
} else {
quote! { crate::wrappers::JsArgs((#(#param_refs),*)) }
}
}
pub fn get_function_name(name: &str, function: &Function) -> anyhow::Result<String> {
let func_name = match &function.kind {
FunctionKind::Freestanding => name.to_string(),
FunctionKind::AsyncFreestanding => name.to_string(),
FunctionKind::Method(_) => name["[method]".len()..]
.split_once('.')
.ok_or_else(|| anyhow::anyhow!("Failed to parse method name {name}"))?
.1
.to_string(),
FunctionKind::AsyncMethod(_) => name["[method]".len()..]
.split_once('.')
.ok_or_else(|| anyhow::anyhow!("Failed to parse method name {name}"))?
.1
.to_string(),
FunctionKind::Static(_) => name["[static]".len()..]
.split_once('.')
.ok_or_else(|| anyhow::anyhow!("Failed to parse method name {name}"))?
.1
.to_string(),
FunctionKind::AsyncStatic(_) => name["[static]".len()..]
.split_once('.')
.ok_or_else(|| anyhow::anyhow!("Failed to parse method name {name}"))?
.1
.to_string(),
FunctionKind::Constructor(_) => "new".to_string(),
};
Ok(func_name)
}
pub struct ReturnTypeInformation {
pub wit_level_ret: WrappedType,
pub func_ret: WrappedType,
pub expected_exception: Option<WrappedType>,
}
pub fn get_return_type(
context: &GeneratorContext,
function: &Function,
name: &str,
rust_fn: &RustWitFunction,
) -> anyhow::Result<ReturnTypeInformation> {
match &function.result {
Some(typ) => match &rust_fn.return_type {
RustType::Result { ok, err } => {
let Type::Id(id) = typ else {
return Err(anyhow!("Unexpected mismatch between Type and RustType"));
};
let typedef = context
.resolve
.types
.get(*id)
.ok_or_else(|| anyhow!("Unknown type id {id:?}"))?;
let TypeDefKind::Result(result) = &typedef.kind else {
return Err(anyhow!(
"Unexpected mismatch between TypeDefKind and RustType"
));
};
let expected_exception = result
.err
.map(|err_type| get_wrapped_type(context, err, err, &err_type))
.transpose()?
.unwrap_or(WrappedType::unit());
if let Some(ok_type) = result.ok {
Ok(ReturnTypeInformation {
wit_level_ret: get_wrapped_type(
context,
&rust_fn.return_type,
&rust_fn.return_type,
typ,
)
.context(format!("Failed to encode result type for {name}"))?,
func_ret: get_wrapped_type(context, ok, ok, &ok_type)
.context(format!("Failed to encode result type for {name}"))?,
expected_exception: Some(expected_exception),
})
} else {
Ok(ReturnTypeInformation {
wit_level_ret: get_wrapped_type(
context,
&rust_fn.return_type,
&rust_fn.return_type,
typ,
)
.context(format!("Failed to encode result type for {name}"))?,
func_ret: WrappedType::unit(),
expected_exception: Some(expected_exception),
})
}
}
_ => Ok(ReturnTypeInformation {
wit_level_ret: get_wrapped_type(
context,
&rust_fn.return_type,
&rust_fn.return_type,
typ,
)
.context(format!("Failed to encode result type for {name}"))?,
func_ret: get_wrapped_type(
context,
&rust_fn.return_type,
&rust_fn.return_type,
typ,
)
.context(format!("Failed to encode result type for {name}"))?,
expected_exception: None,
}),
},
None => Ok(ReturnTypeInformation {
wit_level_ret: WrappedType::unit(),
func_ret: WrappedType::unit(),
expected_exception: None,
}),
}
}