use crate::GeneratorContext;
use crate::javascript::escape_js_ident;
use crate::rust_bindgen::{RustType, TypeOwnershipStyle, escape_rust_ident, type_mode_for};
use crate::types::{get_wrapped_type, type_id_to_type_ref};
use anyhow::Context;
use heck::{ToLowerCamelCase, ToShoutySnakeCase, ToSnakeCase, ToUpperCamelCase};
use proc_macro2::{Ident, Span, TokenStream};
use quote::quote;
use std::collections::BTreeSet;
use syn::{Lit, LitStr};
use wit_parser::{Type, TypeDefKind, TypeId};
pub fn generate_conversions(context: &GeneratorContext<'_>) -> anyhow::Result<()> {
let conversion_instances = generate_conversion_instances(context)?;
let conversions_tokens = quote! {
#(#conversion_instances)*
};
let conversions_ast: syn::File = syn::parse2(conversions_tokens)
.context("failed to parse generated conversions.rs tokens")?;
let conversions_path = context.output.join("src").join("conversions.rs");
let conversions_src = prettier_please::unparse(&conversions_ast);
crate::write_if_changed(&conversions_path, conversions_src)?;
Ok(())
}
fn generate_conversion_instances(
context: &GeneratorContext<'_>,
) -> anyhow::Result<Vec<TokenStream>> {
let mut result = Vec::new();
let types_to_process = context.visited_types.borrow().clone();
let mut visited_types = BTreeSet::new();
for type_id in &types_to_process {
if let Some(snippet) =
generate_conversion_instances_for_type(context, *type_id, &mut visited_types)?
{
result.push(snippet);
}
}
Ok(result)
}
fn generate_conversion_instances_for_type(
context: &GeneratorContext<'_>,
type_id: TypeId,
visited_types: &mut BTreeSet<TypeId>,
) -> anyhow::Result<Option<TokenStream>> {
if !visited_types.insert(type_id) {
return Ok(None);
}
let typ = context.typ(type_id)?;
match &typ.kind {
TypeDefKind::Record(record) => {
let type_path = type_id_to_type_ref(context, type_id)?;
let mut set_fields = Vec::new();
let mut get_fields = Vec::new();
let mut rust_field_list = Vec::new();
for field in &record.fields {
let js_field_name = escape_js_ident(field.name.to_lower_camel_case());
let rust_field_ident = Ident::new(
&escape_rust_ident(&field.name.to_snake_case()),
Span::call_site(),
);
let field_name_lit = Lit::Str(LitStr::new(&js_field_name, Span::call_site()));
let rust_type = RustType::from_type(
context,
&field.ty,
type_mode_for(context, &field.ty, TypeOwnershipStyle::Owned, "'_"),
);
let field_type = get_wrapped_type(context, &rust_type, &rust_type, &field.ty)?;
let original_field_type = &field_type.original_type_ref;
let wrapped_field_type = &field_type.wrapped_type_ref;
let wrapped_field = field_type.wrap.run(quote! { self.#rust_field_ident });
let unwrapped_field = field_type.unwrap.run(quote! { #rust_field_ident });
set_fields.push(quote! {
let #rust_field_ident: #wrapped_field_type = #wrapped_field;
obj.set(#field_name_lit, #rust_field_ident)?;
});
get_fields.push(quote! {
let #rust_field_ident: #wrapped_field_type = obj.get(#field_name_lit)?;
let #rust_field_ident: #original_field_type = #unwrapped_field;
});
rust_field_list.push(rust_field_ident);
}
Ok(Some(quote! {
impl<'js> rquickjs::IntoJs<'js> for #type_path {
fn into_js(self, ctx: &rquickjs::Ctx<'js>) -> rquickjs::Result<rquickjs::Value<'js>> {
let obj = rquickjs::Object::new(ctx.clone())?;
#(#set_fields);*
Ok(obj.into_value())
}
}
impl<'js> rquickjs::FromJs<'js> for #type_path {
fn from_js(_ctx: &rquickjs::Ctx<'js>, value: rquickjs::Value<'js>) -> rquickjs::Result<Self> {
let obj = rquickjs::Object::from_value(value)?;
#(#get_fields);*
Ok(Self {
#(#rust_field_list),*
})
}
}
}))
}
TypeDefKind::Flags(flags) => {
let type_path = type_id_to_type_ref(context, type_id)?;
let mut set_fields = Vec::new();
let mut get_fields = Vec::new();
for flag in &flags.flags {
let js_field_name = escape_js_ident(flag.name.to_lower_camel_case());
let rust_field_ident =
Ident::new(&flag.name.to_shouty_snake_case(), Span::call_site());
let field_name_lit = Lit::Str(LitStr::new(&js_field_name, Span::call_site()));
set_fields.push(quote! {
obj.set(#field_name_lit, self & #type_path::#rust_field_ident == #type_path::#rust_field_ident)?;
});
get_fields.push(quote! {
if obj.get(#field_name_lit)? {
result |= #type_path::#rust_field_ident;
}
});
}
Ok(Some(quote! {
impl<'js> rquickjs::IntoJs<'js> for #type_path {
fn into_js(self, ctx: &rquickjs::Ctx<'js>) -> rquickjs::Result<rquickjs::Value<'js>> {
let obj = rquickjs::Object::new(ctx.clone())?;
#(#set_fields);*
Ok(obj.into_value())
}
}
impl<'js> rquickjs::FromJs<'js> for #type_path {
fn from_js(_ctx: &rquickjs::Ctx<'js>, value: rquickjs::Value<'js>) -> rquickjs::Result<Self> {
let obj = rquickjs::Object::from_value(value)?;
let mut result = #type_path::empty();
#(#get_fields);*
Ok(result)
}
}
}))
}
TypeDefKind::Variant(variant) => {
let type_path = type_id_to_type_ref(context, type_id)?;
let mut into_cases = Vec::new();
let mut from_cases = Vec::new();
for case in &variant.cases {
let rust_ident = Ident::new(&case.name.to_upper_camel_case(), Span::call_site());
let case_name_lit = Lit::Str(LitStr::new(&case.name, Span::call_site()));
if let Some(ty) = &case.ty {
let rust_type = RustType::from_type(
context,
ty,
type_mode_for(context, ty, TypeOwnershipStyle::Owned, "'_"),
);
let wrapped_type = get_wrapped_type(context, &rust_type, &rust_type, ty)?;
let wrapped_inner = wrapped_type.wrap.run(quote! { inner });
let unwrapped_inner = wrapped_type.unwrap.run(quote! { inner });
let wrapped_type = &wrapped_type.wrapped_type_ref;
into_cases.push(quote! {
#type_path::#rust_ident(inner) => {
obj.set(crate::wrappers::TAG, #case_name_lit)?;
let case_value: #wrapped_type = #wrapped_inner;
obj.set(crate::wrappers::VALUE, case_value)?;
}
});
from_cases.push(quote! {
#case_name_lit => {
let inner: #wrapped_type = obj.get(crate::wrappers::VALUE)?;
Ok(#type_path::#rust_ident(#unwrapped_inner))
}
});
} else {
into_cases.push(quote! {
#type_path::#rust_ident => {
obj.set(crate::wrappers::TAG, #case_name_lit)?;
}
});
from_cases.push(quote! {
#case_name_lit => Ok(#type_path::#rust_ident),
});
}
}
let lit_js_type = Lit::Str(LitStr::new(
&format!("JS {}", typ.name.clone().unwrap_or_default()),
Span::call_site(),
));
let lit_wit_type = Lit::Str(LitStr::new(
&format!("WIT {}", typ.name.clone().unwrap_or_default()),
Span::call_site(),
));
Ok(Some(quote! {
impl<'js> rquickjs::IntoJs<'js> for #type_path {
fn into_js(self, ctx: &rquickjs::Ctx<'js>) -> rquickjs::Result<rquickjs::Value<'js>> {
let obj = rquickjs::Object::new(ctx.clone())?;
match self {
#(#into_cases)*
}
Ok(obj.into_value())
}
}
impl<'js> rquickjs::FromJs<'js> for #type_path {
fn from_js(_ctx: &rquickjs::Ctx<'js>, value: rquickjs::Value<'js>) -> rquickjs::Result<Self> {
let obj = rquickjs::Object::from_value(value)?;
let tag: String = obj.get(crate::wrappers::TAG)?;
match tag.as_str() {
#(#from_cases)*
_ => Err(rquickjs::Error::new_from_js_message(
#lit_js_type,
#lit_wit_type,
format!("Unknown variant case: {tag}"),
)),
}
}
}
}))
}
TypeDefKind::Enum(enm) => {
let type_path = type_id_to_type_ref(context, type_id)?;
let mut into_cases = Vec::new();
let mut from_cases = Vec::new();
for case in &enm.cases {
let ident = Ident::new(&case.name.to_upper_camel_case(), Span::call_site());
let lit = Lit::Str(LitStr::new(&case.name, Span::call_site()));
into_cases.push(quote! {
#type_path::#ident => #lit.into_js(ctx),
});
from_cases.push(quote! {
#lit => Ok(#type_path::#ident),
});
}
let name = typ.name.clone().unwrap_or_default();
let lit_js_type = Lit::Str(LitStr::new(&format!("JS {name}"), Span::call_site()));
let lit_wit_type = Lit::Str(LitStr::new(&format!("WIT {name}"), Span::call_site()));
Ok(Some(quote! {
impl<'js> rquickjs::IntoJs<'js> for #type_path {
fn into_js(self, ctx: &rquickjs::Ctx<'js>) -> rquickjs::Result<rquickjs::Value<'js>> {
match self {
#(#into_cases)*
}
}
}
impl<'js> rquickjs::FromJs<'js> for #type_path {
fn from_js(_ctx: &rquickjs::Ctx<'js>, value: rquickjs::Value<'js>) -> rquickjs::Result<Self> {
let value = value
.as_string()
.ok_or_else(|| {
rquickjs::Error::new_from_js_message(#lit_js_type, #lit_wit_type, "Expected a string")
})?
.to_string()?;
match value.as_str() {
#(#from_cases)*
_ => Err(rquickjs::Error::new_from_js_message(
#lit_js_type,
#lit_wit_type,
format!("Unknown case value: {value}"),
)),
}
}
}
}))
}
TypeDefKind::Type(Type::Id(type_id)) => {
generate_conversion_instances_for_type(context, *type_id, visited_types)
}
_ => Ok(None),
}
}