#![warn(missing_docs, rust_2018_idioms, unused_qualifications)]
#![no_std]
extern crate alloc;
use alloc::format;
use alloc::string::ToString;
use proc_macro::TokenStream;
use proc_macro2::{Span, TokenStream as TokenStream2};
use quote::quote;
use syn::{Data, DeriveInput, Error, parse_macro_input};
macro_rules! derive_error {
($string: tt) => {
Error::new(Span::call_site(), $string)
.to_compile_error()
.into()
};
}
#[proc_macro_derive(TryFromJsValue)]
pub fn derive_try_from_jsvalue(input: TokenStream) -> TokenStream {
let input: DeriveInput = parse_macro_input!(input as DeriveInput);
let name = input.ident;
let data = input.data;
match data {
Data::Struct(_) => {}
_ => return derive_error!("TryFromJsValue may only be derived on structs"),
};
let wasm_bindgen_attr = input.attrs.iter().find(|attr| match &attr.meta {
syn::Meta::Path(path) => path.is_ident("wasm_bindgen"),
syn::Meta::List(list) => list.path.is_ident("wasm_bindgen"),
syn::Meta::NameValue(_) => false,
});
let Some(wasm_bindgen_attr) = wasm_bindgen_attr else {
return derive_error!(
"TryFromJsValue can be defined only on struct exported to wasm with #[wasm_bindgen]"
);
};
let maybe_js_class = if let syn::Meta::List(list) = &wasm_bindgen_attr.meta {
let mut js_name = None;
if let Err(err) = list.parse_nested_meta(|meta| {
if meta.path.is_ident("js_name") {
let value = meta.value()?;
let s: syn::LitStr = value.parse()?;
js_name = Some(s.value());
}
Ok(())
}) {
return err.into_compile_error().into();
}
js_name
} else {
None
};
let wasm_bindgen_macro_invocaton = match maybe_js_class {
Some(class) => format!(
"::wasm_bindgen::prelude::wasm_bindgen(js_class = \"{}\")",
class
),
None => "::wasm_bindgen::prelude::wasm_bindgen".to_string(),
}
.parse::<TokenStream2>()
.unwrap();
let expanded = quote! {
impl #name {
pub fn __get_classname() -> &'static str {
::core::stringify!(#name)
}
}
#[#wasm_bindgen_macro_invocaton]
impl #name {
#[::wasm_bindgen::prelude::wasm_bindgen(js_name = "__getClassname")]
pub fn __js_get_classname(&self) -> ::wasm_bindgen_derive::alloc::string::String {
use ::wasm_bindgen_derive::alloc::borrow::ToOwned;
::core::stringify!(#name).to_owned()
}
}
impl ::core::convert::TryFrom<&::wasm_bindgen::JsValue> for #name {
type Error = ::wasm_bindgen_derive::alloc::string::String;
fn try_from(js: &::wasm_bindgen::JsValue) -> Result<Self, Self::Error> {
use ::wasm_bindgen_derive::alloc::{borrow::ToOwned, string::{String, ToString}, format};
use ::wasm_bindgen::JsCast;
use ::wasm_bindgen::convert::RefFromWasmAbi;
let classname = Self::__get_classname();
if !js.is_object() {
return Err(format!("Value supplied as {} is not an object", classname));
}
let no_get_classname_msg = concat!(
"no __getClassname method specified for object; ",
"did you forget to derive TryFromJsObject for this type?");
let get_classname = ::js_sys::Reflect::get(
js,
&::wasm_bindgen::JsValue::from("__getClassname"),
)
.or(Err(no_get_classname_msg.to_string()))?;
if get_classname.is_undefined() {
return Err(no_get_classname_msg.to_string());
}
let get_classname = get_classname
.dyn_into::<::js_sys::Function>()
.map_err(|err| format!("__getClassname is not a function, {:?}", err))?;
let object_classname: String = ::js_sys::Reflect::apply(
&get_classname,
js,
&::js_sys::Array::new(),
)
.ok()
.and_then(|v| v.as_string())
.ok_or_else(|| "Failed to get classname".to_owned())?;
if object_classname.as_str() == classname {
let ptr = ::js_sys::Reflect::get(js, &::wasm_bindgen::JsValue::from_str("__wbg_ptr"))
.map_err(|err| format!("{:?}", err))?;
let ptr_u32: u32 = ptr.as_f64().ok_or(::wasm_bindgen::JsValue::NULL)
.map_err(|err| format!("{:?}", err))?
as u32;
let ptr_abi: ::wasm_bindgen::__rt::WasmPtr<::wasm_bindgen::__rt::WasmRefCell<#name>> =
::wasm_bindgen::__rt::WasmPtr::from_usize(ptr_u32 as usize);
let instance_ref = unsafe { #name::ref_from_abi(ptr_abi) };
Ok(instance_ref.clone())
} else {
Err(format!("Cannot convert {} to {}", object_classname, classname))
}
}
}
};
TokenStream::from(expanded)
}