1 | use std::cell::{Cell, RefCell}; |
2 | use std::char; |
3 | use std::str::Chars; |
4 | |
5 | use ast::OperationKind; |
6 | use backend::ast; |
7 | use backend::util::{ident_ty, ShortHash}; |
8 | use backend::Diagnostic; |
9 | use proc_macro2::{Ident, Span, TokenStream, TokenTree}; |
10 | use quote::ToTokens; |
11 | use syn::ext::IdentExt; |
12 | use syn::parse::{Parse, ParseStream, Result as SynResult}; |
13 | use syn::spanned::Spanned; |
14 | use syn::{ItemFn, Lit, MacroDelimiter, ReturnType}; |
15 | |
16 | use crate::ClassMarker; |
17 | |
18 | thread_local!(static ATTRS: AttributeParseState = Default::default()); |
19 | |
20 | /// Javascript keywords which are not keywords in Rust. |
21 | const JS_KEYWORDS: [&str; 20] = [ |
22 | "class" , |
23 | "case" , |
24 | "catch" , |
25 | "debugger" , |
26 | "default" , |
27 | "delete" , |
28 | "export" , |
29 | "extends" , |
30 | "finally" , |
31 | "function" , |
32 | "import" , |
33 | "instanceof" , |
34 | "new" , |
35 | "null" , |
36 | "switch" , |
37 | "this" , |
38 | "throw" , |
39 | "var" , |
40 | "void" , |
41 | "with" , |
42 | ]; |
43 | #[derive (Default)] |
44 | struct AttributeParseState { |
45 | parsed: Cell<usize>, |
46 | checks: Cell<usize>, |
47 | unused_attrs: RefCell<Vec<Ident>>, |
48 | } |
49 | |
50 | /// Parsed attributes from a `#[wasm_bindgen(..)]`. |
51 | #[cfg_attr (feature = "extra-traits" , derive(Debug))] |
52 | pub struct BindgenAttrs { |
53 | /// List of parsed attributes |
54 | pub attrs: Vec<(Cell<bool>, BindgenAttr)>, |
55 | } |
56 | |
57 | macro_rules! attrgen { |
58 | ($mac:ident) => { |
59 | $mac! { |
60 | (catch, Catch(Span)), |
61 | (constructor, Constructor(Span)), |
62 | (method, Method(Span)), |
63 | (static_method_of, StaticMethodOf(Span, Ident)), |
64 | (js_namespace, JsNamespace(Span, Vec<String>, Vec<Span>)), |
65 | (module, Module(Span, String, Span)), |
66 | (raw_module, RawModule(Span, String, Span)), |
67 | (inline_js, InlineJs(Span, String, Span)), |
68 | (getter, Getter(Span, Option<Ident>)), |
69 | (setter, Setter(Span, Option<Ident>)), |
70 | (indexing_getter, IndexingGetter(Span)), |
71 | (indexing_setter, IndexingSetter(Span)), |
72 | (indexing_deleter, IndexingDeleter(Span)), |
73 | (structural, Structural(Span)), |
74 | (r#final, Final(Span)), |
75 | (readonly, Readonly(Span)), |
76 | (js_name, JsName(Span, String, Span)), |
77 | (js_class, JsClass(Span, String, Span)), |
78 | (inspectable, Inspectable(Span)), |
79 | (is_type_of, IsTypeOf(Span, syn::Expr)), |
80 | (extends, Extends(Span, syn::Path)), |
81 | (no_deref, NoDeref(Span)), |
82 | (vendor_prefix, VendorPrefix(Span, Ident)), |
83 | (variadic, Variadic(Span)), |
84 | (typescript_custom_section, TypescriptCustomSection(Span)), |
85 | (skip_typescript, SkipTypescript(Span)), |
86 | (skip_jsdoc, SkipJsDoc(Span)), |
87 | (main, Main(Span)), |
88 | (start, Start(Span)), |
89 | (wasm_bindgen, WasmBindgen(Span, syn::Path)), |
90 | (wasm_bindgen_futures, WasmBindgenFutures(Span, syn::Path)), |
91 | (skip, Skip(Span)), |
92 | (typescript_type, TypeScriptType(Span, String, Span)), |
93 | (getter_with_clone, GetterWithClone(Span)), |
94 | |
95 | // For testing purposes only. |
96 | (assert_no_shim, AssertNoShim(Span)), |
97 | } |
98 | }; |
99 | } |
100 | |
101 | macro_rules! methods { |
102 | ($(($name:ident, $variant:ident($($contents:tt)*)),)*) => { |
103 | $(methods!(@method $name, $variant($($contents)*));)* |
104 | |
105 | fn enforce_used(self) -> Result<(), Diagnostic> { |
106 | // Account for the fact this method was called |
107 | ATTRS.with(|state| state.checks.set(state.checks.get() + 1)); |
108 | |
109 | let mut errors = Vec::new(); |
110 | for (used, attr) in self.attrs.iter() { |
111 | if used.get() { |
112 | continue |
113 | } |
114 | let span = match attr { |
115 | $(BindgenAttr::$variant(span, ..) => span,)* |
116 | }; |
117 | errors.push(Diagnostic::span_error(*span, "unused wasm_bindgen attribute" )); |
118 | } |
119 | Diagnostic::from_vec(errors) |
120 | } |
121 | |
122 | fn check_used(self) { |
123 | // Account for the fact this method was called |
124 | ATTRS.with(|state| { |
125 | state.checks.set(state.checks.get() + 1); |
126 | |
127 | state.unused_attrs.borrow_mut().extend( |
128 | self.attrs |
129 | .iter() |
130 | .filter_map(|(used, attr)| if used.get() { None } else { Some(attr) }) |
131 | .map(|attr| { |
132 | match attr { |
133 | $(BindgenAttr::$variant(span, ..) => { |
134 | syn::parse_quote_spanned!(*span => $name) |
135 | })* |
136 | } |
137 | }) |
138 | ); |
139 | }); |
140 | } |
141 | }; |
142 | |
143 | (@method $name:ident, $variant:ident(Span, String, Span)) => { |
144 | fn $name(&self) -> Option<(&str, Span)> { |
145 | self.attrs |
146 | .iter() |
147 | .find_map(|a| match &a.1 { |
148 | BindgenAttr::$variant(_, s, span) => { |
149 | a.0.set(true); |
150 | Some((&s[..], *span)) |
151 | } |
152 | _ => None, |
153 | }) |
154 | } |
155 | }; |
156 | |
157 | (@method $name:ident, $variant:ident(Span, Vec<String>, Vec<Span>)) => { |
158 | fn $name(&self) -> Option<(&[String], &[Span])> { |
159 | self.attrs |
160 | .iter() |
161 | .find_map(|a| match &a.1 { |
162 | BindgenAttr::$variant(_, ss, spans) => { |
163 | a.0.set(true); |
164 | Some((&ss[..], &spans[..])) |
165 | } |
166 | _ => None, |
167 | }) |
168 | } |
169 | }; |
170 | |
171 | (@method $name:ident, $variant:ident(Span, $($other:tt)*)) => { |
172 | #[allow(unused)] |
173 | fn $name(&self) -> Option<&$($other)*> { |
174 | self.attrs |
175 | .iter() |
176 | .find_map(|a| match &a.1 { |
177 | BindgenAttr::$variant(_, s) => { |
178 | a.0.set(true); |
179 | Some(s) |
180 | } |
181 | _ => None, |
182 | }) |
183 | } |
184 | }; |
185 | |
186 | (@method $name:ident, $variant:ident($($other:tt)*)) => { |
187 | #[allow(unused)] |
188 | fn $name(&self) -> Option<&$($other)*> { |
189 | self.attrs |
190 | .iter() |
191 | .find_map(|a| match &a.1 { |
192 | BindgenAttr::$variant(s) => { |
193 | a.0.set(true); |
194 | Some(s) |
195 | } |
196 | _ => None, |
197 | }) |
198 | } |
199 | }; |
200 | } |
201 | |
202 | impl BindgenAttrs { |
203 | /// Find and parse the wasm_bindgen attributes. |
204 | fn find(attrs: &mut Vec<syn::Attribute>) -> Result<BindgenAttrs, Diagnostic> { |
205 | let mut ret = BindgenAttrs::default(); |
206 | loop { |
207 | let pos = attrs |
208 | .iter() |
209 | .enumerate() |
210 | .find(|&(_, m)| m.path().segments[0].ident == "wasm_bindgen" ) |
211 | .map(|a| a.0); |
212 | let pos = match pos { |
213 | Some(i) => i, |
214 | None => return Ok(ret), |
215 | }; |
216 | let attr = attrs.remove(pos); |
217 | let tokens = match attr.meta { |
218 | syn::Meta::Path(_) => continue, |
219 | syn::Meta::List(syn::MetaList { |
220 | delimiter: MacroDelimiter::Paren(_), |
221 | tokens, |
222 | .. |
223 | }) => tokens, |
224 | syn::Meta::List(_) | syn::Meta::NameValue(_) => { |
225 | bail_span!(attr, "malformed #[wasm_bindgen] attribute" ) |
226 | } |
227 | }; |
228 | let mut attrs: BindgenAttrs = syn::parse2(tokens)?; |
229 | ret.attrs.append(&mut attrs.attrs); |
230 | attrs.check_used(); |
231 | } |
232 | } |
233 | |
234 | attrgen!(methods); |
235 | } |
236 | |
237 | impl Default for BindgenAttrs { |
238 | fn default() -> BindgenAttrs { |
239 | // Add 1 to the list of parsed attribute sets. We'll use this counter to |
240 | // sanity check that we call `check_used` an appropriate number of |
241 | // times. |
242 | ATTRS.with(|state: &AttributeParseState| state.parsed.set(val:state.parsed.get() + 1)); |
243 | BindgenAttrs { attrs: Vec::new() } |
244 | } |
245 | } |
246 | |
247 | impl Parse for BindgenAttrs { |
248 | fn parse(input: ParseStream) -> SynResult<Self> { |
249 | let mut attrs: BindgenAttrs = BindgenAttrs::default(); |
250 | if input.is_empty() { |
251 | return Ok(attrs); |
252 | } |
253 | |
254 | let opts: Punctuated = syn::punctuated::Punctuated::<_, syn::token::Comma>::parse_terminated(input)?; |
255 | attrs.attrs = opts.into_iter().map(|c: BindgenAttr| (Cell::new(false), c)).collect(); |
256 | Ok(attrs) |
257 | } |
258 | } |
259 | |
260 | macro_rules! gen_bindgen_attr { |
261 | ($(($method:ident, $($variants:tt)*),)*) => { |
262 | /// The possible attributes in the `#[wasm_bindgen]`. |
263 | #[cfg_attr(feature = "extra-traits" , derive(Debug))] |
264 | pub enum BindgenAttr { |
265 | $($($variants)*,)* |
266 | } |
267 | } |
268 | } |
269 | attrgen!(gen_bindgen_attr); |
270 | |
271 | impl Parse for BindgenAttr { |
272 | fn parse(input: ParseStream) -> SynResult<Self> { |
273 | let original = input.fork(); |
274 | let attr: AnyIdent = input.parse()?; |
275 | let attr = attr.0; |
276 | let attr_span = attr.span(); |
277 | let attr_string = attr.to_string(); |
278 | let raw_attr_string = format!("r# {}" , attr_string); |
279 | |
280 | macro_rules! parsers { |
281 | ($(($name:ident, $($contents:tt)*),)*) => { |
282 | $( |
283 | if attr_string == stringify!($name) || raw_attr_string == stringify!($name) { |
284 | parsers!( |
285 | @parser |
286 | $($contents)* |
287 | ); |
288 | } |
289 | )* |
290 | }; |
291 | |
292 | (@parser $variant:ident(Span)) => ({ |
293 | return Ok(BindgenAttr::$variant(attr_span)); |
294 | }); |
295 | |
296 | (@parser $variant:ident(Span, Ident)) => ({ |
297 | input.parse::<Token![=]>()?; |
298 | let ident = input.parse::<AnyIdent>()?.0; |
299 | return Ok(BindgenAttr::$variant(attr_span, ident)) |
300 | }); |
301 | |
302 | (@parser $variant:ident(Span, Option<Ident>)) => ({ |
303 | if input.parse::<Token![=]>().is_ok() { |
304 | let ident = input.parse::<AnyIdent>()?.0; |
305 | return Ok(BindgenAttr::$variant(attr_span, Some(ident))) |
306 | } else { |
307 | return Ok(BindgenAttr::$variant(attr_span, None)); |
308 | } |
309 | }); |
310 | |
311 | (@parser $variant:ident(Span, syn::Path)) => ({ |
312 | input.parse::<Token![=]>()?; |
313 | return Ok(BindgenAttr::$variant(attr_span, input.parse()?)); |
314 | }); |
315 | |
316 | (@parser $variant:ident(Span, syn::Expr)) => ({ |
317 | input.parse::<Token![=]>()?; |
318 | return Ok(BindgenAttr::$variant(attr_span, input.parse()?)); |
319 | }); |
320 | |
321 | (@parser $variant:ident(Span, String, Span)) => ({ |
322 | input.parse::<Token![=]>()?; |
323 | let (val, span) = match input.parse::<syn::LitStr>() { |
324 | Ok(str) => (str.value(), str.span()), |
325 | Err(_) => { |
326 | let ident = input.parse::<AnyIdent>()?.0; |
327 | (ident.to_string(), ident.span()) |
328 | } |
329 | }; |
330 | return Ok(BindgenAttr::$variant(attr_span, val, span)) |
331 | }); |
332 | |
333 | (@parser $variant:ident(Span, Vec<String>, Vec<Span>)) => ({ |
334 | input.parse::<Token![=]>()?; |
335 | let (vals, spans) = match input.parse::<syn::ExprArray>() { |
336 | Ok(exprs) => { |
337 | let mut vals = vec![]; |
338 | let mut spans = vec![]; |
339 | |
340 | for expr in exprs.elems.iter() { |
341 | if let syn::Expr::Lit(syn::ExprLit { |
342 | lit: syn::Lit::Str(ref str), |
343 | .. |
344 | }) = expr { |
345 | vals.push(str.value()); |
346 | spans.push(str.span()); |
347 | } else { |
348 | return Err(syn::Error::new(expr.span(), "expected string literals" )); |
349 | } |
350 | } |
351 | |
352 | (vals, spans) |
353 | }, |
354 | Err(_) => { |
355 | let ident = input.parse::<AnyIdent>()?.0; |
356 | (vec![ident.to_string()], vec![ident.span()]) |
357 | } |
358 | }; |
359 | return Ok(BindgenAttr::$variant(attr_span, vals, spans)) |
360 | }); |
361 | } |
362 | |
363 | attrgen!(parsers); |
364 | |
365 | Err(original.error(if attr_string.starts_with('_' ) { |
366 | "unknown attribute: it's safe to remove unused attributes entirely." |
367 | } else { |
368 | "unknown attribute" |
369 | })) |
370 | } |
371 | } |
372 | |
373 | struct AnyIdent(Ident); |
374 | |
375 | impl Parse for AnyIdent { |
376 | fn parse(input: ParseStream) -> SynResult<Self> { |
377 | input.step(|cursor: StepCursor<'_, '_>| match cursor.ident() { |
378 | Some((ident: Ident, remaining: Cursor<'_>)) => Ok((AnyIdent(ident), remaining)), |
379 | None => Err(cursor.error(message:"expected an identifier" )), |
380 | }) |
381 | } |
382 | } |
383 | |
384 | /// Conversion trait with context. |
385 | /// |
386 | /// Used to convert syn tokens into an AST, that we can then use to generate glue code. The context |
387 | /// (`Ctx`) is used to pass in the attributes from the `#[wasm_bindgen]`, if needed. |
388 | trait ConvertToAst<Ctx> { |
389 | /// What we are converting to. |
390 | type Target; |
391 | /// Convert into our target. |
392 | /// |
393 | /// Since this is used in a procedural macro, use panic to fail. |
394 | fn convert(self, context: Ctx) -> Result<Self::Target, Diagnostic>; |
395 | } |
396 | |
397 | impl<'a> ConvertToAst<(&ast::Program, BindgenAttrs)> for &'a mut syn::ItemStruct { |
398 | type Target = ast::Struct; |
399 | |
400 | fn convert( |
401 | self, |
402 | (program, attrs): (&ast::Program, BindgenAttrs), |
403 | ) -> Result<Self::Target, Diagnostic> { |
404 | if !self.generics.params.is_empty() { |
405 | bail_span!( |
406 | self.generics, |
407 | "structs with #[wasm_bindgen] cannot have lifetime or \ |
408 | type parameters currently" |
409 | ); |
410 | } |
411 | let mut fields = Vec::new(); |
412 | let js_name = attrs |
413 | .js_name() |
414 | .map(|s| s.0.to_string()) |
415 | .unwrap_or(self.ident.to_string()); |
416 | let is_inspectable = attrs.inspectable().is_some(); |
417 | let getter_with_clone = attrs.getter_with_clone(); |
418 | for (i, field) in self.fields.iter_mut().enumerate() { |
419 | match field.vis { |
420 | syn::Visibility::Public(..) => {} |
421 | _ => continue, |
422 | } |
423 | let (js_field_name, member) = match &field.ident { |
424 | Some(ident) => (ident.unraw().to_string(), syn::Member::Named(ident.clone())), |
425 | None => (i.to_string(), syn::Member::Unnamed(i.into())), |
426 | }; |
427 | |
428 | let attrs = BindgenAttrs::find(&mut field.attrs)?; |
429 | if attrs.skip().is_some() { |
430 | attrs.check_used(); |
431 | continue; |
432 | } |
433 | |
434 | let js_field_name = match attrs.js_name() { |
435 | Some((name, _)) => name.to_string(), |
436 | None => js_field_name, |
437 | }; |
438 | |
439 | let comments = extract_doc_comments(&field.attrs); |
440 | let getter = shared::struct_field_get(&js_name, &js_field_name); |
441 | let setter = shared::struct_field_set(&js_name, &js_field_name); |
442 | |
443 | fields.push(ast::StructField { |
444 | rust_name: member, |
445 | js_name: js_field_name, |
446 | struct_name: self.ident.clone(), |
447 | readonly: attrs.readonly().is_some(), |
448 | ty: field.ty.clone(), |
449 | getter: Ident::new(&getter, Span::call_site()), |
450 | setter: Ident::new(&setter, Span::call_site()), |
451 | comments, |
452 | generate_typescript: attrs.skip_typescript().is_none(), |
453 | generate_jsdoc: attrs.skip_jsdoc().is_none(), |
454 | getter_with_clone: attrs.getter_with_clone().or(getter_with_clone).copied(), |
455 | wasm_bindgen: program.wasm_bindgen.clone(), |
456 | }); |
457 | attrs.check_used(); |
458 | } |
459 | let generate_typescript = attrs.skip_typescript().is_none(); |
460 | let comments: Vec<String> = extract_doc_comments(&self.attrs); |
461 | attrs.check_used(); |
462 | Ok(ast::Struct { |
463 | rust_name: self.ident.clone(), |
464 | js_name, |
465 | fields, |
466 | comments, |
467 | is_inspectable, |
468 | generate_typescript, |
469 | wasm_bindgen: program.wasm_bindgen.clone(), |
470 | }) |
471 | } |
472 | } |
473 | |
474 | fn get_ty(mut ty: &syn::Type) -> &syn::Type { |
475 | while let syn::Type::Group(g: &TypeGroup) = ty { |
476 | ty = &g.elem; |
477 | } |
478 | |
479 | ty |
480 | } |
481 | |
482 | fn get_expr(mut expr: &syn::Expr) -> &syn::Expr { |
483 | while let syn::Expr::Group(g: &ExprGroup) = expr { |
484 | expr = &g.expr; |
485 | } |
486 | |
487 | expr |
488 | } |
489 | |
490 | impl<'a> ConvertToAst<(&ast::Program, BindgenAttrs, &'a Option<ast::ImportModule>)> |
491 | for syn::ForeignItemFn |
492 | { |
493 | type Target = ast::ImportKind; |
494 | |
495 | fn convert( |
496 | self, |
497 | (program, opts, module): (&ast::Program, BindgenAttrs, &'a Option<ast::ImportModule>), |
498 | ) -> Result<Self::Target, Diagnostic> { |
499 | let mut wasm = function_from_decl( |
500 | &self.sig.ident, |
501 | &opts, |
502 | self.sig.clone(), |
503 | self.attrs.clone(), |
504 | self.vis.clone(), |
505 | false, |
506 | None, |
507 | false, |
508 | Some(&["default" ]), |
509 | )? |
510 | .0; |
511 | let catch = opts.catch().is_some(); |
512 | let variadic = opts.variadic().is_some(); |
513 | let js_ret = if catch { |
514 | // TODO: this assumes a whole bunch: |
515 | // |
516 | // * The outer type is actually a `Result` |
517 | // * The error type is a `JsValue` |
518 | // * The actual type is the first type parameter |
519 | // |
520 | // should probably fix this one day... |
521 | extract_first_ty_param(wasm.ret.as_ref())? |
522 | } else { |
523 | wasm.ret.clone() |
524 | }; |
525 | |
526 | let operation_kind = operation_kind(&opts); |
527 | |
528 | let kind = if opts.method().is_some() { |
529 | let class = wasm.arguments.first().ok_or_else(|| { |
530 | err_span!(self, "imported methods must have at least one argument" ) |
531 | })?; |
532 | let class = match get_ty(&class.ty) { |
533 | syn::Type::Reference(syn::TypeReference { |
534 | mutability: None, |
535 | elem, |
536 | .. |
537 | }) => &**elem, |
538 | _ => bail_span!( |
539 | class.ty, |
540 | "first argument of method must be a shared reference" |
541 | ), |
542 | }; |
543 | let class_name = match get_ty(class) { |
544 | syn::Type::Path(syn::TypePath { |
545 | qself: None, |
546 | ref path, |
547 | }) => path, |
548 | _ => bail_span!(class, "first argument of method must be a path" ), |
549 | }; |
550 | let class_name = extract_path_ident(class_name)?; |
551 | let class_name = opts |
552 | .js_class() |
553 | .map(|p| p.0.into()) |
554 | .unwrap_or_else(|| class_name.to_string()); |
555 | |
556 | let kind = ast::MethodKind::Operation(ast::Operation { |
557 | is_static: false, |
558 | kind: operation_kind, |
559 | }); |
560 | |
561 | ast::ImportFunctionKind::Method { |
562 | class: class_name, |
563 | ty: class.clone(), |
564 | kind, |
565 | } |
566 | } else if let Some(cls) = opts.static_method_of() { |
567 | let class = opts |
568 | .js_class() |
569 | .map(|p| p.0.into()) |
570 | .unwrap_or_else(|| cls.to_string()); |
571 | let ty = ident_ty(cls.clone()); |
572 | |
573 | let kind = ast::MethodKind::Operation(ast::Operation { |
574 | is_static: true, |
575 | kind: operation_kind, |
576 | }); |
577 | |
578 | ast::ImportFunctionKind::Method { class, ty, kind } |
579 | } else if opts.constructor().is_some() { |
580 | let class = match js_ret { |
581 | Some(ref ty) => ty, |
582 | _ => bail_span!(self, "constructor returns must be bare types" ), |
583 | }; |
584 | let class_name = match get_ty(class) { |
585 | syn::Type::Path(syn::TypePath { |
586 | qself: None, |
587 | ref path, |
588 | }) => path, |
589 | _ => bail_span!(self, "return value of constructor must be a bare path" ), |
590 | }; |
591 | let class_name = extract_path_ident(class_name)?; |
592 | let class_name = opts |
593 | .js_class() |
594 | .map(|p| p.0.into()) |
595 | .unwrap_or_else(|| class_name.to_string()); |
596 | |
597 | ast::ImportFunctionKind::Method { |
598 | class: class_name, |
599 | ty: class.clone(), |
600 | kind: ast::MethodKind::Constructor, |
601 | } |
602 | } else { |
603 | ast::ImportFunctionKind::Normal |
604 | }; |
605 | |
606 | let shim = { |
607 | let ns = match kind { |
608 | ast::ImportFunctionKind::Normal => (0, "n" ), |
609 | ast::ImportFunctionKind::Method { ref class, .. } => (1, &class[..]), |
610 | }; |
611 | let data = (ns, &self.sig.ident, module); |
612 | format!( |
613 | "__wbg_ {}_ {}" , |
614 | wasm.name |
615 | .chars() |
616 | .filter(|c| c.is_ascii_alphanumeric()) |
617 | .collect::<String>(), |
618 | ShortHash(data) |
619 | ) |
620 | }; |
621 | if let Some(span) = opts.r#final() { |
622 | if opts.structural().is_some() { |
623 | let msg = "cannot specify both `structural` and `final`" ; |
624 | return Err(Diagnostic::span_error(*span, msg)); |
625 | } |
626 | } |
627 | let assert_no_shim = opts.assert_no_shim().is_some(); |
628 | |
629 | let mut doc_comment = String::new(); |
630 | // Extract the doc comments from our list of attributes. |
631 | wasm.rust_attrs.retain(|attr| { |
632 | /// Returns the contents of the passed `#[doc = "..."]` attribute, |
633 | /// or `None` if it isn't one. |
634 | fn get_docs(attr: &syn::Attribute) -> Option<String> { |
635 | if attr.path().is_ident("doc" ) { |
636 | if let syn::Meta::NameValue(syn::MetaNameValue { |
637 | value: |
638 | syn::Expr::Lit(syn::ExprLit { |
639 | lit: Lit::Str(str), .. |
640 | }), |
641 | .. |
642 | }) = &attr.meta |
643 | { |
644 | Some(str.value()) |
645 | } else { |
646 | None |
647 | } |
648 | } else { |
649 | None |
650 | } |
651 | } |
652 | |
653 | if let Some(docs) = get_docs(attr) { |
654 | if !doc_comment.is_empty() { |
655 | // Add newlines between the doc comments |
656 | doc_comment.push(' \n' ); |
657 | } |
658 | // Add this doc comment to the complete docs |
659 | doc_comment.push_str(&docs); |
660 | |
661 | // Remove it from the list of regular attributes |
662 | false |
663 | } else { |
664 | true |
665 | } |
666 | }); |
667 | |
668 | let ret = ast::ImportKind::Function(ast::ImportFunction { |
669 | function: wasm, |
670 | assert_no_shim, |
671 | kind, |
672 | js_ret, |
673 | catch, |
674 | variadic, |
675 | structural: opts.structural().is_some() || opts.r#final().is_none(), |
676 | rust_name: self.sig.ident, |
677 | shim: Ident::new(&shim, Span::call_site()), |
678 | doc_comment, |
679 | wasm_bindgen: program.wasm_bindgen.clone(), |
680 | wasm_bindgen_futures: program.wasm_bindgen_futures.clone(), |
681 | }); |
682 | opts.check_used(); |
683 | |
684 | Ok(ret) |
685 | } |
686 | } |
687 | |
688 | impl ConvertToAst<(&ast::Program, BindgenAttrs)> for syn::ForeignItemType { |
689 | type Target = ast::ImportKind; |
690 | |
691 | fn convert( |
692 | self, |
693 | (program, attrs): (&ast::Program, BindgenAttrs), |
694 | ) -> Result<Self::Target, Diagnostic> { |
695 | let js_name = attrs |
696 | .js_name() |
697 | .map(|s| s.0) |
698 | .map_or_else(|| self.ident.to_string(), |s| s.to_string()); |
699 | let typescript_type = attrs.typescript_type().map(|s| s.0.to_string()); |
700 | let is_type_of = attrs.is_type_of().cloned(); |
701 | let shim = format!("__wbg_instanceof_ {}_ {}" , self.ident, ShortHash(&self.ident)); |
702 | let mut extends = Vec::new(); |
703 | let mut vendor_prefixes = Vec::new(); |
704 | let no_deref = attrs.no_deref().is_some(); |
705 | for (used, attr) in attrs.attrs.iter() { |
706 | match attr { |
707 | BindgenAttr::Extends(_, e) => { |
708 | extends.push(e.clone()); |
709 | used.set(true); |
710 | } |
711 | BindgenAttr::VendorPrefix(_, e) => { |
712 | vendor_prefixes.push(e.clone()); |
713 | used.set(true); |
714 | } |
715 | _ => {} |
716 | } |
717 | } |
718 | attrs.check_used(); |
719 | Ok(ast::ImportKind::Type(ast::ImportType { |
720 | vis: self.vis, |
721 | attrs: self.attrs, |
722 | doc_comment: None, |
723 | instanceof_shim: shim, |
724 | is_type_of, |
725 | rust_name: self.ident, |
726 | typescript_type, |
727 | js_name, |
728 | extends, |
729 | vendor_prefixes, |
730 | no_deref, |
731 | wasm_bindgen: program.wasm_bindgen.clone(), |
732 | })) |
733 | } |
734 | } |
735 | |
736 | impl<'a> ConvertToAst<(&ast::Program, BindgenAttrs, &'a Option<ast::ImportModule>)> |
737 | for syn::ForeignItemStatic |
738 | { |
739 | type Target = ast::ImportKind; |
740 | |
741 | fn convert( |
742 | self, |
743 | (program, opts, module): (&ast::Program, BindgenAttrs, &'a Option<ast::ImportModule>), |
744 | ) -> Result<Self::Target, Diagnostic> { |
745 | if let syn::StaticMutability::Mut(_) = self.mutability { |
746 | bail_span!(self.mutability, "cannot import mutable globals yet" ) |
747 | } |
748 | |
749 | let default_name = self.ident.to_string(); |
750 | let js_name = opts |
751 | .js_name() |
752 | .map(|p| p.0) |
753 | .unwrap_or(&default_name) |
754 | .to_string(); |
755 | let shim = format!( |
756 | "__wbg_static_accessor_ {}_ {}" , |
757 | self.ident, |
758 | ShortHash((&js_name, module, &self.ident)), |
759 | ); |
760 | opts.check_used(); |
761 | Ok(ast::ImportKind::Static(ast::ImportStatic { |
762 | ty: *self.ty, |
763 | vis: self.vis, |
764 | rust_name: self.ident.clone(), |
765 | js_name, |
766 | shim: Ident::new(&shim, Span::call_site()), |
767 | wasm_bindgen: program.wasm_bindgen.clone(), |
768 | })) |
769 | } |
770 | } |
771 | |
772 | impl ConvertToAst<BindgenAttrs> for syn::ItemFn { |
773 | type Target = ast::Function; |
774 | |
775 | fn convert(self, attrs: BindgenAttrs) -> Result<Self::Target, Diagnostic> { |
776 | match self.vis { |
777 | syn::Visibility::Public(_) => {} |
778 | _ if attrs.start().is_some() => {} |
779 | _ => bail_span!(self, "can only #[wasm_bindgen] public functions" ), |
780 | } |
781 | if self.sig.constness.is_some() { |
782 | bail_span!( |
783 | self.sig.constness, |
784 | "can only #[wasm_bindgen] non-const functions" |
785 | ); |
786 | } |
787 | |
788 | let ret = function_from_decl( |
789 | &self.sig.ident, |
790 | &attrs, |
791 | self.sig.clone(), |
792 | self.attrs, |
793 | self.vis, |
794 | false, |
795 | None, |
796 | false, |
797 | None, |
798 | )?; |
799 | attrs.check_used(); |
800 | Ok(ret.0) |
801 | } |
802 | } |
803 | |
804 | pub(crate) fn is_js_keyword(keyword: &str, skip: Option<&[&str]>) -> bool { |
805 | JS_KEYWORDS |
806 | .iter() |
807 | .filter(|keyword: &&&str| skip.filter(|skip: &&[&str]| skip.contains(keyword)).is_none()) |
808 | .any(|this: &&str| *this == keyword) |
809 | } |
810 | |
811 | /// Construct a function (and gets the self type if appropriate) for our AST from a syn function. |
812 | #[allow (clippy::too_many_arguments)] |
813 | fn function_from_decl( |
814 | decl_name: &syn::Ident, |
815 | opts: &BindgenAttrs, |
816 | sig: syn::Signature, |
817 | attrs: Vec<syn::Attribute>, |
818 | vis: syn::Visibility, |
819 | allow_self: bool, |
820 | self_ty: Option<&Ident>, |
821 | is_from_impl: bool, |
822 | skip_keywords: Option<&[&str]>, |
823 | ) -> Result<(ast::Function, Option<ast::MethodSelf>), Diagnostic> { |
824 | if sig.variadic.is_some() { |
825 | bail_span!(sig.variadic, "can't #[wasm_bindgen] variadic functions" ); |
826 | } |
827 | if !sig.generics.params.is_empty() { |
828 | bail_span!( |
829 | sig.generics, |
830 | "can't #[wasm_bindgen] functions with lifetime or type parameters" , |
831 | ); |
832 | } |
833 | |
834 | assert_no_lifetimes(&sig)?; |
835 | |
836 | let syn::Signature { inputs, output, .. } = sig; |
837 | |
838 | let replace_self = |t: syn::Type| { |
839 | let self_ty = match self_ty { |
840 | Some(i) => i, |
841 | None => return t, |
842 | }; |
843 | let path = match get_ty(&t) { |
844 | syn::Type::Path(syn::TypePath { qself: None, path }) => path.clone(), |
845 | other => return other.clone(), |
846 | }; |
847 | let new_path = if path.segments.len() == 1 && path.segments[0].ident == "Self" { |
848 | self_ty.clone().into() |
849 | } else { |
850 | path |
851 | }; |
852 | syn::Type::Path(syn::TypePath { |
853 | qself: None, |
854 | path: new_path, |
855 | }) |
856 | }; |
857 | |
858 | let replace_colliding_arg = |i: &mut syn::PatType| { |
859 | if let syn::Pat::Ident(ref mut i) = *i.pat { |
860 | let ident = i.ident.to_string(); |
861 | if is_js_keyword(ident.as_str(), skip_keywords) { |
862 | i.ident = Ident::new(format!("_ {}" , ident).as_str(), i.ident.span()); |
863 | } |
864 | } |
865 | }; |
866 | |
867 | let mut method_self = None; |
868 | let arguments = inputs |
869 | .into_iter() |
870 | .filter_map(|arg| match arg { |
871 | syn::FnArg::Typed(mut c) => { |
872 | replace_colliding_arg(&mut c); |
873 | c.ty = Box::new(replace_self(*c.ty)); |
874 | Some(c) |
875 | } |
876 | syn::FnArg::Receiver(r) => { |
877 | if !allow_self { |
878 | panic!("arguments cannot be `self`" ) |
879 | } |
880 | assert!(method_self.is_none()); |
881 | if r.reference.is_none() { |
882 | method_self = Some(ast::MethodSelf::ByValue); |
883 | } else if r.mutability.is_some() { |
884 | method_self = Some(ast::MethodSelf::RefMutable); |
885 | } else { |
886 | method_self = Some(ast::MethodSelf::RefShared); |
887 | } |
888 | None |
889 | } |
890 | }) |
891 | .collect::<Vec<_>>(); |
892 | |
893 | let ret = match output { |
894 | syn::ReturnType::Default => None, |
895 | syn::ReturnType::Type(_, ty) => Some(replace_self(*ty)), |
896 | }; |
897 | |
898 | let (name, name_span, renamed_via_js_name) = |
899 | if let Some((js_name, js_name_span)) = opts.js_name() { |
900 | let kind = operation_kind(opts); |
901 | let prefix = match kind { |
902 | OperationKind::Setter(_) => "set_" , |
903 | _ => "" , |
904 | }; |
905 | let name = if prefix.is_empty() |
906 | && opts.method().is_none() |
907 | && is_js_keyword(js_name, skip_keywords) |
908 | { |
909 | format!("_ {}" , js_name) |
910 | } else { |
911 | format!(" {}{}" , prefix, js_name) |
912 | }; |
913 | (name, js_name_span, true) |
914 | } else { |
915 | let name = if !is_from_impl |
916 | && opts.method().is_none() |
917 | && is_js_keyword(&decl_name.to_string(), skip_keywords) |
918 | { |
919 | format!("_ {}" , decl_name) |
920 | } else { |
921 | decl_name.to_string() |
922 | }; |
923 | (name, decl_name.span(), false) |
924 | }; |
925 | Ok(( |
926 | ast::Function { |
927 | arguments, |
928 | name_span, |
929 | name, |
930 | renamed_via_js_name, |
931 | ret, |
932 | rust_attrs: attrs, |
933 | rust_vis: vis, |
934 | r#unsafe: sig.unsafety.is_some(), |
935 | r#async: sig.asyncness.is_some(), |
936 | generate_typescript: opts.skip_typescript().is_none(), |
937 | generate_jsdoc: opts.skip_jsdoc().is_none(), |
938 | variadic: opts.variadic().is_some(), |
939 | }, |
940 | method_self, |
941 | )) |
942 | } |
943 | |
944 | pub(crate) trait MacroParse<Ctx> { |
945 | /// Parse the contents of an object into our AST, with a context if necessary. |
946 | /// |
947 | /// The context is used to have access to the attributes on `#[wasm_bindgen]`, and to allow |
948 | /// writing to the output `TokenStream`. |
949 | fn macro_parse(self, program: &mut ast::Program, context: Ctx) -> Result<(), Diagnostic>; |
950 | } |
951 | |
952 | impl<'a> MacroParse<(Option<BindgenAttrs>, &'a mut TokenStream)> for syn::Item { |
953 | fn macro_parse( |
954 | self, |
955 | program: &mut ast::Program, |
956 | (opts, tokens): (Option<BindgenAttrs>, &'a mut TokenStream), |
957 | ) -> Result<(), Diagnostic> { |
958 | match self { |
959 | syn::Item::Fn(mut f) => { |
960 | let opts = opts.unwrap_or_default(); |
961 | if let Some(path) = opts.wasm_bindgen() { |
962 | program.wasm_bindgen = path.clone(); |
963 | } |
964 | if let Some(path) = opts.wasm_bindgen_futures() { |
965 | program.wasm_bindgen_futures = path.clone(); |
966 | } |
967 | |
968 | if opts.main().is_some() { |
969 | opts.check_used(); |
970 | return main(program, f, tokens); |
971 | } |
972 | |
973 | let no_mangle = f |
974 | .attrs |
975 | .iter() |
976 | .enumerate() |
977 | .find(|(_, m)| m.path().is_ident("no_mangle" )); |
978 | if let Some((i, _)) = no_mangle { |
979 | f.attrs.remove(i); |
980 | } |
981 | let comments = extract_doc_comments(&f.attrs); |
982 | // If the function isn't used for anything other than being exported to JS, |
983 | // it'll be unused when not building for the wasm target and produce a |
984 | // `dead_code` warning. So, add `#[allow(dead_code)]` before it to avoid that. |
985 | tokens.extend(quote::quote! { #[allow(dead_code)] }); |
986 | f.to_tokens(tokens); |
987 | if opts.start().is_some() { |
988 | if !f.sig.generics.params.is_empty() { |
989 | bail_span!(&f.sig.generics, "the start function cannot have generics" ,); |
990 | } |
991 | if !f.sig.inputs.is_empty() { |
992 | bail_span!(&f.sig.inputs, "the start function cannot have arguments" ,); |
993 | } |
994 | } |
995 | let method_kind = ast::MethodKind::Operation(ast::Operation { |
996 | is_static: true, |
997 | kind: operation_kind(&opts), |
998 | }); |
999 | let rust_name = f.sig.ident.clone(); |
1000 | let start = opts.start().is_some(); |
1001 | program.exports.push(ast::Export { |
1002 | comments, |
1003 | function: f.convert(opts)?, |
1004 | js_class: None, |
1005 | method_kind, |
1006 | method_self: None, |
1007 | rust_class: None, |
1008 | rust_name, |
1009 | start, |
1010 | wasm_bindgen: program.wasm_bindgen.clone(), |
1011 | wasm_bindgen_futures: program.wasm_bindgen_futures.clone(), |
1012 | }); |
1013 | } |
1014 | syn::Item::Struct(mut s) => { |
1015 | let opts = opts.unwrap_or_default(); |
1016 | program.structs.push((&mut s).convert((program, opts))?); |
1017 | s.to_tokens(tokens); |
1018 | } |
1019 | syn::Item::Impl(mut i) => { |
1020 | let opts = opts.unwrap_or_default(); |
1021 | (&mut i).macro_parse(program, opts)?; |
1022 | i.to_tokens(tokens); |
1023 | } |
1024 | syn::Item::ForeignMod(mut f) => { |
1025 | let opts = match opts { |
1026 | Some(opts) => opts, |
1027 | None => BindgenAttrs::find(&mut f.attrs)?, |
1028 | }; |
1029 | f.macro_parse(program, opts)?; |
1030 | } |
1031 | syn::Item::Enum(mut e) => { |
1032 | let opts = match opts { |
1033 | Some(opts) => opts, |
1034 | None => BindgenAttrs::find(&mut e.attrs)?, |
1035 | }; |
1036 | e.macro_parse(program, (tokens, opts))?; |
1037 | } |
1038 | syn::Item::Const(mut c) => { |
1039 | let opts = match opts { |
1040 | Some(opts) => opts, |
1041 | None => BindgenAttrs::find(&mut c.attrs)?, |
1042 | }; |
1043 | c.macro_parse(program, opts)?; |
1044 | } |
1045 | _ => { |
1046 | bail_span!( |
1047 | self, |
1048 | "#[wasm_bindgen] can only be applied to a function, \ |
1049 | struct, enum, impl, or extern block" , |
1050 | ); |
1051 | } |
1052 | } |
1053 | |
1054 | Ok(()) |
1055 | } |
1056 | } |
1057 | |
1058 | impl<'a> MacroParse<BindgenAttrs> for &'a mut syn::ItemImpl { |
1059 | fn macro_parse(self, program: &mut ast::Program, opts: BindgenAttrs) -> Result<(), Diagnostic> { |
1060 | if self.defaultness.is_some() { |
1061 | bail_span!( |
1062 | self.defaultness, |
1063 | "#[wasm_bindgen] default impls are not supported" |
1064 | ); |
1065 | } |
1066 | if self.unsafety.is_some() { |
1067 | bail_span!( |
1068 | self.unsafety, |
1069 | "#[wasm_bindgen] unsafe impls are not supported" |
1070 | ); |
1071 | } |
1072 | if let Some((_, path, _)) = &self.trait_ { |
1073 | bail_span!(path, "#[wasm_bindgen] trait impls are not supported" ); |
1074 | } |
1075 | if !self.generics.params.is_empty() { |
1076 | bail_span!( |
1077 | self.generics, |
1078 | "#[wasm_bindgen] generic impls aren't supported" |
1079 | ); |
1080 | } |
1081 | let name = match get_ty(&self.self_ty) { |
1082 | syn::Type::Path(syn::TypePath { |
1083 | qself: None, |
1084 | ref path, |
1085 | }) => path, |
1086 | _ => bail_span!( |
1087 | self.self_ty, |
1088 | "unsupported self type in #[wasm_bindgen] impl" |
1089 | ), |
1090 | }; |
1091 | let mut errors = Vec::new(); |
1092 | for item in self.items.iter_mut() { |
1093 | if let Err(e) = prepare_for_impl_recursion(item, name, program, &opts) { |
1094 | errors.push(e); |
1095 | } |
1096 | } |
1097 | Diagnostic::from_vec(errors)?; |
1098 | opts.check_used(); |
1099 | Ok(()) |
1100 | } |
1101 | } |
1102 | |
1103 | // Prepare for recursion into an `impl` block. Here we want to attach an |
1104 | // internal attribute, `__wasm_bindgen_class_marker`, with any metadata we need |
1105 | // to pass from the impl to the impl item. Recursive macro expansion will then |
1106 | // expand the `__wasm_bindgen_class_marker` attribute. |
1107 | // |
1108 | // Note that we currently do this because inner items may have things like cfgs |
1109 | // on them, so we want to expand the impl first, let the insides get cfg'd, and |
1110 | // then go for the rest. |
1111 | fn prepare_for_impl_recursion( |
1112 | item: &mut syn::ImplItem, |
1113 | class: &syn::Path, |
1114 | program: &ast::Program, |
1115 | impl_opts: &BindgenAttrs, |
1116 | ) -> Result<(), Diagnostic> { |
1117 | let method = match item { |
1118 | syn::ImplItem::Fn(m) => m, |
1119 | syn::ImplItem::Const(_) => { |
1120 | bail_span!( |
1121 | &*item, |
1122 | "const definitions aren't supported with #[wasm_bindgen]" |
1123 | ); |
1124 | } |
1125 | syn::ImplItem::Type(_) => bail_span!( |
1126 | &*item, |
1127 | "type definitions in impls aren't supported with #[wasm_bindgen]" |
1128 | ), |
1129 | syn::ImplItem::Macro(_) => { |
1130 | // In theory we want to allow this, but we have no way of expanding |
1131 | // the macro and then placing our magical attributes on the expanded |
1132 | // functions. As a result, just disallow it for now to hopefully |
1133 | // ward off buggy results from this macro. |
1134 | bail_span!(&*item, "macros in impls aren't supported" ); |
1135 | } |
1136 | syn::ImplItem::Verbatim(_) => panic!("unparsed impl item?" ), |
1137 | other => bail_span!(other, "failed to parse this item as a known item" ), |
1138 | }; |
1139 | |
1140 | let ident = extract_path_ident(class)?; |
1141 | |
1142 | let js_class = impl_opts |
1143 | .js_class() |
1144 | .map(|s| s.0.to_string()) |
1145 | .unwrap_or(ident.to_string()); |
1146 | |
1147 | let wasm_bindgen = &program.wasm_bindgen; |
1148 | let wasm_bindgen_futures = &program.wasm_bindgen_futures; |
1149 | method.attrs.insert( |
1150 | 0, |
1151 | syn::Attribute { |
1152 | pound_token: Default::default(), |
1153 | style: syn::AttrStyle::Outer, |
1154 | bracket_token: Default::default(), |
1155 | meta: syn::parse_quote! { #wasm_bindgen::prelude::__wasm_bindgen_class_marker(#class = #js_class, wasm_bindgen = #wasm_bindgen, wasm_bindgen_futures = #wasm_bindgen_futures) }, |
1156 | }, |
1157 | ); |
1158 | |
1159 | Ok(()) |
1160 | } |
1161 | |
1162 | impl<'a> MacroParse<&ClassMarker> for &'a mut syn::ImplItemFn { |
1163 | fn macro_parse( |
1164 | self, |
1165 | program: &mut ast::Program, |
1166 | ClassMarker { |
1167 | class, |
1168 | js_class, |
1169 | wasm_bindgen, |
1170 | wasm_bindgen_futures, |
1171 | }: &ClassMarker, |
1172 | ) -> Result<(), Diagnostic> { |
1173 | program.wasm_bindgen = wasm_bindgen.clone(); |
1174 | program.wasm_bindgen_futures = wasm_bindgen_futures.clone(); |
1175 | |
1176 | match self.vis { |
1177 | syn::Visibility::Public(_) => {} |
1178 | _ => return Ok(()), |
1179 | } |
1180 | if self.defaultness.is_some() { |
1181 | panic!("default methods are not supported" ); |
1182 | } |
1183 | if self.sig.constness.is_some() { |
1184 | bail_span!( |
1185 | self.sig.constness, |
1186 | "can only #[wasm_bindgen] non-const functions" , |
1187 | ); |
1188 | } |
1189 | |
1190 | let opts = BindgenAttrs::find(&mut self.attrs)?; |
1191 | let comments = extract_doc_comments(&self.attrs); |
1192 | let (function, method_self) = function_from_decl( |
1193 | &self.sig.ident, |
1194 | &opts, |
1195 | self.sig.clone(), |
1196 | self.attrs.clone(), |
1197 | self.vis.clone(), |
1198 | true, |
1199 | Some(class), |
1200 | true, |
1201 | None, |
1202 | )?; |
1203 | let method_kind = if opts.constructor().is_some() { |
1204 | ast::MethodKind::Constructor |
1205 | } else { |
1206 | let is_static = method_self.is_none(); |
1207 | let kind = operation_kind(&opts); |
1208 | ast::MethodKind::Operation(ast::Operation { is_static, kind }) |
1209 | }; |
1210 | program.exports.push(ast::Export { |
1211 | comments, |
1212 | function, |
1213 | js_class: Some(js_class.to_string()), |
1214 | method_kind, |
1215 | method_self, |
1216 | rust_class: Some(class.clone()), |
1217 | rust_name: self.sig.ident.clone(), |
1218 | start: false, |
1219 | wasm_bindgen: program.wasm_bindgen.clone(), |
1220 | wasm_bindgen_futures: program.wasm_bindgen_futures.clone(), |
1221 | }); |
1222 | opts.check_used(); |
1223 | Ok(()) |
1224 | } |
1225 | } |
1226 | |
1227 | fn import_enum(enum_: syn::ItemEnum, program: &mut ast::Program) -> Result<(), Diagnostic> { |
1228 | let mut variants = vec![]; |
1229 | let mut variant_values = vec![]; |
1230 | |
1231 | for v in enum_.variants.iter() { |
1232 | match v.fields { |
1233 | syn::Fields::Unit => (), |
1234 | _ => bail_span!(v.fields, "only C-Style enums allowed with #[wasm_bindgen]" ), |
1235 | } |
1236 | |
1237 | let (_, expr) = match &v.discriminant { |
1238 | Some(pair) => pair, |
1239 | None => { |
1240 | bail_span!(v, "all variants must have a value" ); |
1241 | } |
1242 | }; |
1243 | match get_expr(expr) { |
1244 | syn::Expr::Lit(syn::ExprLit { |
1245 | attrs: _, |
1246 | lit: syn::Lit::Str(str_lit), |
1247 | }) => { |
1248 | variants.push(v.ident.clone()); |
1249 | variant_values.push(str_lit.value()); |
1250 | } |
1251 | expr => bail_span!( |
1252 | expr, |
1253 | "enums with #[wasm_bindgen] cannot mix string and non-string values" , |
1254 | ), |
1255 | } |
1256 | } |
1257 | |
1258 | program.imports.push(ast::Import { |
1259 | module: None, |
1260 | js_namespace: None, |
1261 | kind: ast::ImportKind::Enum(ast::ImportEnum { |
1262 | vis: enum_.vis, |
1263 | name: enum_.ident, |
1264 | variants, |
1265 | variant_values, |
1266 | rust_attrs: enum_.attrs, |
1267 | wasm_bindgen: program.wasm_bindgen.clone(), |
1268 | }), |
1269 | }); |
1270 | |
1271 | Ok(()) |
1272 | } |
1273 | |
1274 | impl<'a> MacroParse<(&'a mut TokenStream, BindgenAttrs)> for syn::ItemEnum { |
1275 | fn macro_parse( |
1276 | self, |
1277 | program: &mut ast::Program, |
1278 | (tokens, opts): (&'a mut TokenStream, BindgenAttrs), |
1279 | ) -> Result<(), Diagnostic> { |
1280 | if self.variants.is_empty() { |
1281 | bail_span!(self, "cannot export empty enums to JS" ); |
1282 | } |
1283 | let generate_typescript = opts.skip_typescript().is_none(); |
1284 | |
1285 | // Check if the first value is a string literal |
1286 | if let Some((_, expr)) = &self.variants[0].discriminant { |
1287 | if let syn::Expr::Lit(syn::ExprLit { |
1288 | lit: syn::Lit::Str(_), |
1289 | .. |
1290 | }) = get_expr(expr) |
1291 | { |
1292 | opts.check_used(); |
1293 | return import_enum(self, program); |
1294 | } |
1295 | } |
1296 | let js_name = opts |
1297 | .js_name() |
1298 | .map(|s| s.0) |
1299 | .map_or_else(|| self.ident.to_string(), |s| s.to_string()); |
1300 | opts.check_used(); |
1301 | |
1302 | let has_discriminant = self.variants[0].discriminant.is_some(); |
1303 | |
1304 | match self.vis { |
1305 | syn::Visibility::Public(_) => {} |
1306 | _ => bail_span!(self, "only public enums are allowed with #[wasm_bindgen]" ), |
1307 | } |
1308 | |
1309 | let variants = self |
1310 | .variants |
1311 | .iter() |
1312 | .enumerate() |
1313 | .map(|(i, v)| { |
1314 | match v.fields { |
1315 | syn::Fields::Unit => (), |
1316 | _ => bail_span!(v.fields, "only C-Style enums allowed with #[wasm_bindgen]" ), |
1317 | } |
1318 | |
1319 | // Require that everything either has a discriminant or doesn't. |
1320 | // We don't really want to get in the business of emulating how |
1321 | // rustc assigns values to enums. |
1322 | if v.discriminant.is_some() != has_discriminant { |
1323 | bail_span!( |
1324 | v, |
1325 | "must either annotate discriminant of all variants or none" |
1326 | ); |
1327 | } |
1328 | |
1329 | let value = match &v.discriminant { |
1330 | Some((_, expr)) => match get_expr(expr) { |
1331 | syn::Expr::Lit(syn::ExprLit { |
1332 | attrs: _, |
1333 | lit: syn::Lit::Int(int_lit), |
1334 | }) => match int_lit.base10_digits().parse::<u32>() { |
1335 | Ok(v) => v, |
1336 | Err(_) => { |
1337 | bail_span!( |
1338 | int_lit, |
1339 | "enums with #[wasm_bindgen] can only support \ |
1340 | numbers that can be represented as u32" |
1341 | ); |
1342 | } |
1343 | }, |
1344 | expr => bail_span!( |
1345 | expr, |
1346 | "enums with #[wasm_bindgen] may only have \ |
1347 | number literal values" , |
1348 | ), |
1349 | }, |
1350 | None => i as u32, |
1351 | }; |
1352 | |
1353 | let comments = extract_doc_comments(&v.attrs); |
1354 | Ok(ast::Variant { |
1355 | name: v.ident.clone(), |
1356 | value, |
1357 | comments, |
1358 | }) |
1359 | }) |
1360 | .collect::<Result<Vec<_>, Diagnostic>>()?; |
1361 | |
1362 | let mut values = variants.iter().map(|v| v.value).collect::<Vec<_>>(); |
1363 | values.sort(); |
1364 | let hole = values |
1365 | .windows(2) |
1366 | .find_map(|window| { |
1367 | if window[0] + 1 != window[1] { |
1368 | Some(window[0] + 1) |
1369 | } else { |
1370 | None |
1371 | } |
1372 | }) |
1373 | .unwrap_or(*values.last().unwrap() + 1); |
1374 | for value in values { |
1375 | assert!(hole != value); |
1376 | } |
1377 | |
1378 | let comments = extract_doc_comments(&self.attrs); |
1379 | |
1380 | self.to_tokens(tokens); |
1381 | |
1382 | program.enums.push(ast::Enum { |
1383 | rust_name: self.ident, |
1384 | js_name, |
1385 | variants, |
1386 | comments, |
1387 | hole, |
1388 | generate_typescript, |
1389 | wasm_bindgen: program.wasm_bindgen.clone(), |
1390 | }); |
1391 | Ok(()) |
1392 | } |
1393 | } |
1394 | |
1395 | impl MacroParse<BindgenAttrs> for syn::ItemConst { |
1396 | fn macro_parse(self, program: &mut ast::Program, opts: BindgenAttrs) -> Result<(), Diagnostic> { |
1397 | // Shortcut |
1398 | if opts.typescript_custom_section().is_none() { |
1399 | bail_span!(self, "#[wasm_bindgen] will not work on constants unless you are defining a #[wasm_bindgen(typescript_custom_section)]." ); |
1400 | } |
1401 | |
1402 | match get_expr(&self.expr) { |
1403 | syn::Expr::Lit(syn::ExprLit { |
1404 | lit: syn::Lit::Str(litstr: &LitStr), |
1405 | .. |
1406 | }) => { |
1407 | program.typescript_custom_sections.push(litstr.value()); |
1408 | } |
1409 | expr: &Expr => { |
1410 | bail_span!(expr, "Expected a string literal to be used with #[wasm_bindgen(typescript_custom_section)]." ); |
1411 | } |
1412 | } |
1413 | |
1414 | opts.check_used(); |
1415 | |
1416 | Ok(()) |
1417 | } |
1418 | } |
1419 | |
1420 | impl MacroParse<BindgenAttrs> for syn::ItemForeignMod { |
1421 | fn macro_parse(self, program: &mut ast::Program, opts: BindgenAttrs) -> Result<(), Diagnostic> { |
1422 | let mut errors = Vec::new(); |
1423 | if let Some(other) = self.abi.name.filter(|l| l.value() != "C" ) { |
1424 | errors.push(err_span!( |
1425 | other, |
1426 | "only foreign mods with the `C` ABI are allowed" |
1427 | )); |
1428 | } |
1429 | let js_namespace = opts.js_namespace().map(|(s, _)| s.to_owned()); |
1430 | let module = module_from_opts(program, &opts) |
1431 | .map_err(|e| errors.push(e)) |
1432 | .unwrap_or_default(); |
1433 | for item in self.items.into_iter() { |
1434 | let ctx = ForeignItemCtx { |
1435 | module: module.clone(), |
1436 | js_namespace: js_namespace.clone(), |
1437 | }; |
1438 | if let Err(e) = item.macro_parse(program, ctx) { |
1439 | errors.push(e); |
1440 | } |
1441 | } |
1442 | Diagnostic::from_vec(errors)?; |
1443 | opts.check_used(); |
1444 | Ok(()) |
1445 | } |
1446 | } |
1447 | |
1448 | struct ForeignItemCtx { |
1449 | module: Option<ast::ImportModule>, |
1450 | js_namespace: Option<Vec<String>>, |
1451 | } |
1452 | |
1453 | impl MacroParse<ForeignItemCtx> for syn::ForeignItem { |
1454 | fn macro_parse( |
1455 | mut self, |
1456 | program: &mut ast::Program, |
1457 | ctx: ForeignItemCtx, |
1458 | ) -> Result<(), Diagnostic> { |
1459 | let item_opts = { |
1460 | let attrs = match self { |
1461 | syn::ForeignItem::Fn(ref mut f) => &mut f.attrs, |
1462 | syn::ForeignItem::Type(ref mut t) => &mut t.attrs, |
1463 | syn::ForeignItem::Static(ref mut s) => &mut s.attrs, |
1464 | _ => panic!("only foreign functions/types allowed for now" ), |
1465 | }; |
1466 | BindgenAttrs::find(attrs)? |
1467 | }; |
1468 | |
1469 | let js_namespace = item_opts |
1470 | .js_namespace() |
1471 | .map(|(s, _)| s.to_owned()) |
1472 | .or(ctx.js_namespace); |
1473 | let module = ctx.module; |
1474 | |
1475 | let kind = match self { |
1476 | syn::ForeignItem::Fn(f) => f.convert((program, item_opts, &module))?, |
1477 | syn::ForeignItem::Type(t) => t.convert((program, item_opts))?, |
1478 | syn::ForeignItem::Static(s) => s.convert((program, item_opts, &module))?, |
1479 | _ => panic!("only foreign functions/types allowed for now" ), |
1480 | }; |
1481 | |
1482 | program.imports.push(ast::Import { |
1483 | module, |
1484 | js_namespace, |
1485 | kind, |
1486 | }); |
1487 | |
1488 | Ok(()) |
1489 | } |
1490 | } |
1491 | |
1492 | pub fn module_from_opts( |
1493 | program: &mut ast::Program, |
1494 | opts: &BindgenAttrs, |
1495 | ) -> Result<Option<ast::ImportModule>, Diagnostic> { |
1496 | if let Some(path) = opts.wasm_bindgen() { |
1497 | program.wasm_bindgen = path.clone(); |
1498 | } |
1499 | |
1500 | if let Some(path) = opts.wasm_bindgen_futures() { |
1501 | program.wasm_bindgen_futures = path.clone(); |
1502 | } |
1503 | |
1504 | let mut errors = Vec::new(); |
1505 | let module = if let Some((name, span)) = opts.module() { |
1506 | if opts.inline_js().is_some() { |
1507 | let msg = "cannot specify both `module` and `inline_js`" ; |
1508 | errors.push(Diagnostic::span_error(span, msg)); |
1509 | } |
1510 | if opts.raw_module().is_some() { |
1511 | let msg = "cannot specify both `module` and `raw_module`" ; |
1512 | errors.push(Diagnostic::span_error(span, msg)); |
1513 | } |
1514 | Some(ast::ImportModule::Named(name.to_string(), span)) |
1515 | } else if let Some((name, span)) = opts.raw_module() { |
1516 | if opts.inline_js().is_some() { |
1517 | let msg = "cannot specify both `raw_module` and `inline_js`" ; |
1518 | errors.push(Diagnostic::span_error(span, msg)); |
1519 | } |
1520 | Some(ast::ImportModule::RawNamed(name.to_string(), span)) |
1521 | } else if let Some((js, span)) = opts.inline_js() { |
1522 | let i = program.inline_js.len(); |
1523 | program.inline_js.push(js.to_string()); |
1524 | Some(ast::ImportModule::Inline(i, span)) |
1525 | } else { |
1526 | None |
1527 | }; |
1528 | Diagnostic::from_vec(errors)?; |
1529 | Ok(module) |
1530 | } |
1531 | |
1532 | /// Get the first type parameter of a generic type, errors on incorrect input. |
1533 | fn extract_first_ty_param(ty: Option<&syn::Type>) -> Result<Option<syn::Type>, Diagnostic> { |
1534 | let t = match ty { |
1535 | Some(t) => t, |
1536 | None => return Ok(None), |
1537 | }; |
1538 | let path = match *get_ty(t) { |
1539 | syn::Type::Path(syn::TypePath { |
1540 | qself: None, |
1541 | ref path, |
1542 | }) => path, |
1543 | _ => bail_span!(t, "must be Result<...>" ), |
1544 | }; |
1545 | let seg = path |
1546 | .segments |
1547 | .last() |
1548 | .ok_or_else(|| err_span!(t, "must have at least one segment" ))?; |
1549 | let generics = match seg.arguments { |
1550 | syn::PathArguments::AngleBracketed(ref t) => t, |
1551 | _ => bail_span!(t, "must be Result<...>" ), |
1552 | }; |
1553 | let generic = generics |
1554 | .args |
1555 | .first() |
1556 | .ok_or_else(|| err_span!(t, "must have at least one generic parameter" ))?; |
1557 | let ty = match generic { |
1558 | syn::GenericArgument::Type(t) => t, |
1559 | other => bail_span!(other, "must be a type parameter" ), |
1560 | }; |
1561 | match get_ty(ty) { |
1562 | syn::Type::Tuple(t) if t.elems.is_empty() => return Ok(None), |
1563 | _ => {} |
1564 | } |
1565 | Ok(Some(ty.clone())) |
1566 | } |
1567 | |
1568 | /// Extract the documentation comments from a Vec of attributes |
1569 | fn extract_doc_comments(attrs: &[syn::Attribute]) -> Vec<String> { |
1570 | attrs |
1571 | .iter() |
1572 | .filter_map(|a| { |
1573 | // if the path segments include an ident of "doc" we know this |
1574 | // this is a doc comment |
1575 | if a.path().segments.iter().any(|s| s.ident == "doc" ) { |
1576 | let tokens = match &a.meta { |
1577 | syn::Meta::Path(_) => None, |
1578 | syn::Meta::List(list) => Some(list.tokens.clone()), |
1579 | syn::Meta::NameValue(name_value) => Some(name_value.value.to_token_stream()), |
1580 | }; |
1581 | |
1582 | Some( |
1583 | // We want to filter out any Puncts so just grab the Literals |
1584 | tokens.into_iter().flatten().filter_map(|t| match t { |
1585 | TokenTree::Literal(lit) => { |
1586 | let quoted = lit.to_string(); |
1587 | Some(try_unescape("ed).unwrap_or(quoted)) |
1588 | } |
1589 | _ => None, |
1590 | }), |
1591 | ) |
1592 | } else { |
1593 | None |
1594 | } |
1595 | }) |
1596 | //Fold up the [[String]] iter we created into Vec<String> |
1597 | .fold(vec![], |mut acc, a| { |
1598 | acc.extend(a); |
1599 | acc |
1600 | }) |
1601 | } |
1602 | |
1603 | // Unescapes a quoted string. char::escape_debug() was used to escape the text. |
1604 | fn try_unescape(mut s: &str) -> Option<String> { |
1605 | s = s.strip_prefix('"' ).unwrap_or(s); |
1606 | s = s.strip_suffix('"' ).unwrap_or(s); |
1607 | let mut result = String::with_capacity(s.len()); |
1608 | let mut chars = s.chars(); |
1609 | while let Some(c) = chars.next() { |
1610 | if c == ' \\' { |
1611 | let c = chars.next()?; |
1612 | match c { |
1613 | 't' => result.push(' \t' ), |
1614 | 'r' => result.push(' \r' ), |
1615 | 'n' => result.push(' \n' ), |
1616 | ' \\' | ' \'' | '"' => result.push(c), |
1617 | 'u' => { |
1618 | if chars.next() != Some('{' ) { |
1619 | return None; |
1620 | } |
1621 | let (c, next) = unescape_unicode(&mut chars)?; |
1622 | result.push(c); |
1623 | if next != '}' { |
1624 | return None; |
1625 | } |
1626 | } |
1627 | _ => return None, |
1628 | } |
1629 | } else { |
1630 | result.push(c); |
1631 | } |
1632 | } |
1633 | Some(result) |
1634 | } |
1635 | |
1636 | fn unescape_unicode(chars: &mut Chars) -> Option<(char, char)> { |
1637 | let mut value: u32 = 0; |
1638 | for (i: usize, c: char) in chars.enumerate() { |
1639 | match (i, c.to_digit(radix:16)) { |
1640 | (0..=5, Some(num: u32)) => value = (value << 4) | num, |
1641 | (1.., None) => return Some((char::from_u32(value)?, c)), |
1642 | _ => break, |
1643 | } |
1644 | } |
1645 | None |
1646 | } |
1647 | |
1648 | /// Check there are no lifetimes on the function. |
1649 | fn assert_no_lifetimes(sig: &syn::Signature) -> Result<(), Diagnostic> { |
1650 | struct Walk { |
1651 | diagnostics: Vec<Diagnostic>, |
1652 | } |
1653 | |
1654 | impl<'ast> syn::visit::Visit<'ast> for Walk { |
1655 | fn visit_lifetime(&mut self, i: &'ast syn::Lifetime) { |
1656 | self.diagnostics.push(err_span!( |
1657 | i, |
1658 | "it is currently not sound to use lifetimes in function \ |
1659 | signatures" |
1660 | )); |
1661 | } |
1662 | } |
1663 | let mut walk: Walk = Walk { |
1664 | diagnostics: Vec::new(), |
1665 | }; |
1666 | syn::visit::Visit::visit_signature(&mut walk, i:sig); |
1667 | Diagnostic::from_vec(walk.diagnostics) |
1668 | } |
1669 | |
1670 | /// Extracts the last ident from the path |
1671 | fn extract_path_ident(path: &syn::Path) -> Result<Ident, Diagnostic> { |
1672 | for segment: &PathSegment in path.segments.iter() { |
1673 | match segment.arguments { |
1674 | syn::PathArguments::None => {} |
1675 | _ => bail_span!(path, "paths with type parameters are not supported yet" ), |
1676 | } |
1677 | } |
1678 | |
1679 | match path.segments.last() { |
1680 | Some(value: &PathSegment) => Ok(value.ident.clone()), |
1681 | None => { |
1682 | bail_span!(path, "empty idents are not supported" ); |
1683 | } |
1684 | } |
1685 | } |
1686 | |
1687 | pub fn reset_attrs_used() { |
1688 | ATTRS.with(|state: &AttributeParseState| { |
1689 | state.parsed.set(val:0); |
1690 | state.checks.set(val:0); |
1691 | state.unused_attrs.borrow_mut().clear(); |
1692 | }) |
1693 | } |
1694 | |
1695 | pub fn check_unused_attrs(tokens: &mut TokenStream) { |
1696 | ATTRS.with(|state: &AttributeParseState| { |
1697 | assert_eq!(state.parsed.get(), state.checks.get()); |
1698 | let unused_attrs: &Vec = &*state.unused_attrs.borrow(); |
1699 | if !unused_attrs.is_empty() { |
1700 | tokens.extend(iter:quote::quote! { |
1701 | // Anonymous scope to prevent name clashes. |
1702 | const _: () = { |
1703 | #(let #unused_attrs: ();)* |
1704 | }; |
1705 | }); |
1706 | } |
1707 | }) |
1708 | } |
1709 | |
1710 | fn operation_kind(opts: &BindgenAttrs) -> ast::OperationKind { |
1711 | let mut operation_kind: OperationKind = ast::OperationKind::Regular; |
1712 | if let Some(g: &Option) = opts.getter() { |
1713 | operation_kind = ast::OperationKind::Getter(g.clone()); |
1714 | } |
1715 | if let Some(s: &Option) = opts.setter() { |
1716 | operation_kind = ast::OperationKind::Setter(s.clone()); |
1717 | } |
1718 | if opts.indexing_getter().is_some() { |
1719 | operation_kind = ast::OperationKind::IndexingGetter; |
1720 | } |
1721 | if opts.indexing_setter().is_some() { |
1722 | operation_kind = ast::OperationKind::IndexingSetter; |
1723 | } |
1724 | if opts.indexing_deleter().is_some() { |
1725 | operation_kind = ast::OperationKind::IndexingDeleter; |
1726 | } |
1727 | operation_kind |
1728 | } |
1729 | |
1730 | pub fn link_to(opts: BindgenAttrs) -> Result<ast::LinkToModule, Diagnostic> { |
1731 | let mut program: Program = ast::Program::default(); |
1732 | let module: ImportModule = module_from_opts(&mut program, &opts)?.ok_or_else(|| { |
1733 | Diagnostic::span_error(Span::call_site(), text:"`link_to!` requires a module." ) |
1734 | })?; |
1735 | if let ast::ImportModule::Named(p: &String, s: &Span) | ast::ImportModule::RawNamed(p: &String, s: &Span) = &module { |
1736 | if !p.starts_with("./" ) && !p.starts_with("../" ) && !p.starts_with('/' ) { |
1737 | return Err(Diagnostic::span_error( |
1738 | *s, |
1739 | text:"`link_to!` does not support module paths." , |
1740 | )); |
1741 | } |
1742 | } |
1743 | opts.enforce_used()?; |
1744 | program.linked_modules.push(module); |
1745 | Ok(ast::LinkToModule(program)) |
1746 | } |
1747 | |
1748 | fn main(program: &ast::Program, mut f: ItemFn, tokens: &mut TokenStream) -> Result<(), Diagnostic> { |
1749 | if f.sig.ident != "main" { |
1750 | bail_span!(&f.sig.ident, "the main function has to be called main" ); |
1751 | } |
1752 | if let Some(constness) = f.sig.constness { |
1753 | bail_span!(&constness, "the main function cannot be const" ); |
1754 | } |
1755 | if !f.sig.generics.params.is_empty() { |
1756 | bail_span!(&f.sig.generics, "the main function cannot have generics" ); |
1757 | } |
1758 | if !f.sig.inputs.is_empty() { |
1759 | bail_span!(&f.sig.inputs, "the main function cannot have arguments" ); |
1760 | } |
1761 | |
1762 | let r#return = f.sig.output; |
1763 | f.sig.output = ReturnType::Default; |
1764 | let body = f.block; |
1765 | |
1766 | let wasm_bindgen = &program.wasm_bindgen; |
1767 | let wasm_bindgen_futures = &program.wasm_bindgen_futures; |
1768 | |
1769 | if f.sig.asyncness.take().is_some() { |
1770 | f.block = Box::new( |
1771 | syn::parse2(quote::quote! { |
1772 | { |
1773 | async fn __wasm_bindgen_generated_main() #r#return #body |
1774 | #wasm_bindgen_futures::spawn_local( |
1775 | async move { |
1776 | use #wasm_bindgen::__rt::Main; |
1777 | let __ret = __wasm_bindgen_generated_main(); |
1778 | (&mut &mut &mut #wasm_bindgen::__rt::MainWrapper(Some(__ret.await))).__wasm_bindgen_main() |
1779 | }, |
1780 | ) |
1781 | } |
1782 | }) |
1783 | .unwrap(), |
1784 | ); |
1785 | } else { |
1786 | f.block = Box::new( |
1787 | syn::parse2(quote::quote! { |
1788 | { |
1789 | fn __wasm_bindgen_generated_main() #r#return #body |
1790 | use #wasm_bindgen::__rt::Main; |
1791 | let __ret = __wasm_bindgen_generated_main(); |
1792 | (&mut &mut &mut #wasm_bindgen::__rt::MainWrapper(Some(__ret))).__wasm_bindgen_main() |
1793 | } |
1794 | }) |
1795 | .unwrap(), |
1796 | ); |
1797 | } |
1798 | |
1799 | f.to_tokens(tokens); |
1800 | |
1801 | Ok(()) |
1802 | } |
1803 | |
1804 | #[cfg (test)] |
1805 | mod tests { |
1806 | #[test ] |
1807 | fn test_try_unescape() { |
1808 | use super::try_unescape; |
1809 | assert_eq!(try_unescape("hello" ).unwrap(), "hello" ); |
1810 | assert_eq!(try_unescape(" \"hello" ).unwrap(), "hello" ); |
1811 | assert_eq!(try_unescape("hello \"" ).unwrap(), "hello" ); |
1812 | assert_eq!(try_unescape(" \"hello \"" ).unwrap(), "hello" ); |
1813 | assert_eq!(try_unescape("hello \\\\" ).unwrap(), "hello \\" ); |
1814 | assert_eq!(try_unescape("hello \\n" ).unwrap(), "hello \n" ); |
1815 | assert_eq!(try_unescape("hello \\u" ), None); |
1816 | assert_eq!(try_unescape("hello \\u{" ), None); |
1817 | assert_eq!(try_unescape("hello \\u{}" ), None); |
1818 | assert_eq!(try_unescape("hello \\u{0}" ).unwrap(), "hello \0" ); |
1819 | assert_eq!(try_unescape("hello \\u{000000}" ).unwrap(), "hello \0" ); |
1820 | assert_eq!(try_unescape("hello \\u{0000000}" ), None); |
1821 | } |
1822 | } |
1823 | |