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::{parse_macro_input, Data, DeriveInput, Error};
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)]
41pub fn derive_try_from_jsvalue(input: TokenStream) -> TokenStream {
42 let input: DeriveInput = parse_macro_input!(input as DeriveInput);
43
44 let name = input.ident;
45 let data = input.data;
46
47 match data {
48 Data::Struct(_) => {}
49 _ => return derive_error!("TryFromJsValue may only be derived on structs"),
50 };
51
52 let wasm_bindgen_meta = input.attrs.iter().find_map(|attr| {
53 attr.parse_meta()
54 .ok()
55 .and_then(|meta| match meta.path().is_ident("wasm_bindgen") {
56 true => Some(meta),
57 false => None,
58 })
59 });
60 if wasm_bindgen_meta.is_none() {
61 return derive_error!(
62 "TryFromJsValue can be defined only on struct exported to wasm with #[wasm_bindgen]"
63 );
64 }
65
66 let maybe_js_class = wasm_bindgen_meta
67 .and_then(|meta| match meta {
68 syn::Meta::List(list) => Some(list),
69 _ => None,
70 })
71 .and_then(|meta_list| {
72 meta_list.nested.iter().find_map(|nested_meta| {
73 let maybe_meta = match nested_meta {
74 syn::NestedMeta::Meta(meta) => Some(meta),
75 _ => None,
76 };
77
78 maybe_meta
79 .and_then(|meta| match meta {
80 syn::Meta::NameValue(name_value) => Some(name_value),
81 _ => None,
82 })
83 .and_then(|name_value| match name_value.path.is_ident("js_name") {
84 true => Some(name_value.lit.clone()),
85 false => None,
86 })
87 .and_then(|lit| match lit {
88 syn::Lit::Str(str) => Some(str.value()),
89 _ => None,
90 })
91 })
92 });
93
94 let wasm_bindgen_macro_invocaton = match maybe_js_class {
95 Some(class) => format!(
96 "::wasm_bindgen::prelude::wasm_bindgen(js_class = \"{}\")",
97 class
98 ),
99 None => "::wasm_bindgen::prelude::wasm_bindgen".to_string(),
100 }
101 .parse::<TokenStream2>()
102 .unwrap();
103
104 let expanded = quote! {
105 impl #name {
106 pub fn __get_classname() -> &'static str {
107 ::core::stringify!(#name)
108 }
109 }
110
111 #[#wasm_bindgen_macro_invocaton]
112 impl #name {
113 #[::wasm_bindgen::prelude::wasm_bindgen(js_name = "__getClassname")]
114 pub fn __js_get_classname(&self) -> String {
115 use ::alloc::borrow::ToOwned;
116 ::core::stringify!(#name).to_owned()
117 }
118 }
119
120 impl ::core::convert::TryFrom<&::wasm_bindgen::JsValue> for #name {
121 type Error = String;
122
123 fn try_from(js: &::wasm_bindgen::JsValue) -> Result<Self, Self::Error> {
124 use ::alloc::borrow::ToOwned;
125 use ::alloc::string::ToString;
126 use ::wasm_bindgen::JsCast;
127 use ::wasm_bindgen::convert::RefFromWasmAbi;
128
129 let classname = Self::__get_classname();
130
131 if !js.is_object() {
132 return Err(format!("Value supplied as {} is not an object", classname));
133 }
134
135 let no_get_classname_msg = concat!(
136 "no __getClassname method specified for object; ",
137 "did you forget to derive TryFromJsObject for this type?");
138
139 let get_classname = ::js_sys::Reflect::get(
140 js,
141 &::wasm_bindgen::JsValue::from("__getClassname"),
142 )
143 .or(Err(no_get_classname_msg.to_string()))?;
144
145 if get_classname.is_undefined() {
146 return Err(no_get_classname_msg.to_string());
147 }
148
149 let get_classname = get_classname
150 .dyn_into::<::js_sys::Function>()
151 .map_err(|err| format!("__getClassname is not a function, {:?}", err))?;
152
153 let object_classname: String = ::js_sys::Reflect::apply(
154 &get_classname,
155 js,
156 &::js_sys::Array::new(),
157 )
158 .ok()
159 .and_then(|v| v.as_string())
160 .ok_or_else(|| "Failed to get classname".to_owned())?;
161
162 if object_classname.as_str() == classname {
163 let ptr = ::js_sys::Reflect::get(js, &::wasm_bindgen::JsValue::from_str("__wbg_ptr"))
166 .map_err(|err| format!("{:?}", err))?;
167 let ptr_u32: u32 = ptr.as_f64().ok_or(::wasm_bindgen::JsValue::NULL)
168 .map_err(|err| format!("{:?}", err))?
169 as u32;
170 let instance_ref = unsafe { #name::ref_from_abi(ptr_u32) };
171 Ok(instance_ref.clone())
172 } else {
173 Err(format!("Cannot convert {} to {}", object_classname, classname))
174 }
175 }
176 }
177 };
178
179 TokenStream::from(expanded)
180}