wasm_bindgen_derive_macro/
lib.rs1#![warn(missing_docs, rust_2018_idioms, unused_qualifications)]
7#![no_std]
8
9extern crate alloc;
10
11use alloc::format;
12use alloc::string::ToString;
13
14use proc_macro::TokenStream;
15use proc_macro2::{Span, TokenStream as TokenStream2};
16use quote::quote;
17use syn::{Data, DeriveInput, Error, parse_macro_input};
18
19macro_rules! derive_error {
20 ($string: tt) => {
21 Error::new(Span::call_site(), $string)
22 .to_compile_error()
23 .into()
24 };
25}
26
27#[proc_macro_derive(TryFromJsValue)]
40pub fn derive_try_from_jsvalue(input: TokenStream) -> TokenStream {
41 let input: DeriveInput = parse_macro_input!(input as DeriveInput);
42
43 let name = input.ident;
44 let data = input.data;
45
46 match data {
47 Data::Struct(_) => {}
48 _ => return derive_error!("TryFromJsValue may only be derived on structs"),
49 };
50
51 let wasm_bindgen_attr = input.attrs.iter().find(|attr| match &attr.meta {
53 syn::Meta::Path(path) => path.is_ident("wasm_bindgen"),
54 syn::Meta::List(list) => list.path.is_ident("wasm_bindgen"),
55 syn::Meta::NameValue(_) => false,
56 });
57
58 let Some(wasm_bindgen_attr) = wasm_bindgen_attr else {
59 return derive_error!(
60 "TryFromJsValue can be defined only on struct exported to wasm with #[wasm_bindgen]"
61 );
62 };
63
64 let maybe_js_class = if let syn::Meta::List(list) = &wasm_bindgen_attr.meta {
65 let mut js_name = None;
66 if let Err(err) = list.parse_nested_meta(|meta| {
67 if meta.path.is_ident("js_name") {
68 let value = meta.value()?;
69 let s: syn::LitStr = value.parse()?;
70 js_name = Some(s.value());
71 }
72 Ok(())
73 }) {
74 return err.into_compile_error().into();
75 }
76 js_name
77 } else {
78 None
79 };
80
81 let wasm_bindgen_macro_invocaton = match maybe_js_class {
82 Some(class) => format!(
83 "::wasm_bindgen::prelude::wasm_bindgen(js_class = \"{}\")",
84 class
85 ),
86 None => "::wasm_bindgen::prelude::wasm_bindgen".to_string(),
87 }
88 .parse::<TokenStream2>()
89 .unwrap();
90
91 let expanded = quote! {
94 impl #name {
95 pub fn __get_classname() -> &'static str {
96 ::core::stringify!(#name)
97 }
98 }
99
100 #[#wasm_bindgen_macro_invocaton]
101 impl #name {
102 #[::wasm_bindgen::prelude::wasm_bindgen(js_name = "__getClassname")]
103 pub fn __js_get_classname(&self) -> ::wasm_bindgen_derive::alloc::string::String {
104 use ::wasm_bindgen_derive::alloc::borrow::ToOwned;
105 ::core::stringify!(#name).to_owned()
106 }
107 }
108
109 impl ::core::convert::TryFrom<&::wasm_bindgen::JsValue> for #name {
110 type Error = ::wasm_bindgen_derive::alloc::string::String;
111
112 fn try_from(js: &::wasm_bindgen::JsValue) -> Result<Self, Self::Error> {
113 use ::wasm_bindgen_derive::alloc::{borrow::ToOwned, string::{String, ToString}, format};
114 use ::wasm_bindgen::JsCast;
115 use ::wasm_bindgen::convert::RefFromWasmAbi;
116
117 let classname = Self::__get_classname();
118
119 if !js.is_object() {
120 return Err(format!("Value supplied as {} is not an object", classname));
121 }
122
123 let no_get_classname_msg = concat!(
124 "no __getClassname method specified for object; ",
125 "did you forget to derive TryFromJsObject for this type?");
126
127 let get_classname = ::js_sys::Reflect::get(
128 js,
129 &::wasm_bindgen::JsValue::from("__getClassname"),
130 )
131 .or(Err(no_get_classname_msg.to_string()))?;
132
133 if get_classname.is_undefined() {
134 return Err(no_get_classname_msg.to_string());
135 }
136
137 let get_classname = get_classname
138 .dyn_into::<::js_sys::Function>()
139 .map_err(|err| format!("__getClassname is not a function, {:?}", err))?;
140
141 let object_classname: String = ::js_sys::Reflect::apply(
142 &get_classname,
143 js,
144 &::js_sys::Array::new(),
145 )
146 .ok()
147 .and_then(|v| v.as_string())
148 .ok_or_else(|| "Failed to get classname".to_owned())?;
149
150 if object_classname.as_str() == classname {
151 let ptr = ::js_sys::Reflect::get(js, &::wasm_bindgen::JsValue::from_str("__wbg_ptr"))
154 .map_err(|err| format!("{:?}", err))?;
155 let ptr_u32: u32 = ptr.as_f64().ok_or(::wasm_bindgen::JsValue::NULL)
157 .map_err(|err| format!("{:?}", err))?
158 as u32;
159 let ptr_abi: ::wasm_bindgen::__rt::WasmPtr<::wasm_bindgen::__rt::WasmRefCell<#name>> =
160 ::wasm_bindgen::__rt::WasmPtr::from_usize(ptr_u32 as usize);
161 let instance_ref = unsafe { #name::ref_from_abi(ptr_abi) };
162 Ok(instance_ref.clone())
163 } else {
164 Err(format!("Cannot convert {} to {}", object_classname, classname))
165 }
166 }
167 }
168 };
169
170 TokenStream::from(expanded)
171}