1 | use crate::ast; |
2 | use crate::encode; |
3 | use crate::Diagnostic; |
4 | use once_cell::sync::Lazy; |
5 | use proc_macro2::{Ident, Literal, Span, TokenStream}; |
6 | use quote::format_ident; |
7 | use quote::quote_spanned; |
8 | use quote::{quote, ToTokens}; |
9 | use std::collections::{HashMap, HashSet}; |
10 | use std::sync::Mutex; |
11 | use syn::spanned::Spanned; |
12 | use wasm_bindgen_shared as shared; |
13 | |
14 | /// A trait for converting AST structs into Tokens and adding them to a TokenStream, |
15 | /// or providing a diagnostic if conversion fails. |
16 | pub trait TryToTokens { |
17 | /// Attempt to convert a `Self` into tokens and add it to the `TokenStream` |
18 | fn try_to_tokens(&self, tokens: &mut TokenStream) -> Result<(), Diagnostic>; |
19 | |
20 | /// Attempt to convert a `Self` into a new `TokenStream` |
21 | fn try_to_token_stream(&self) -> Result<TokenStream, Diagnostic> { |
22 | let mut tokens: TokenStream = TokenStream::new(); |
23 | self.try_to_tokens(&mut tokens)?; |
24 | Ok(tokens) |
25 | } |
26 | } |
27 | |
28 | impl TryToTokens for ast::Program { |
29 | // Generate wrappers for all the items that we've found |
30 | fn try_to_tokens(&self, tokens: &mut TokenStream) -> Result<(), Diagnostic> { |
31 | let mut errors = Vec::new(); |
32 | for export in self.exports.iter() { |
33 | if let Err(e) = export.try_to_tokens(tokens) { |
34 | errors.push(e); |
35 | } |
36 | } |
37 | for s in self.structs.iter() { |
38 | s.to_tokens(tokens); |
39 | } |
40 | let mut types = HashMap::new(); |
41 | for i in self.imports.iter() { |
42 | if let ast::ImportKind::Type(t) = &i.kind { |
43 | types.insert(t.rust_name.to_string(), t.rust_name.clone()); |
44 | } |
45 | } |
46 | for i in self.imports.iter() { |
47 | DescribeImport { |
48 | kind: &i.kind, |
49 | wasm_bindgen: &self.wasm_bindgen, |
50 | } |
51 | .to_tokens(tokens); |
52 | |
53 | // If there is a js namespace, check that name isn't a type. If it is, |
54 | // this import might be a method on that type. |
55 | if let Some(nss) = &i.js_namespace { |
56 | // When the namespace is `A.B`, the type name should be `B`. |
57 | if let Some(ns) = nss.last().and_then(|t| types.get(t)) { |
58 | if i.kind.fits_on_impl() { |
59 | let kind = match i.kind.try_to_token_stream() { |
60 | Ok(kind) => kind, |
61 | Err(e) => { |
62 | errors.push(e); |
63 | continue; |
64 | } |
65 | }; |
66 | (quote! { |
67 | #[automatically_derived] |
68 | impl #ns { #kind } |
69 | }) |
70 | .to_tokens(tokens); |
71 | continue; |
72 | } |
73 | } |
74 | } |
75 | |
76 | if let Err(e) = i.kind.try_to_tokens(tokens) { |
77 | errors.push(e); |
78 | } |
79 | } |
80 | for e in self.enums.iter() { |
81 | e.to_tokens(tokens); |
82 | } |
83 | |
84 | Diagnostic::from_vec(errors)?; |
85 | |
86 | // Generate a static which will eventually be what lives in a custom section |
87 | // of the wasm executable. For now it's just a plain old static, but we'll |
88 | // eventually have it actually in its own section. |
89 | |
90 | // See comments in `crates/cli-support/src/lib.rs` about what this |
91 | // `schema_version` is. |
92 | let prefix_json = format!( |
93 | r#" {{"schema_version":" {}","version":" {}" }}"# , |
94 | shared::SCHEMA_VERSION, |
95 | shared::version() |
96 | ); |
97 | let encoded = encode::encode(self)?; |
98 | let len = prefix_json.len() as u32; |
99 | let bytes = [ |
100 | &len.to_le_bytes()[..], |
101 | prefix_json.as_bytes(), |
102 | &encoded.custom_section, |
103 | ] |
104 | .concat(); |
105 | |
106 | let generated_static_length = bytes.len(); |
107 | let generated_static_value = syn::LitByteStr::new(&bytes, Span::call_site()); |
108 | |
109 | // We already consumed the contents of included files when generating |
110 | // the custom section, but we want to make sure that updates to the |
111 | // generated files will cause this macro to rerun incrementally. To do |
112 | // that we use `include_str!` to force rustc to think it has a |
113 | // dependency on these files. That way when the file changes Cargo will |
114 | // automatically rerun rustc which will rerun this macro. Other than |
115 | // this we don't actually need the results of the `include_str!`, so |
116 | // it's just shoved into an anonymous static. |
117 | let file_dependencies = encoded.included_files.iter().map(|file| { |
118 | let file = file.to_str().unwrap(); |
119 | quote! { include_str!(#file) } |
120 | }); |
121 | |
122 | (quote! { |
123 | #[cfg(target_arch = "wasm32" )] |
124 | #[automatically_derived] |
125 | const _: () = { |
126 | static _INCLUDED_FILES: &[&str] = &[#(#file_dependencies),*]; |
127 | |
128 | #[link_section = "__wasm_bindgen_unstable" ] |
129 | pub static _GENERATED: [u8; #generated_static_length] = |
130 | *#generated_static_value; |
131 | }; |
132 | }) |
133 | .to_tokens(tokens); |
134 | |
135 | Ok(()) |
136 | } |
137 | } |
138 | |
139 | impl TryToTokens for ast::LinkToModule { |
140 | fn try_to_tokens(&self, tokens: &mut TokenStream) -> Result<(), Diagnostic> { |
141 | let mut program: TokenStream = TokenStream::new(); |
142 | self.0.try_to_tokens(&mut program)?; |
143 | let link_function_name: String = self.0.link_function_name(idx:0); |
144 | let name: Ident = Ident::new(&link_function_name, Span::call_site()); |
145 | let wasm_bindgen: &Path = &self.0.wasm_bindgen; |
146 | let abi_ret: TokenStream = quote! { #wasm_bindgen::convert::WasmRet<<std::string::String as #wasm_bindgen::convert::FromWasmAbi>::Abi> }; |
147 | let extern_fn: TokenStream = extern_fn(&name, &[], &[], &[], abi_ret); |
148 | (quote! { |
149 | { |
150 | #program |
151 | #extern_fn |
152 | |
153 | unsafe { |
154 | <std::string::String as #wasm_bindgen::convert::FromWasmAbi>::from_abi(#name().join()) |
155 | } |
156 | } |
157 | }) |
158 | .to_tokens(tokens); |
159 | Ok(()) |
160 | } |
161 | } |
162 | |
163 | impl ToTokens for ast::Struct { |
164 | fn to_tokens(&self, tokens: &mut TokenStream) { |
165 | let name = &self.rust_name; |
166 | let name_str = self.js_name.to_string(); |
167 | let name_len = name_str.len() as u32; |
168 | let name_chars: Vec<u32> = name_str.chars().map(|c| c as u32).collect(); |
169 | let new_fn = Ident::new(&shared::new_function(&name_str), Span::call_site()); |
170 | let free_fn = Ident::new(&shared::free_function(&name_str), Span::call_site()); |
171 | let unwrap_fn = Ident::new(&shared::unwrap_function(&name_str), Span::call_site()); |
172 | let wasm_bindgen = &self.wasm_bindgen; |
173 | (quote! { |
174 | #[automatically_derived] |
175 | impl #wasm_bindgen::describe::WasmDescribe for #name { |
176 | fn describe() { |
177 | use #wasm_bindgen::__wbindgen_if_not_std; |
178 | __wbindgen_if_not_std! { |
179 | compile_error! { |
180 | "exporting a class to JS requires the `std` feature to \ |
181 | be enabled in the `wasm-bindgen` crate" |
182 | } |
183 | } |
184 | use #wasm_bindgen::describe::*; |
185 | inform(RUST_STRUCT); |
186 | inform(#name_len); |
187 | #(inform(#name_chars);)* |
188 | } |
189 | } |
190 | |
191 | #[automatically_derived] |
192 | impl #wasm_bindgen::convert::IntoWasmAbi for #name { |
193 | type Abi = u32; |
194 | |
195 | fn into_abi(self) -> u32 { |
196 | use #wasm_bindgen::__rt::std::boxed::Box; |
197 | use #wasm_bindgen::__rt::WasmRefCell; |
198 | Box::into_raw(Box::new(WasmRefCell::new(self))) as u32 |
199 | } |
200 | } |
201 | |
202 | #[automatically_derived] |
203 | impl #wasm_bindgen::convert::FromWasmAbi for #name { |
204 | type Abi = u32; |
205 | |
206 | unsafe fn from_abi(js: u32) -> Self { |
207 | use #wasm_bindgen::__rt::std::boxed::Box; |
208 | use #wasm_bindgen::__rt::{assert_not_null, WasmRefCell}; |
209 | |
210 | let ptr = js as *mut WasmRefCell<#name>; |
211 | assert_not_null(ptr); |
212 | let js = Box::from_raw(ptr); |
213 | (*js).borrow_mut(); // make sure no one's borrowing |
214 | js.into_inner() |
215 | } |
216 | } |
217 | |
218 | #[automatically_derived] |
219 | impl #wasm_bindgen::__rt::core::convert::From<#name> for |
220 | #wasm_bindgen::JsValue |
221 | { |
222 | fn from(value: #name) -> Self { |
223 | let ptr = #wasm_bindgen::convert::IntoWasmAbi::into_abi(value); |
224 | |
225 | #[link(wasm_import_module = "__wbindgen_placeholder__" )] |
226 | #[cfg(all(target_arch = "wasm32" , not(any(target_os = "emscripten" , target_os = "wasi" ))))] |
227 | extern "C" { |
228 | fn #new_fn(ptr: u32) -> u32; |
229 | } |
230 | |
231 | #[cfg(not(all(target_arch = "wasm32" , not(any(target_os = "emscripten" , target_os = "wasi" )))))] |
232 | unsafe fn #new_fn(_: u32) -> u32 { |
233 | panic!("cannot convert to JsValue outside of the wasm target" ) |
234 | } |
235 | |
236 | unsafe { |
237 | <#wasm_bindgen::JsValue as #wasm_bindgen::convert::FromWasmAbi> |
238 | ::from_abi(#new_fn(ptr)) |
239 | } |
240 | } |
241 | } |
242 | |
243 | #[cfg(all(target_arch = "wasm32" , not(any(target_os = "emscripten" , target_os = "wasi" ))))] |
244 | #[automatically_derived] |
245 | const _: () = { |
246 | #[no_mangle] |
247 | #[doc(hidden)] |
248 | pub unsafe extern "C" fn #free_fn(ptr: u32) { |
249 | let _ = <#name as #wasm_bindgen::convert::FromWasmAbi>::from_abi(ptr); //implicit `drop()` |
250 | } |
251 | }; |
252 | |
253 | #[automatically_derived] |
254 | impl #wasm_bindgen::convert::RefFromWasmAbi for #name { |
255 | type Abi = u32; |
256 | type Anchor = #wasm_bindgen::__rt::Ref<'static, #name>; |
257 | |
258 | unsafe fn ref_from_abi(js: Self::Abi) -> Self::Anchor { |
259 | let js = js as *mut #wasm_bindgen::__rt::WasmRefCell<#name>; |
260 | #wasm_bindgen::__rt::assert_not_null(js); |
261 | (*js).borrow() |
262 | } |
263 | } |
264 | |
265 | #[automatically_derived] |
266 | impl #wasm_bindgen::convert::RefMutFromWasmAbi for #name { |
267 | type Abi = u32; |
268 | type Anchor = #wasm_bindgen::__rt::RefMut<'static, #name>; |
269 | |
270 | unsafe fn ref_mut_from_abi(js: Self::Abi) -> Self::Anchor { |
271 | let js = js as *mut #wasm_bindgen::__rt::WasmRefCell<#name>; |
272 | #wasm_bindgen::__rt::assert_not_null(js); |
273 | (*js).borrow_mut() |
274 | } |
275 | } |
276 | |
277 | #[automatically_derived] |
278 | impl #wasm_bindgen::convert::LongRefFromWasmAbi for #name { |
279 | type Abi = u32; |
280 | type Anchor = #wasm_bindgen::__rt::Ref<'static, #name>; |
281 | |
282 | unsafe fn long_ref_from_abi(js: Self::Abi) -> Self::Anchor { |
283 | <Self as #wasm_bindgen::convert::RefFromWasmAbi>::ref_from_abi(js) |
284 | } |
285 | } |
286 | |
287 | #[automatically_derived] |
288 | impl #wasm_bindgen::convert::OptionIntoWasmAbi for #name { |
289 | #[inline] |
290 | fn none() -> Self::Abi { 0 } |
291 | } |
292 | |
293 | #[automatically_derived] |
294 | impl #wasm_bindgen::convert::OptionFromWasmAbi for #name { |
295 | #[inline] |
296 | fn is_none(abi: &Self::Abi) -> bool { *abi == 0 } |
297 | } |
298 | |
299 | #[allow(clippy::all)] |
300 | impl #wasm_bindgen::convert::TryFromJsValue for #name { |
301 | type Error = #wasm_bindgen::JsValue; |
302 | |
303 | fn try_from_js_value(value: #wasm_bindgen::JsValue) |
304 | -> #wasm_bindgen::__rt::std::result::Result<Self, Self::Error> { |
305 | let idx = #wasm_bindgen::convert::IntoWasmAbi::into_abi(&value); |
306 | |
307 | #[link(wasm_import_module = "__wbindgen_placeholder__" )] |
308 | #[cfg(all(target_arch = "wasm32" , not(any(target_os = "emscripten" , target_os = "wasi" ))))] |
309 | extern "C" { |
310 | fn #unwrap_fn(ptr: u32) -> u32; |
311 | } |
312 | |
313 | #[cfg(not(all(target_arch = "wasm32" , not(any(target_os = "emscripten" , target_os = "wasi" )))))] |
314 | unsafe fn #unwrap_fn(_: u32) -> u32 { |
315 | panic!("cannot convert from JsValue outside of the wasm target" ) |
316 | } |
317 | |
318 | let ptr = unsafe { #unwrap_fn(idx) }; |
319 | if ptr == 0 { |
320 | #wasm_bindgen::__rt::std::result::Result::Err(value) |
321 | } else { |
322 | // Don't run `JsValue`'s destructor, `unwrap_fn` already did that for us. |
323 | #wasm_bindgen::__rt::std::mem::forget(value); |
324 | unsafe { |
325 | #wasm_bindgen::__rt::std::result::Result::Ok( |
326 | <Self as #wasm_bindgen::convert::FromWasmAbi>::from_abi(ptr) |
327 | ) |
328 | } |
329 | } |
330 | } |
331 | } |
332 | |
333 | impl #wasm_bindgen::describe::WasmDescribeVector for #name { |
334 | fn describe_vector() { |
335 | use #wasm_bindgen::describe::*; |
336 | inform(VECTOR); |
337 | inform(NAMED_EXTERNREF); |
338 | inform(#name_len); |
339 | #(inform(#name_chars);)* |
340 | } |
341 | } |
342 | |
343 | impl #wasm_bindgen::convert::VectorIntoWasmAbi for #name { |
344 | type Abi = < |
345 | #wasm_bindgen::__rt::std::boxed::Box<[#wasm_bindgen::JsValue]> |
346 | as #wasm_bindgen::convert::IntoWasmAbi |
347 | >::Abi; |
348 | |
349 | fn vector_into_abi( |
350 | vector: #wasm_bindgen::__rt::std::boxed::Box<[#name]> |
351 | ) -> Self::Abi { |
352 | #wasm_bindgen::convert::js_value_vector_into_abi(vector) |
353 | } |
354 | } |
355 | |
356 | impl #wasm_bindgen::convert::VectorFromWasmAbi for #name { |
357 | type Abi = < |
358 | #wasm_bindgen::__rt::std::boxed::Box<[#wasm_bindgen::JsValue]> |
359 | as #wasm_bindgen::convert::FromWasmAbi |
360 | >::Abi; |
361 | |
362 | unsafe fn vector_from_abi( |
363 | js: Self::Abi |
364 | ) -> #wasm_bindgen::__rt::std::boxed::Box<[#name]> { |
365 | #wasm_bindgen::convert::js_value_vector_from_abi(js) |
366 | } |
367 | } |
368 | }) |
369 | .to_tokens(tokens); |
370 | |
371 | for field in self.fields.iter() { |
372 | field.to_tokens(tokens); |
373 | } |
374 | } |
375 | } |
376 | |
377 | impl ToTokens for ast::StructField { |
378 | fn to_tokens(&self, tokens: &mut TokenStream) { |
379 | let rust_name = &self.rust_name; |
380 | let struct_name = &self.struct_name; |
381 | let ty = &self.ty; |
382 | let getter = &self.getter; |
383 | let setter = &self.setter; |
384 | |
385 | let maybe_assert_copy = if self.getter_with_clone.is_some() { |
386 | quote! {} |
387 | } else { |
388 | quote! { assert_copy::<#ty>() } |
389 | }; |
390 | let maybe_assert_copy = respan(maybe_assert_copy, ty); |
391 | |
392 | // Split this out so that it isn't affected by `quote_spanned!`. |
393 | // |
394 | // If we don't do this, it might end up being unable to reference `js` |
395 | // properly because it doesn't have the same span. |
396 | // |
397 | // See https://github.com/rustwasm/wasm-bindgen/pull/3725. |
398 | let js_token = quote! { js }; |
399 | let mut val = quote_spanned!(self.rust_name.span()=> (*#js_token).borrow().#rust_name); |
400 | if let Some(span) = self.getter_with_clone { |
401 | val = quote_spanned!(span=> <#ty as Clone>::clone(&#val) ); |
402 | } |
403 | |
404 | let wasm_bindgen = &self.wasm_bindgen; |
405 | |
406 | (quote! { |
407 | #[automatically_derived] |
408 | const _: () = { |
409 | #[cfg_attr(all(target_arch = "wasm32" , not(any(target_os = "emscripten" , target_os = "wasi" ))), no_mangle)] |
410 | #[doc(hidden)] |
411 | pub unsafe extern "C" fn #getter(js: u32) |
412 | -> #wasm_bindgen::convert::WasmRet<<#ty as #wasm_bindgen::convert::IntoWasmAbi>::Abi> |
413 | { |
414 | use #wasm_bindgen::__rt::{WasmRefCell, assert_not_null}; |
415 | use #wasm_bindgen::convert::IntoWasmAbi; |
416 | |
417 | fn assert_copy<T: Copy>(){} |
418 | #maybe_assert_copy; |
419 | |
420 | let js = js as *mut WasmRefCell<#struct_name>; |
421 | assert_not_null(js); |
422 | let val = #val; |
423 | <#ty as IntoWasmAbi>::into_abi(val).into() |
424 | } |
425 | }; |
426 | }) |
427 | .to_tokens(tokens); |
428 | |
429 | Descriptor { |
430 | ident: getter, |
431 | inner: quote! { |
432 | <#ty as WasmDescribe>::describe(); |
433 | }, |
434 | attrs: vec![], |
435 | wasm_bindgen: &self.wasm_bindgen, |
436 | } |
437 | .to_tokens(tokens); |
438 | |
439 | if self.readonly { |
440 | return; |
441 | } |
442 | |
443 | let abi = quote! { <#ty as #wasm_bindgen::convert::FromWasmAbi>::Abi }; |
444 | let (args, names) = splat(wasm_bindgen, &Ident::new("val" , rust_name.span()), &abi); |
445 | |
446 | (quote! { |
447 | #[cfg(all(target_arch = "wasm32" , not(any(target_os = "emscripten" , target_os = "wasi" ))))] |
448 | #[automatically_derived] |
449 | const _: () = { |
450 | #[no_mangle] |
451 | #[doc(hidden)] |
452 | pub unsafe extern "C" fn #setter( |
453 | js: u32, |
454 | #(#args,)* |
455 | ) { |
456 | use #wasm_bindgen::__rt::{WasmRefCell, assert_not_null}; |
457 | use #wasm_bindgen::convert::FromWasmAbi; |
458 | |
459 | let js = js as *mut WasmRefCell<#struct_name>; |
460 | assert_not_null(js); |
461 | let val = <#abi as #wasm_bindgen::convert::WasmAbi>::join(#(#names),*); |
462 | let val = <#ty as FromWasmAbi>::from_abi(val); |
463 | (*js).borrow_mut().#rust_name = val; |
464 | } |
465 | }; |
466 | }) |
467 | .to_tokens(tokens); |
468 | } |
469 | } |
470 | |
471 | impl TryToTokens for ast::Export { |
472 | fn try_to_tokens(self: &ast::Export, into: &mut TokenStream) -> Result<(), Diagnostic> { |
473 | let generated_name = self.rust_symbol(); |
474 | let export_name = self.export_name(); |
475 | let mut args = vec![]; |
476 | let mut arg_conversions = vec![]; |
477 | let mut converted_arguments = vec![]; |
478 | let ret = Ident::new("_ret" , Span::call_site()); |
479 | |
480 | let offset = if self.method_self.is_some() { |
481 | args.push(quote! { me: u32 }); |
482 | 1 |
483 | } else { |
484 | 0 |
485 | }; |
486 | |
487 | let name = &self.rust_name; |
488 | let wasm_bindgen = &self.wasm_bindgen; |
489 | let wasm_bindgen_futures = &self.wasm_bindgen_futures; |
490 | let receiver = match self.method_self { |
491 | Some(ast::MethodSelf::ByValue) => { |
492 | let class = self.rust_class.as_ref().unwrap(); |
493 | arg_conversions.push(quote! { |
494 | let me = unsafe { |
495 | <#class as #wasm_bindgen::convert::FromWasmAbi>::from_abi(me) |
496 | }; |
497 | }); |
498 | quote! { me.#name } |
499 | } |
500 | Some(ast::MethodSelf::RefMutable) => { |
501 | let class = self.rust_class.as_ref().unwrap(); |
502 | arg_conversions.push(quote! { |
503 | let mut me = unsafe { |
504 | <#class as #wasm_bindgen::convert::RefMutFromWasmAbi> |
505 | ::ref_mut_from_abi(me) |
506 | }; |
507 | let me = &mut *me; |
508 | }); |
509 | quote! { me.#name } |
510 | } |
511 | Some(ast::MethodSelf::RefShared) => { |
512 | let class = self.rust_class.as_ref().unwrap(); |
513 | arg_conversions.push(quote! { |
514 | let me = unsafe { |
515 | <#class as #wasm_bindgen::convert::RefFromWasmAbi> |
516 | ::ref_from_abi(me) |
517 | }; |
518 | let me = &*me; |
519 | }); |
520 | quote! { me.#name } |
521 | } |
522 | None => match &self.rust_class { |
523 | Some(class) => quote! { #class::#name }, |
524 | None => quote! { #name }, |
525 | }, |
526 | }; |
527 | |
528 | let mut argtys = Vec::new(); |
529 | for (i, arg) in self.function.arguments.iter().enumerate() { |
530 | argtys.push(&*arg.ty); |
531 | let i = i + offset; |
532 | let ident = Ident::new(&format!("arg {}" , i), Span::call_site()); |
533 | fn unwrap_nested_types(ty: &syn::Type) -> &syn::Type { |
534 | match &ty { |
535 | syn::Type::Group(syn::TypeGroup { ref elem, .. }) => unwrap_nested_types(elem), |
536 | syn::Type::Paren(syn::TypeParen { ref elem, .. }) => unwrap_nested_types(elem), |
537 | _ => ty, |
538 | } |
539 | } |
540 | let ty = unwrap_nested_types(&arg.ty); |
541 | |
542 | match &ty { |
543 | syn::Type::Reference(syn::TypeReference { |
544 | mutability: Some(_), |
545 | elem, |
546 | .. |
547 | }) => { |
548 | let abi = quote! { <#elem as #wasm_bindgen::convert::RefMutFromWasmAbi>::Abi }; |
549 | let (prim_args, prim_names) = splat(wasm_bindgen, &ident, &abi); |
550 | args.extend(prim_args); |
551 | arg_conversions.push(quote! { |
552 | let mut #ident = unsafe { |
553 | <#elem as #wasm_bindgen::convert::RefMutFromWasmAbi> |
554 | ::ref_mut_from_abi( |
555 | <#abi as #wasm_bindgen::convert::WasmAbi>::join(#(#prim_names),*) |
556 | ) |
557 | }; |
558 | let #ident = &mut *#ident; |
559 | }); |
560 | } |
561 | syn::Type::Reference(syn::TypeReference { elem, .. }) => { |
562 | if self.function.r#async { |
563 | let abi = |
564 | quote! { <#elem as #wasm_bindgen::convert::LongRefFromWasmAbi>::Abi }; |
565 | let (prim_args, prim_names) = splat(wasm_bindgen, &ident, &abi); |
566 | args.extend(prim_args); |
567 | arg_conversions.push(quote! { |
568 | let #ident = unsafe { |
569 | <#elem as #wasm_bindgen::convert::LongRefFromWasmAbi> |
570 | ::long_ref_from_abi( |
571 | <#abi as #wasm_bindgen::convert::WasmAbi>::join(#(#prim_names),*) |
572 | ) |
573 | }; |
574 | let #ident = <<#elem as #wasm_bindgen::convert::LongRefFromWasmAbi> |
575 | ::Anchor as core::borrow::Borrow<#elem>> |
576 | ::borrow(&#ident); |
577 | }); |
578 | } else { |
579 | let abi = quote! { <#elem as #wasm_bindgen::convert::RefFromWasmAbi>::Abi }; |
580 | let (prim_args, prim_names) = splat(wasm_bindgen, &ident, &abi); |
581 | args.extend(prim_args); |
582 | arg_conversions.push(quote! { |
583 | let #ident = unsafe { |
584 | <#elem as #wasm_bindgen::convert::RefFromWasmAbi> |
585 | ::ref_from_abi( |
586 | <#abi as #wasm_bindgen::convert::WasmAbi>::join(#(#prim_names),*) |
587 | ) |
588 | }; |
589 | let #ident = &*#ident; |
590 | }); |
591 | } |
592 | } |
593 | _ => { |
594 | let abi = quote! { <#ty as #wasm_bindgen::convert::FromWasmAbi>::Abi }; |
595 | let (prim_args, prim_names) = splat(wasm_bindgen, &ident, &abi); |
596 | args.extend(prim_args); |
597 | arg_conversions.push(quote! { |
598 | let #ident = unsafe { |
599 | <#ty as #wasm_bindgen::convert::FromWasmAbi> |
600 | ::from_abi( |
601 | <#abi as #wasm_bindgen::convert::WasmAbi>::join(#(#prim_names),*) |
602 | ) |
603 | }; |
604 | }); |
605 | } |
606 | } |
607 | converted_arguments.push(quote! { #ident }); |
608 | } |
609 | let syn_unit = syn::Type::Tuple(syn::TypeTuple { |
610 | elems: Default::default(), |
611 | paren_token: Default::default(), |
612 | }); |
613 | let syn_ret = self.function.ret.as_ref().unwrap_or(&syn_unit); |
614 | if let syn::Type::Reference(_) = syn_ret { |
615 | bail_span!(syn_ret, "cannot return a borrowed ref with #[wasm_bindgen]" ,) |
616 | } |
617 | |
618 | // For an `async` function we always run it through `future_to_promise` |
619 | // since we're returning a promise to JS, and this will implicitly |
620 | // require that the function returns a `Future<Output = Result<...>>` |
621 | let (ret_ty, inner_ret_ty, ret_expr) = if self.function.r#async { |
622 | if self.start { |
623 | ( |
624 | quote! { () }, |
625 | quote! { () }, |
626 | quote! { |
627 | <#syn_ret as #wasm_bindgen::__rt::Start>::start(#ret.await) |
628 | }, |
629 | ) |
630 | } else { |
631 | ( |
632 | quote! { #wasm_bindgen::JsValue }, |
633 | quote! { #syn_ret }, |
634 | quote! { |
635 | <#syn_ret as #wasm_bindgen::__rt::IntoJsResult>::into_js_result(#ret.await) |
636 | }, |
637 | ) |
638 | } |
639 | } else if self.start { |
640 | ( |
641 | quote! { () }, |
642 | quote! { () }, |
643 | quote! { <#syn_ret as #wasm_bindgen::__rt::Start>::start(#ret) }, |
644 | ) |
645 | } else { |
646 | (quote! { #syn_ret }, quote! { #syn_ret }, quote! { #ret }) |
647 | }; |
648 | |
649 | let mut call = quote! { |
650 | { |
651 | #(#arg_conversions)* |
652 | let #ret = #receiver(#(#converted_arguments),*); |
653 | #ret_expr |
654 | } |
655 | }; |
656 | |
657 | if self.function.r#async { |
658 | if self.start { |
659 | call = quote! { |
660 | #wasm_bindgen_futures::spawn_local(async move { |
661 | #call |
662 | }) |
663 | } |
664 | } else { |
665 | call = quote! { |
666 | #wasm_bindgen_futures::future_to_promise(async move { |
667 | #call |
668 | }).into() |
669 | } |
670 | } |
671 | } |
672 | |
673 | let projection = quote! { <#ret_ty as #wasm_bindgen::convert::ReturnWasmAbi> }; |
674 | let convert_ret = quote! { #projection::return_abi(#ret).into() }; |
675 | let describe_ret = quote! { |
676 | <#ret_ty as WasmDescribe>::describe(); |
677 | <#inner_ret_ty as WasmDescribe>::describe(); |
678 | }; |
679 | let nargs = self.function.arguments.len() as u32; |
680 | let attrs = &self.function.rust_attrs; |
681 | |
682 | let start_check = if self.start { |
683 | quote! { const _ASSERT: fn() = || -> #projection::Abi { loop {} }; } |
684 | } else { |
685 | quote! {} |
686 | }; |
687 | |
688 | (quote! { |
689 | #[automatically_derived] |
690 | const _: () = { |
691 | #(#attrs)* |
692 | #[cfg_attr( |
693 | all(target_arch = "wasm32" , not(any(target_os = "emscripten" , target_os = "wasi" ))), |
694 | export_name = #export_name, |
695 | )] |
696 | pub unsafe extern "C" fn #generated_name(#(#args),*) -> #wasm_bindgen::convert::WasmRet<#projection::Abi> { |
697 | #start_check |
698 | |
699 | let #ret = #call; |
700 | #convert_ret |
701 | } |
702 | }; |
703 | }) |
704 | .to_tokens(into); |
705 | |
706 | let describe_args: TokenStream = argtys |
707 | .iter() |
708 | .map(|ty| match ty { |
709 | syn::Type::Reference(reference) |
710 | if self.function.r#async && reference.mutability.is_none() => |
711 | { |
712 | let inner = &reference.elem; |
713 | quote! { |
714 | inform(LONGREF); |
715 | <#inner as WasmDescribe>::describe(); |
716 | } |
717 | } |
718 | _ => quote! { <#ty as WasmDescribe>::describe(); }, |
719 | }) |
720 | .collect(); |
721 | |
722 | // In addition to generating the shim function above which is what |
723 | // our generated JS will invoke, we *also* generate a "descriptor" |
724 | // shim. This descriptor shim uses the `WasmDescribe` trait to |
725 | // programmatically describe the type signature of the generated |
726 | // shim above. This in turn is then used to inform the |
727 | // `wasm-bindgen` CLI tool exactly what types and such it should be |
728 | // using in JS. |
729 | // |
730 | // Note that this descriptor function is a purely an internal detail |
731 | // of `#[wasm_bindgen]` and isn't intended to be exported to anyone |
732 | // or actually part of the final was binary. Additionally, this is |
733 | // literally executed when the `wasm-bindgen` tool executes. |
734 | // |
735 | // In any case, there's complications in `wasm-bindgen` to handle |
736 | // this, but the tl;dr; is that this is stripped from the final wasm |
737 | // binary along with anything it references. |
738 | let export = Ident::new(&export_name, Span::call_site()); |
739 | Descriptor { |
740 | ident: &export, |
741 | inner: quote! { |
742 | inform(FUNCTION); |
743 | inform(0); |
744 | inform(#nargs); |
745 | #describe_args |
746 | #describe_ret |
747 | }, |
748 | attrs: attrs.clone(), |
749 | wasm_bindgen: &self.wasm_bindgen, |
750 | } |
751 | .to_tokens(into); |
752 | |
753 | Ok(()) |
754 | } |
755 | } |
756 | |
757 | impl TryToTokens for ast::ImportKind { |
758 | fn try_to_tokens(&self, tokens: &mut TokenStream) -> Result<(), Diagnostic> { |
759 | match *self { |
760 | ast::ImportKind::Function(ref f: &ImportFunction) => f.try_to_tokens(tokens)?, |
761 | ast::ImportKind::Static(ref s: &ImportStatic) => s.to_tokens(tokens), |
762 | ast::ImportKind::Type(ref t: &ImportType) => t.to_tokens(tokens), |
763 | ast::ImportKind::Enum(ref e: &ImportEnum) => e.to_tokens(tokens), |
764 | } |
765 | |
766 | Ok(()) |
767 | } |
768 | } |
769 | |
770 | impl ToTokens for ast::ImportType { |
771 | fn to_tokens(&self, tokens: &mut TokenStream) { |
772 | let vis = &self.vis; |
773 | let rust_name = &self.rust_name; |
774 | let attrs = &self.attrs; |
775 | let doc_comment = match &self.doc_comment { |
776 | None => "" , |
777 | Some(comment) => comment, |
778 | }; |
779 | let instanceof_shim = Ident::new(&self.instanceof_shim, Span::call_site()); |
780 | |
781 | let wasm_bindgen = &self.wasm_bindgen; |
782 | let internal_obj = match self.extends.first() { |
783 | Some(target) => { |
784 | quote! { #target } |
785 | } |
786 | None => { |
787 | quote! { #wasm_bindgen::JsValue } |
788 | } |
789 | }; |
790 | |
791 | let description = if let Some(typescript_type) = &self.typescript_type { |
792 | let typescript_type_len = typescript_type.len() as u32; |
793 | let typescript_type_chars = typescript_type.chars().map(|c| c as u32); |
794 | quote! { |
795 | use #wasm_bindgen::describe::*; |
796 | inform(NAMED_EXTERNREF); |
797 | inform(#typescript_type_len); |
798 | #(inform(#typescript_type_chars);)* |
799 | } |
800 | } else { |
801 | quote! { |
802 | JsValue::describe() |
803 | } |
804 | }; |
805 | |
806 | let is_type_of = self.is_type_of.as_ref().map(|is_type_of| { |
807 | quote! { |
808 | #[inline] |
809 | fn is_type_of(val: &JsValue) -> bool { |
810 | let is_type_of: fn(&JsValue) -> bool = #is_type_of; |
811 | is_type_of(val) |
812 | } |
813 | } |
814 | }); |
815 | |
816 | let no_deref = self.no_deref; |
817 | |
818 | (quote! { |
819 | #[automatically_derived] |
820 | #(#attrs)* |
821 | #[doc = #doc_comment] |
822 | #[repr(transparent)] |
823 | #vis struct #rust_name { |
824 | obj: #internal_obj |
825 | } |
826 | |
827 | #[automatically_derived] |
828 | const _: () = { |
829 | use #wasm_bindgen::convert::TryFromJsValue; |
830 | use #wasm_bindgen::convert::{IntoWasmAbi, FromWasmAbi}; |
831 | use #wasm_bindgen::convert::{OptionIntoWasmAbi, OptionFromWasmAbi}; |
832 | use #wasm_bindgen::convert::{RefFromWasmAbi, LongRefFromWasmAbi}; |
833 | use #wasm_bindgen::describe::WasmDescribe; |
834 | use #wasm_bindgen::{JsValue, JsCast, JsObject}; |
835 | use #wasm_bindgen::__rt::core; |
836 | |
837 | impl WasmDescribe for #rust_name { |
838 | fn describe() { |
839 | #description |
840 | } |
841 | } |
842 | |
843 | impl IntoWasmAbi for #rust_name { |
844 | type Abi = <JsValue as IntoWasmAbi>::Abi; |
845 | |
846 | #[inline] |
847 | fn into_abi(self) -> Self::Abi { |
848 | self.obj.into_abi() |
849 | } |
850 | } |
851 | |
852 | impl OptionIntoWasmAbi for #rust_name { |
853 | #[inline] |
854 | fn none() -> Self::Abi { |
855 | 0 |
856 | } |
857 | } |
858 | |
859 | impl<'a> OptionIntoWasmAbi for &'a #rust_name { |
860 | #[inline] |
861 | fn none() -> Self::Abi { |
862 | 0 |
863 | } |
864 | } |
865 | |
866 | impl FromWasmAbi for #rust_name { |
867 | type Abi = <JsValue as FromWasmAbi>::Abi; |
868 | |
869 | #[inline] |
870 | unsafe fn from_abi(js: Self::Abi) -> Self { |
871 | #rust_name { |
872 | obj: JsValue::from_abi(js).into(), |
873 | } |
874 | } |
875 | } |
876 | |
877 | impl OptionFromWasmAbi for #rust_name { |
878 | #[inline] |
879 | fn is_none(abi: &Self::Abi) -> bool { *abi == 0 } |
880 | } |
881 | |
882 | impl<'a> IntoWasmAbi for &'a #rust_name { |
883 | type Abi = <&'a JsValue as IntoWasmAbi>::Abi; |
884 | |
885 | #[inline] |
886 | fn into_abi(self) -> Self::Abi { |
887 | (&self.obj).into_abi() |
888 | } |
889 | } |
890 | |
891 | impl RefFromWasmAbi for #rust_name { |
892 | type Abi = <JsValue as RefFromWasmAbi>::Abi; |
893 | type Anchor = core::mem::ManuallyDrop<#rust_name>; |
894 | |
895 | #[inline] |
896 | unsafe fn ref_from_abi(js: Self::Abi) -> Self::Anchor { |
897 | let tmp = <JsValue as RefFromWasmAbi>::ref_from_abi(js); |
898 | core::mem::ManuallyDrop::new(#rust_name { |
899 | obj: core::mem::ManuallyDrop::into_inner(tmp).into(), |
900 | }) |
901 | } |
902 | } |
903 | |
904 | impl LongRefFromWasmAbi for #rust_name { |
905 | type Abi = <JsValue as LongRefFromWasmAbi>::Abi; |
906 | type Anchor = #rust_name; |
907 | |
908 | #[inline] |
909 | unsafe fn long_ref_from_abi(js: Self::Abi) -> Self::Anchor { |
910 | let tmp = <JsValue as LongRefFromWasmAbi>::long_ref_from_abi(js); |
911 | #rust_name { obj: tmp.into() } |
912 | } |
913 | } |
914 | |
915 | // TODO: remove this on the next major version |
916 | impl From<JsValue> for #rust_name { |
917 | #[inline] |
918 | fn from(obj: JsValue) -> #rust_name { |
919 | #rust_name { obj: obj.into() } |
920 | } |
921 | } |
922 | |
923 | impl AsRef<JsValue> for #rust_name { |
924 | #[inline] |
925 | fn as_ref(&self) -> &JsValue { self.obj.as_ref() } |
926 | } |
927 | |
928 | impl AsRef<#rust_name> for #rust_name { |
929 | #[inline] |
930 | fn as_ref(&self) -> &#rust_name { self } |
931 | } |
932 | |
933 | |
934 | impl From<#rust_name> for JsValue { |
935 | #[inline] |
936 | fn from(obj: #rust_name) -> JsValue { |
937 | obj.obj.into() |
938 | } |
939 | } |
940 | |
941 | impl JsCast for #rust_name { |
942 | fn instanceof(val: &JsValue) -> bool { |
943 | #[link(wasm_import_module = "__wbindgen_placeholder__" )] |
944 | #[cfg(all(target_arch = "wasm32" , not(any(target_os = "emscripten" , target_os = "wasi" ))))] |
945 | extern "C" { |
946 | fn #instanceof_shim(val: u32) -> u32; |
947 | } |
948 | #[cfg(not(all(target_arch = "wasm32" , not(any(target_os = "emscripten" , target_os = "wasi" )))))] |
949 | unsafe fn #instanceof_shim(_: u32) -> u32 { |
950 | panic!("cannot check instanceof on non-wasm targets" ); |
951 | } |
952 | unsafe { |
953 | let idx = val.into_abi(); |
954 | #instanceof_shim(idx) != 0 |
955 | } |
956 | } |
957 | |
958 | #is_type_of |
959 | |
960 | #[inline] |
961 | fn unchecked_from_js(val: JsValue) -> Self { |
962 | #rust_name { obj: val.into() } |
963 | } |
964 | |
965 | #[inline] |
966 | fn unchecked_from_js_ref(val: &JsValue) -> &Self { |
967 | // Should be safe because `#rust_name` is a transparent |
968 | // wrapper around `val` |
969 | unsafe { &*(val as *const JsValue as *const #rust_name) } |
970 | } |
971 | } |
972 | |
973 | impl JsObject for #rust_name {} |
974 | }; |
975 | }) |
976 | .to_tokens(tokens); |
977 | |
978 | if !no_deref { |
979 | (quote! { |
980 | #[automatically_derived] |
981 | impl core::ops::Deref for #rust_name { |
982 | type Target = #internal_obj; |
983 | |
984 | #[inline] |
985 | fn deref(&self) -> &#internal_obj { |
986 | &self.obj |
987 | } |
988 | } |
989 | }) |
990 | .to_tokens(tokens); |
991 | } |
992 | |
993 | for superclass in self.extends.iter() { |
994 | (quote! { |
995 | #[automatically_derived] |
996 | impl From<#rust_name> for #superclass { |
997 | #[inline] |
998 | fn from(obj: #rust_name) -> #superclass { |
999 | use #wasm_bindgen::JsCast; |
1000 | #superclass::unchecked_from_js(obj.into()) |
1001 | } |
1002 | } |
1003 | |
1004 | #[automatically_derived] |
1005 | impl AsRef<#superclass> for #rust_name { |
1006 | #[inline] |
1007 | fn as_ref(&self) -> &#superclass { |
1008 | use #wasm_bindgen::JsCast; |
1009 | #superclass::unchecked_from_js_ref(self.as_ref()) |
1010 | } |
1011 | } |
1012 | }) |
1013 | .to_tokens(tokens); |
1014 | } |
1015 | } |
1016 | } |
1017 | |
1018 | impl ToTokens for ast::ImportEnum { |
1019 | fn to_tokens(&self, tokens: &mut TokenStream) { |
1020 | let vis = &self.vis; |
1021 | let name = &self.name; |
1022 | let expect_string = format!("attempted to convert invalid {} into JSValue" , name); |
1023 | let variants = &self.variants; |
1024 | let variant_strings = &self.variant_values; |
1025 | let attrs = &self.rust_attrs; |
1026 | |
1027 | let mut current_idx: usize = 0; |
1028 | let variant_indexes: Vec<Literal> = variants |
1029 | .iter() |
1030 | .map(|_| { |
1031 | let this_index = current_idx; |
1032 | current_idx += 1; |
1033 | Literal::usize_unsuffixed(this_index) |
1034 | }) |
1035 | .collect(); |
1036 | |
1037 | // Borrow variant_indexes because we need to use it multiple times inside the quote! macro |
1038 | let variant_indexes_ref = &variant_indexes; |
1039 | |
1040 | // A vector of EnumName::VariantName tokens for this enum |
1041 | let variant_paths: Vec<TokenStream> = self |
1042 | .variants |
1043 | .iter() |
1044 | .map(|v| quote!(#name::#v).into_token_stream()) |
1045 | .collect(); |
1046 | |
1047 | // Borrow variant_paths because we need to use it multiple times inside the quote! macro |
1048 | let variant_paths_ref = &variant_paths; |
1049 | |
1050 | let wasm_bindgen = &self.wasm_bindgen; |
1051 | |
1052 | (quote! { |
1053 | #(#attrs)* |
1054 | #vis enum #name { |
1055 | #(#variants = #variant_indexes_ref,)* |
1056 | #[automatically_derived] |
1057 | #[doc(hidden)] |
1058 | __Nonexhaustive, |
1059 | } |
1060 | |
1061 | #[automatically_derived] |
1062 | impl #name { |
1063 | fn from_str(s: &str) -> Option<#name> { |
1064 | match s { |
1065 | #(#variant_strings => Some(#variant_paths_ref),)* |
1066 | _ => None, |
1067 | } |
1068 | } |
1069 | |
1070 | fn to_str(&self) -> &'static str { |
1071 | match self { |
1072 | #(#variant_paths_ref => #variant_strings,)* |
1073 | #name::__Nonexhaustive => panic!(#expect_string), |
1074 | } |
1075 | } |
1076 | |
1077 | #vis fn from_js_value(obj: &#wasm_bindgen::JsValue) -> Option<#name> { |
1078 | obj.as_string().and_then(|obj_str| Self::from_str(obj_str.as_str())) |
1079 | } |
1080 | } |
1081 | |
1082 | // It should really be using &str for all of these, but that requires some major changes to cli-support |
1083 | #[automatically_derived] |
1084 | impl #wasm_bindgen::describe::WasmDescribe for #name { |
1085 | fn describe() { |
1086 | <#wasm_bindgen::JsValue as #wasm_bindgen::describe::WasmDescribe>::describe() |
1087 | } |
1088 | } |
1089 | |
1090 | #[automatically_derived] |
1091 | impl #wasm_bindgen::convert::IntoWasmAbi for #name { |
1092 | type Abi = <#wasm_bindgen::JsValue as #wasm_bindgen::convert::IntoWasmAbi>::Abi; |
1093 | |
1094 | #[inline] |
1095 | fn into_abi(self) -> Self::Abi { |
1096 | <#wasm_bindgen::JsValue as #wasm_bindgen::convert::IntoWasmAbi>::into_abi(self.into()) |
1097 | } |
1098 | } |
1099 | |
1100 | #[automatically_derived] |
1101 | impl #wasm_bindgen::convert::FromWasmAbi for #name { |
1102 | type Abi = <#wasm_bindgen::JsValue as #wasm_bindgen::convert::FromWasmAbi>::Abi; |
1103 | |
1104 | unsafe fn from_abi(js: Self::Abi) -> Self { |
1105 | let s = <#wasm_bindgen::JsValue as #wasm_bindgen::convert::FromWasmAbi>::from_abi(js); |
1106 | #name::from_js_value(&s).unwrap_or(#name::__Nonexhaustive) |
1107 | } |
1108 | } |
1109 | |
1110 | #[automatically_derived] |
1111 | impl #wasm_bindgen::convert::OptionIntoWasmAbi for #name { |
1112 | #[inline] |
1113 | fn none() -> Self::Abi { <::js_sys::Object as #wasm_bindgen::convert::OptionIntoWasmAbi>::none() } |
1114 | } |
1115 | |
1116 | #[automatically_derived] |
1117 | impl #wasm_bindgen::convert::OptionFromWasmAbi for #name { |
1118 | #[inline] |
1119 | fn is_none(abi: &Self::Abi) -> bool { <::js_sys::Object as #wasm_bindgen::convert::OptionFromWasmAbi>::is_none(abi) } |
1120 | } |
1121 | |
1122 | #[automatically_derived] |
1123 | impl From<#name> for #wasm_bindgen::JsValue { |
1124 | fn from(obj: #name) -> #wasm_bindgen::JsValue { |
1125 | #wasm_bindgen::JsValue::from(obj.to_str()) |
1126 | } |
1127 | } |
1128 | }).to_tokens(tokens); |
1129 | } |
1130 | } |
1131 | |
1132 | impl TryToTokens for ast::ImportFunction { |
1133 | fn try_to_tokens(&self, tokens: &mut TokenStream) -> Result<(), Diagnostic> { |
1134 | let mut class_ty = None; |
1135 | let mut is_method = false; |
1136 | match self.kind { |
1137 | ast::ImportFunctionKind::Method { |
1138 | ref ty, ref kind, .. |
1139 | } => { |
1140 | if let ast::MethodKind::Operation(ast::Operation { |
1141 | is_static: false, .. |
1142 | }) = kind |
1143 | { |
1144 | is_method = true; |
1145 | } |
1146 | class_ty = Some(ty); |
1147 | } |
1148 | ast::ImportFunctionKind::Normal => {} |
1149 | } |
1150 | let vis = &self.function.rust_vis; |
1151 | let ret = match &self.function.ret { |
1152 | Some(ty) => quote! { -> #ty }, |
1153 | None => quote!(), |
1154 | }; |
1155 | |
1156 | let mut abi_argument_names = Vec::new(); |
1157 | let mut abi_arguments = Vec::new(); |
1158 | let mut arg_conversions = Vec::new(); |
1159 | let mut arguments = Vec::new(); |
1160 | let ret_ident = Ident::new("_ret" , Span::call_site()); |
1161 | let wasm_bindgen = &self.wasm_bindgen; |
1162 | let wasm_bindgen_futures = &self.wasm_bindgen_futures; |
1163 | |
1164 | for (i, arg) in self.function.arguments.iter().enumerate() { |
1165 | let ty = &arg.ty; |
1166 | let name = match &*arg.pat { |
1167 | syn::Pat::Ident(syn::PatIdent { |
1168 | by_ref: None, |
1169 | ident, |
1170 | subpat: None, |
1171 | .. |
1172 | }) => ident.clone(), |
1173 | syn::Pat::Wild(_) => syn::Ident::new(&format!("__genarg_ {}" , i), Span::call_site()), |
1174 | _ => bail_span!( |
1175 | arg.pat, |
1176 | "unsupported pattern in #[wasm_bindgen] imported function" , |
1177 | ), |
1178 | }; |
1179 | |
1180 | let abi = quote! { <#ty as #wasm_bindgen::convert::IntoWasmAbi>::Abi }; |
1181 | let (prim_args, prim_names) = splat(wasm_bindgen, &name, &abi); |
1182 | abi_arguments.extend(prim_args); |
1183 | abi_argument_names.extend(prim_names.iter().cloned()); |
1184 | |
1185 | let var = if i == 0 && is_method { |
1186 | quote! { self } |
1187 | } else { |
1188 | arguments.push(quote! { #name: #ty }); |
1189 | quote! { #name } |
1190 | }; |
1191 | arg_conversions.push(quote! { |
1192 | let #name = <#ty as #wasm_bindgen::convert::IntoWasmAbi> |
1193 | ::into_abi(#var); |
1194 | let (#(#prim_names),*) = <#abi as #wasm_bindgen::convert::WasmAbi>::split(#name); |
1195 | }); |
1196 | } |
1197 | let abi_ret; |
1198 | let mut convert_ret; |
1199 | match &self.js_ret { |
1200 | Some(syn::Type::Reference(_)) => { |
1201 | bail_span!( |
1202 | self.js_ret, |
1203 | "cannot return references in #[wasm_bindgen] imports yet" |
1204 | ); |
1205 | } |
1206 | Some(ref ty) => { |
1207 | if self.function.r#async { |
1208 | abi_ret = quote! { |
1209 | #wasm_bindgen::convert::WasmRet<<#wasm_bindgen_futures::js_sys::Promise as #wasm_bindgen::convert::FromWasmAbi>::Abi> |
1210 | }; |
1211 | let future = quote! { |
1212 | #wasm_bindgen_futures::JsFuture::from( |
1213 | <#wasm_bindgen_futures::js_sys::Promise as #wasm_bindgen::convert::FromWasmAbi> |
1214 | ::from_abi(#ret_ident.join()) |
1215 | ).await |
1216 | }; |
1217 | convert_ret = if self.catch { |
1218 | quote! { Ok(#future?) } |
1219 | } else { |
1220 | quote! { #future.expect("unexpected exception" ) } |
1221 | }; |
1222 | } else { |
1223 | abi_ret = quote! { |
1224 | #wasm_bindgen::convert::WasmRet<<#ty as #wasm_bindgen::convert::FromWasmAbi>::Abi> |
1225 | }; |
1226 | convert_ret = quote! { |
1227 | <#ty as #wasm_bindgen::convert::FromWasmAbi> |
1228 | ::from_abi(#ret_ident.join()) |
1229 | }; |
1230 | } |
1231 | } |
1232 | None => { |
1233 | if self.function.r#async { |
1234 | abi_ret = quote! { |
1235 | #wasm_bindgen::convert::WasmRet<<#wasm_bindgen_futures::js_sys::Promise as #wasm_bindgen::convert::FromWasmAbi>::Abi> |
1236 | }; |
1237 | let future = quote! { |
1238 | #wasm_bindgen_futures::JsFuture::from( |
1239 | <#wasm_bindgen_futures::js_sys::Promise as #wasm_bindgen::convert::FromWasmAbi> |
1240 | ::from_abi(#ret_ident.join()) |
1241 | ).await |
1242 | }; |
1243 | convert_ret = if self.catch { |
1244 | quote! { #future?; Ok(()) } |
1245 | } else { |
1246 | quote! { #future.expect("uncaught exception" ); } |
1247 | }; |
1248 | } else { |
1249 | abi_ret = quote! { () }; |
1250 | convert_ret = quote! { () }; |
1251 | } |
1252 | } |
1253 | } |
1254 | |
1255 | let mut exceptional_ret = quote!(); |
1256 | if self.catch && !self.function.r#async { |
1257 | convert_ret = quote! { Ok(#convert_ret) }; |
1258 | exceptional_ret = quote! { |
1259 | #wasm_bindgen::__rt::take_last_exception()?; |
1260 | }; |
1261 | } |
1262 | |
1263 | let rust_name = &self.rust_name; |
1264 | let import_name = &self.shim; |
1265 | let attrs = &self.function.rust_attrs; |
1266 | let arguments = &arguments; |
1267 | let abi_arguments = &abi_arguments[..]; |
1268 | let abi_argument_names = &abi_argument_names[..]; |
1269 | |
1270 | let doc_comment = &self.doc_comment; |
1271 | let me = if is_method { |
1272 | quote! { &self, } |
1273 | } else { |
1274 | quote!() |
1275 | }; |
1276 | |
1277 | // Route any errors pointing to this imported function to the identifier |
1278 | // of the function we're imported from so we at least know what function |
1279 | // is causing issues. |
1280 | // |
1281 | // Note that this is where type errors like "doesn't implement |
1282 | // FromWasmAbi" or "doesn't implement IntoWasmAbi" currently get routed. |
1283 | // I suspect that's because they show up in the signature via trait |
1284 | // projections as types of arguments, and all that needs to typecheck |
1285 | // before the body can be typechecked. Due to rust-lang/rust#60980 (and |
1286 | // probably related issues) we can't really get a precise span. |
1287 | // |
1288 | // Ideally what we want is to point errors for particular types back to |
1289 | // the specific argument/type that generated the error, but it looks |
1290 | // like rustc itself doesn't do great in that regard so let's just do |
1291 | // the best we can in the meantime. |
1292 | let extern_fn = respan( |
1293 | extern_fn( |
1294 | import_name, |
1295 | attrs, |
1296 | abi_arguments, |
1297 | abi_argument_names, |
1298 | abi_ret, |
1299 | ), |
1300 | &self.rust_name, |
1301 | ); |
1302 | |
1303 | let maybe_unsafe = if self.function.r#unsafe { |
1304 | Some(quote! {unsafe}) |
1305 | } else { |
1306 | None |
1307 | }; |
1308 | let maybe_async = if self.function.r#async { |
1309 | Some(quote! {async}) |
1310 | } else { |
1311 | None |
1312 | }; |
1313 | let invocation = quote! { |
1314 | // This is due to `#[automatically_derived]` attribute cannot be |
1315 | // placed onto bare functions. |
1316 | #[allow(nonstandard_style)] |
1317 | #[allow(clippy::all, clippy::nursery, clippy::pedantic, clippy::restriction)] |
1318 | #(#attrs)* |
1319 | #[doc = #doc_comment] |
1320 | #vis #maybe_async #maybe_unsafe fn #rust_name(#me #(#arguments),*) #ret { |
1321 | #extern_fn |
1322 | |
1323 | unsafe { |
1324 | let #ret_ident = { |
1325 | #(#arg_conversions)* |
1326 | #import_name(#(#abi_argument_names),*) |
1327 | }; |
1328 | #exceptional_ret |
1329 | #convert_ret |
1330 | } |
1331 | } |
1332 | }; |
1333 | |
1334 | if let Some(class) = class_ty { |
1335 | (quote! { |
1336 | #[automatically_derived] |
1337 | impl #class { |
1338 | #invocation |
1339 | } |
1340 | }) |
1341 | .to_tokens(tokens); |
1342 | } else { |
1343 | invocation.to_tokens(tokens); |
1344 | } |
1345 | |
1346 | Ok(()) |
1347 | } |
1348 | } |
1349 | |
1350 | // See comment above in ast::Export for what's going on here. |
1351 | struct DescribeImport<'a> { |
1352 | kind: &'a ast::ImportKind, |
1353 | wasm_bindgen: &'a syn::Path, |
1354 | } |
1355 | |
1356 | impl<'a> ToTokens for DescribeImport<'a> { |
1357 | fn to_tokens(&self, tokens: &mut TokenStream) { |
1358 | let f = match *self.kind { |
1359 | ast::ImportKind::Function(ref f) => f, |
1360 | ast::ImportKind::Static(_) => return, |
1361 | ast::ImportKind::Type(_) => return, |
1362 | ast::ImportKind::Enum(_) => return, |
1363 | }; |
1364 | let argtys = f.function.arguments.iter().map(|arg| &arg.ty); |
1365 | let nargs = f.function.arguments.len() as u32; |
1366 | let inform_ret = match &f.js_ret { |
1367 | Some(ref t) => quote! { <#t as WasmDescribe>::describe(); }, |
1368 | // async functions always return a JsValue, even if they say to return () |
1369 | None if f.function.r#async => quote! { <JsValue as WasmDescribe>::describe(); }, |
1370 | None => quote! { <() as WasmDescribe>::describe(); }, |
1371 | }; |
1372 | |
1373 | Descriptor { |
1374 | ident: &f.shim, |
1375 | inner: quote! { |
1376 | inform(FUNCTION); |
1377 | inform(0); |
1378 | inform(#nargs); |
1379 | #(<#argtys as WasmDescribe>::describe();)* |
1380 | #inform_ret |
1381 | #inform_ret |
1382 | }, |
1383 | attrs: f.function.rust_attrs.clone(), |
1384 | wasm_bindgen: self.wasm_bindgen, |
1385 | } |
1386 | .to_tokens(tokens); |
1387 | } |
1388 | } |
1389 | |
1390 | impl ToTokens for ast::Enum { |
1391 | fn to_tokens(&self, into: &mut TokenStream) { |
1392 | let enum_name = &self.rust_name; |
1393 | let name_str = self.js_name.to_string(); |
1394 | let name_len = name_str.len() as u32; |
1395 | let name_chars = name_str.chars().map(|c| c as u32); |
1396 | let hole = &self.hole; |
1397 | let cast_clauses = self.variants.iter().map(|variant| { |
1398 | let variant_name = &variant.name; |
1399 | quote! { |
1400 | if js == #enum_name::#variant_name as u32 { |
1401 | #enum_name::#variant_name |
1402 | } |
1403 | } |
1404 | }); |
1405 | let try_from_cast_clauses = cast_clauses.clone(); |
1406 | let wasm_bindgen = &self.wasm_bindgen; |
1407 | (quote! { |
1408 | #[automatically_derived] |
1409 | impl #wasm_bindgen::convert::IntoWasmAbi for #enum_name { |
1410 | type Abi = u32; |
1411 | |
1412 | #[inline] |
1413 | fn into_abi(self) -> u32 { |
1414 | self as u32 |
1415 | } |
1416 | } |
1417 | |
1418 | #[automatically_derived] |
1419 | impl #wasm_bindgen::convert::FromWasmAbi for #enum_name { |
1420 | type Abi = u32; |
1421 | |
1422 | #[inline] |
1423 | unsafe fn from_abi(js: u32) -> Self { |
1424 | #(#cast_clauses else)* { |
1425 | #wasm_bindgen::throw_str("invalid enum value passed" ) |
1426 | } |
1427 | } |
1428 | } |
1429 | |
1430 | #[automatically_derived] |
1431 | impl #wasm_bindgen::convert::OptionFromWasmAbi for #enum_name { |
1432 | #[inline] |
1433 | fn is_none(val: &u32) -> bool { *val == #hole } |
1434 | } |
1435 | |
1436 | #[automatically_derived] |
1437 | impl #wasm_bindgen::convert::OptionIntoWasmAbi for #enum_name { |
1438 | #[inline] |
1439 | fn none() -> Self::Abi { #hole } |
1440 | } |
1441 | |
1442 | #[automatically_derived] |
1443 | impl #wasm_bindgen::describe::WasmDescribe for #enum_name { |
1444 | fn describe() { |
1445 | use #wasm_bindgen::describe::*; |
1446 | inform(ENUM); |
1447 | inform(#name_len); |
1448 | #(inform(#name_chars);)* |
1449 | inform(#hole); |
1450 | } |
1451 | } |
1452 | |
1453 | #[automatically_derived] |
1454 | impl #wasm_bindgen::__rt::core::convert::From<#enum_name> for |
1455 | #wasm_bindgen::JsValue |
1456 | { |
1457 | fn from(value: #enum_name) -> Self { |
1458 | #wasm_bindgen::JsValue::from_f64((value as u32).into()) |
1459 | } |
1460 | } |
1461 | |
1462 | #[allow(clippy::all)] |
1463 | impl #wasm_bindgen::convert::TryFromJsValue for #enum_name { |
1464 | type Error = #wasm_bindgen::JsValue; |
1465 | |
1466 | fn try_from_js_value(value: #wasm_bindgen::JsValue) |
1467 | -> #wasm_bindgen::__rt::std::result::Result<Self, <#enum_name as #wasm_bindgen::convert::TryFromJsValue>::Error> { |
1468 | use #wasm_bindgen::__rt::core::convert::TryFrom; |
1469 | let js = f64::try_from(&value)? as u32; |
1470 | |
1471 | #wasm_bindgen::__rt::std::result::Result::Ok( |
1472 | #(#try_from_cast_clauses else)* { |
1473 | return #wasm_bindgen::__rt::std::result::Result::Err(value) |
1474 | } |
1475 | ) |
1476 | } |
1477 | } |
1478 | |
1479 | impl #wasm_bindgen::describe::WasmDescribeVector for #enum_name { |
1480 | fn describe_vector() { |
1481 | use #wasm_bindgen::describe::*; |
1482 | inform(VECTOR); |
1483 | <#wasm_bindgen::JsValue as #wasm_bindgen::describe::WasmDescribe>::describe(); |
1484 | } |
1485 | } |
1486 | |
1487 | impl #wasm_bindgen::convert::VectorIntoWasmAbi for #enum_name { |
1488 | type Abi = < |
1489 | #wasm_bindgen::__rt::std::boxed::Box<[#wasm_bindgen::JsValue]> |
1490 | as #wasm_bindgen::convert::IntoWasmAbi |
1491 | >::Abi; |
1492 | |
1493 | fn vector_into_abi( |
1494 | vector: #wasm_bindgen::__rt::std::boxed::Box<[#enum_name]> |
1495 | ) -> Self::Abi { |
1496 | #wasm_bindgen::convert::js_value_vector_into_abi(vector) |
1497 | } |
1498 | } |
1499 | |
1500 | impl #wasm_bindgen::convert::VectorFromWasmAbi for #enum_name { |
1501 | type Abi = < |
1502 | #wasm_bindgen::__rt::std::boxed::Box<[#wasm_bindgen::JsValue]> |
1503 | as #wasm_bindgen::convert::FromWasmAbi |
1504 | >::Abi; |
1505 | |
1506 | unsafe fn vector_from_abi( |
1507 | js: Self::Abi |
1508 | ) -> #wasm_bindgen::__rt::std::boxed::Box<[#enum_name]> { |
1509 | #wasm_bindgen::convert::js_value_vector_from_abi(js) |
1510 | } |
1511 | } |
1512 | }) |
1513 | .to_tokens(into); |
1514 | } |
1515 | } |
1516 | |
1517 | impl ToTokens for ast::ImportStatic { |
1518 | fn to_tokens(&self, into: &mut TokenStream) { |
1519 | let name = &self.rust_name; |
1520 | let ty = &self.ty; |
1521 | let shim_name = &self.shim; |
1522 | let vis = &self.vis; |
1523 | let wasm_bindgen = &self.wasm_bindgen; |
1524 | |
1525 | let abi_ret = quote! { |
1526 | #wasm_bindgen::convert::WasmRet<<#ty as #wasm_bindgen::convert::FromWasmAbi>::Abi> |
1527 | }; |
1528 | (quote! { |
1529 | #[automatically_derived] |
1530 | #vis static #name: #wasm_bindgen::JsStatic<#ty> = { |
1531 | fn init() -> #ty { |
1532 | #[link(wasm_import_module = "__wbindgen_placeholder__" )] |
1533 | #[cfg(all(target_arch = "wasm32" , not(any(target_os = "emscripten" , target_os = "wasi" ))))] |
1534 | extern "C" { |
1535 | fn #shim_name() -> #abi_ret; |
1536 | } |
1537 | |
1538 | #[cfg(not(all(target_arch = "wasm32" , not(any(target_os = "emscripten" , target_os = "wasi" )))))] |
1539 | unsafe fn #shim_name() -> #abi_ret { |
1540 | panic!("cannot access imported statics on non-wasm targets" ) |
1541 | } |
1542 | |
1543 | unsafe { |
1544 | <#ty as #wasm_bindgen::convert::FromWasmAbi>::from_abi(#shim_name().join()) |
1545 | } |
1546 | } |
1547 | thread_local!(static _VAL: #ty = init();); |
1548 | #wasm_bindgen::JsStatic { |
1549 | __inner: &_VAL, |
1550 | } |
1551 | }; |
1552 | }) |
1553 | .to_tokens(into); |
1554 | |
1555 | Descriptor { |
1556 | ident: shim_name, |
1557 | inner: quote! { |
1558 | <#ty as WasmDescribe>::describe(); |
1559 | }, |
1560 | attrs: vec![], |
1561 | wasm_bindgen: &self.wasm_bindgen, |
1562 | } |
1563 | .to_tokens(into); |
1564 | } |
1565 | } |
1566 | |
1567 | /// Emits the necessary glue tokens for "descriptor", generating an appropriate |
1568 | /// symbol name as well as attributes around the descriptor function itself. |
1569 | struct Descriptor<'a, T> { |
1570 | ident: &'a Ident, |
1571 | inner: T, |
1572 | attrs: Vec<syn::Attribute>, |
1573 | wasm_bindgen: &'a syn::Path, |
1574 | } |
1575 | |
1576 | impl<'a, T: ToTokens> ToTokens for Descriptor<'a, T> { |
1577 | fn to_tokens(&self, tokens: &mut TokenStream) { |
1578 | // It's possible for the same descriptor to be emitted in two different |
1579 | // modules (aka a value imported twice in a crate, each in a separate |
1580 | // module). In this case no need to emit duplicate descriptors (which |
1581 | // leads to duplicate symbol errors), instead just emit one. |
1582 | // |
1583 | // It's up to the descriptors themselves to ensure they have unique |
1584 | // names for unique items imported, currently done via `ShortHash` and |
1585 | // hashing appropriate data into the symbol name. |
1586 | static DESCRIPTORS_EMITTED: Lazy<Mutex<HashSet<String>>> = Lazy::new(Default::default); |
1587 | |
1588 | let ident = self.ident; |
1589 | |
1590 | if !DESCRIPTORS_EMITTED |
1591 | .lock() |
1592 | .unwrap() |
1593 | .insert(ident.to_string()) |
1594 | { |
1595 | return; |
1596 | } |
1597 | |
1598 | let name = Ident::new(&format!("__wbindgen_describe_ {}" , ident), ident.span()); |
1599 | let inner = &self.inner; |
1600 | let attrs = &self.attrs; |
1601 | let wasm_bindgen = &self.wasm_bindgen; |
1602 | (quote! { |
1603 | #[cfg(all(target_arch = "wasm32" , not(any(target_os = "emscripten" , target_os = "wasi" ))))] |
1604 | #[automatically_derived] |
1605 | const _: () = { |
1606 | #(#attrs)* |
1607 | #[no_mangle] |
1608 | #[doc(hidden)] |
1609 | pub extern "C" fn #name() { |
1610 | use #wasm_bindgen::describe::*; |
1611 | // See definition of `link_mem_intrinsics` for what this is doing |
1612 | #wasm_bindgen::__rt::link_mem_intrinsics(); |
1613 | #inner |
1614 | } |
1615 | }; |
1616 | }) |
1617 | .to_tokens(tokens); |
1618 | } |
1619 | } |
1620 | |
1621 | fn extern_fn( |
1622 | import_name: &Ident, |
1623 | attrs: &[syn::Attribute], |
1624 | abi_arguments: &[TokenStream], |
1625 | abi_argument_names: &[Ident], |
1626 | abi_ret: TokenStream, |
1627 | ) -> TokenStream { |
1628 | quote! { |
1629 | #[cfg(all(target_arch = "wasm32" , not(any(target_os = "emscripten" , target_os = "wasi" ))))] |
1630 | #(#attrs)* |
1631 | #[link(wasm_import_module = "__wbindgen_placeholder__" )] |
1632 | extern "C" { |
1633 | fn #import_name(#(#abi_arguments),*) -> #abi_ret; |
1634 | } |
1635 | |
1636 | #[cfg(not(all(target_arch = "wasm32" , not(any(target_os = "emscripten" , target_os = "wasi" )))))] |
1637 | unsafe fn #import_name(#(#abi_arguments),*) -> #abi_ret { |
1638 | #( |
1639 | drop(#abi_argument_names); |
1640 | )* |
1641 | panic!("cannot call wasm-bindgen imported functions on \ |
1642 | non-wasm targets" ); |
1643 | } |
1644 | } |
1645 | } |
1646 | |
1647 | /// Splats an argument with the given name and ABI type into 4 arguments, one |
1648 | /// for each primitive that the ABI type splits into. |
1649 | /// |
1650 | /// Returns an `(args, names)` pair, where `args` is the list of arguments to |
1651 | /// be inserted into the function signature, and `names` is a list of the names |
1652 | /// of those arguments. |
1653 | fn splat( |
1654 | wasm_bindgen: &syn::Path, |
1655 | name: &Ident, |
1656 | abi: &TokenStream, |
1657 | ) -> (Vec<TokenStream>, Vec<Ident>) { |
1658 | let mut args: Vec = Vec::new(); |
1659 | let mut names: Vec = Vec::new(); |
1660 | |
1661 | for n: i32 in 1..=4 { |
1662 | let arg_name: Ident = format_ident!(" {name}_ {n}" ); |
1663 | let prim_name: Ident = format_ident!("Prim {n}" ); |
1664 | args.push(quote! { |
1665 | #arg_name: <#abi as #wasm_bindgen::convert::WasmAbi>::#prim_name |
1666 | }); |
1667 | names.push(arg_name); |
1668 | } |
1669 | |
1670 | (args, names) |
1671 | } |
1672 | |
1673 | /// Converts `span` into a stream of tokens, and attempts to ensure that `input` |
1674 | /// has all the appropriate span information so errors in it point to `span`. |
1675 | fn respan(input: TokenStream, span: &dyn ToTokens) -> TokenStream { |
1676 | let mut first_span: Span = Span::call_site(); |
1677 | let mut last_span: Span = Span::call_site(); |
1678 | let mut spans: TokenStream = TokenStream::new(); |
1679 | span.to_tokens(&mut spans); |
1680 | |
1681 | for (i: usize, token: TokenTree) in spans.into_iter().enumerate() { |
1682 | if i == 0 { |
1683 | first_span = token.span(); |
1684 | } |
1685 | last_span = token.span(); |
1686 | } |
1687 | |
1688 | let mut new_tokens: Vec = Vec::new(); |
1689 | for (i: usize, mut token: TokenTree) in input.into_iter().enumerate() { |
1690 | if i == 0 { |
1691 | token.set_span(first_span); |
1692 | } else { |
1693 | token.set_span(last_span); |
1694 | } |
1695 | new_tokens.push(token); |
1696 | } |
1697 | new_tokens.into_iter().collect() |
1698 | } |
1699 | |