1 | //! A representation of the Abstract Syntax Tree of a Rust program, |
2 | //! with all the added metadata necessary to generate WASM bindings |
3 | //! for it. |
4 | |
5 | use crate::{util::ShortHash, Diagnostic}; |
6 | use proc_macro2::{Ident, Span}; |
7 | use std::hash::{Hash, Hasher}; |
8 | use syn::Path; |
9 | use wasm_bindgen_shared as shared; |
10 | |
11 | /// An abstract syntax tree representing a rust program. Contains |
12 | /// extra information for joining up this rust code with javascript. |
13 | #[cfg_attr (feature = "extra-traits" , derive(Debug))] |
14 | #[derive (Clone)] |
15 | pub struct Program { |
16 | /// rust -> js interfaces |
17 | pub exports: Vec<Export>, |
18 | /// js -> rust interfaces |
19 | pub imports: Vec<Import>, |
20 | /// linked-to modules |
21 | pub linked_modules: Vec<ImportModule>, |
22 | /// rust enums |
23 | pub enums: Vec<Enum>, |
24 | /// rust structs |
25 | pub structs: Vec<Struct>, |
26 | /// custom typescript sections to be included in the definition file |
27 | pub typescript_custom_sections: Vec<String>, |
28 | /// Inline JS snippets |
29 | pub inline_js: Vec<String>, |
30 | /// Path to wasm_bindgen |
31 | pub wasm_bindgen: Path, |
32 | /// Path to wasm_bindgen_futures |
33 | pub wasm_bindgen_futures: Path, |
34 | } |
35 | |
36 | impl Default for Program { |
37 | fn default() -> Self { |
38 | Self { |
39 | exports: Default::default(), |
40 | imports: Default::default(), |
41 | linked_modules: Default::default(), |
42 | enums: Default::default(), |
43 | structs: Default::default(), |
44 | typescript_custom_sections: Default::default(), |
45 | inline_js: Default::default(), |
46 | wasm_bindgen: syn::parse_quote! { wasm_bindgen }, |
47 | wasm_bindgen_futures: syn::parse_quote! { wasm_bindgen_futures }, |
48 | } |
49 | } |
50 | } |
51 | |
52 | impl Program { |
53 | /// Returns true if the Program is empty |
54 | pub fn is_empty(&self) -> bool { |
55 | self.exports.is_empty() |
56 | && self.imports.is_empty() |
57 | && self.enums.is_empty() |
58 | && self.structs.is_empty() |
59 | && self.typescript_custom_sections.is_empty() |
60 | && self.inline_js.is_empty() |
61 | } |
62 | |
63 | /// Name of the link function for a specific linked module |
64 | pub fn link_function_name(&self, idx: usize) -> String { |
65 | let hash: String = match &self.linked_modules[idx] { |
66 | ImportModule::Inline(idx: &usize, _) => ShortHash((1, &self.inline_js[*idx])).to_string(), |
67 | other: &ImportModule => ShortHash((0, other)).to_string(), |
68 | }; |
69 | format!("__wbindgen_link_ {}" , hash) |
70 | } |
71 | } |
72 | |
73 | /// An abstract syntax tree representing a link to a module in Rust. |
74 | /// In contrast to Program, LinkToModule must expand to an expression. |
75 | /// linked_modules of the inner Program must contain exactly one element |
76 | /// whose link is produced by the expression. |
77 | #[cfg_attr (feature = "extra-traits" , derive(Debug))] |
78 | #[derive (Clone)] |
79 | pub struct LinkToModule(pub Program); |
80 | |
81 | /// A rust to js interface. Allows interaction with rust objects/functions |
82 | /// from javascript. |
83 | #[cfg_attr (feature = "extra-traits" , derive(Debug))] |
84 | #[derive (Clone)] |
85 | pub struct Export { |
86 | /// Comments extracted from the rust source. |
87 | pub comments: Vec<String>, |
88 | /// The rust function |
89 | pub function: Function, |
90 | /// The class name in JS this is attached to |
91 | pub js_class: Option<String>, |
92 | /// The kind (static, named, regular) |
93 | pub method_kind: MethodKind, |
94 | /// The type of `self` (either `self`, `&self`, or `&mut self`) |
95 | pub method_self: Option<MethodSelf>, |
96 | /// The struct name, in Rust, this is attached to |
97 | pub rust_class: Option<Ident>, |
98 | /// The name of the rust function/method on the rust side. |
99 | pub rust_name: Ident, |
100 | /// Whether or not this function should be flagged as the wasm start |
101 | /// function. |
102 | pub start: bool, |
103 | /// Path to wasm_bindgen |
104 | pub wasm_bindgen: Path, |
105 | /// Path to wasm_bindgen_futures |
106 | pub wasm_bindgen_futures: Path, |
107 | } |
108 | |
109 | /// The 3 types variations of `self`. |
110 | #[cfg_attr (feature = "extra-traits" , derive(Debug, PartialEq, Eq))] |
111 | #[derive (Clone)] |
112 | pub enum MethodSelf { |
113 | /// `self` |
114 | ByValue, |
115 | /// `&mut self` |
116 | RefMutable, |
117 | /// `&self` |
118 | RefShared, |
119 | } |
120 | |
121 | /// Things imported from a JS module (in an `extern` block) |
122 | #[cfg_attr (feature = "extra-traits" , derive(Debug))] |
123 | #[derive (Clone)] |
124 | pub struct Import { |
125 | /// The type of module being imported from, if any |
126 | pub module: Option<ImportModule>, |
127 | /// The namespace to access the item through, if any |
128 | pub js_namespace: Option<Vec<String>>, |
129 | /// The type of item being imported |
130 | pub kind: ImportKind, |
131 | } |
132 | |
133 | /// The possible types of module to import from |
134 | #[cfg_attr (feature = "extra-traits" , derive(Debug))] |
135 | #[derive (Clone)] |
136 | pub enum ImportModule { |
137 | /// Import from the named module, with relative paths interpreted |
138 | Named(String, Span), |
139 | /// Import from the named module, without interpreting paths |
140 | RawNamed(String, Span), |
141 | /// Import from an inline JS snippet |
142 | Inline(usize, Span), |
143 | } |
144 | |
145 | impl Hash for ImportModule { |
146 | fn hash<H: Hasher>(&self, h: &mut H) { |
147 | match self { |
148 | ImportModule::Named(name: &String, _) => (1u8, name).hash(state:h), |
149 | ImportModule::Inline(idx: &usize, _) => (2u8, idx).hash(state:h), |
150 | ImportModule::RawNamed(name: &String, _) => (3u8, name).hash(state:h), |
151 | } |
152 | } |
153 | } |
154 | |
155 | /// The type of item being imported |
156 | #[cfg_attr (feature = "extra-traits" , derive(Debug))] |
157 | #[derive (Clone)] |
158 | pub enum ImportKind { |
159 | /// Importing a function |
160 | Function(ImportFunction), |
161 | /// Importing a static value |
162 | Static(ImportStatic), |
163 | /// Importing a type/class |
164 | Type(ImportType), |
165 | /// Importing a JS enum |
166 | Enum(ImportEnum), |
167 | } |
168 | |
169 | /// A function being imported from JS |
170 | #[cfg_attr (feature = "extra-traits" , derive(Debug))] |
171 | #[derive (Clone)] |
172 | pub struct ImportFunction { |
173 | /// The full signature of the function |
174 | pub function: Function, |
175 | /// The name rust code will use |
176 | pub rust_name: Ident, |
177 | /// The type being returned |
178 | pub js_ret: Option<syn::Type>, |
179 | /// Whether to catch JS exceptions |
180 | pub catch: bool, |
181 | /// Whether the function is variadic on the JS side |
182 | pub variadic: bool, |
183 | /// Whether the function should use structural type checking |
184 | pub structural: bool, |
185 | /// Causes the Builder (See cli-support::js::binding::Builder) to error out if |
186 | /// it finds itself generating code for a function with this signature |
187 | pub assert_no_shim: bool, |
188 | /// The kind of function being imported |
189 | pub kind: ImportFunctionKind, |
190 | /// The shim name to use in the generated code. The 'shim' is a function that appears in |
191 | /// the generated JS as a wrapper around the actual function to import, performing any |
192 | /// necessary conversions (EG adding a try/catch to change a thrown error into a Result) |
193 | pub shim: Ident, |
194 | /// The doc comment on this import, if one is provided |
195 | pub doc_comment: String, |
196 | /// Path to wasm_bindgen |
197 | pub wasm_bindgen: Path, |
198 | /// Path to wasm_bindgen_futures |
199 | pub wasm_bindgen_futures: Path, |
200 | } |
201 | |
202 | /// The type of a function being imported |
203 | #[cfg_attr (feature = "extra-traits" , derive(Debug, PartialEq, Eq))] |
204 | #[derive (Clone)] |
205 | pub enum ImportFunctionKind { |
206 | /// A class method |
207 | Method { |
208 | /// The name of the class for this method, in JS |
209 | class: String, |
210 | /// The type of the class for this method, in Rust |
211 | ty: syn::Type, |
212 | /// The kind of method this is |
213 | kind: MethodKind, |
214 | }, |
215 | /// A standard function |
216 | Normal, |
217 | } |
218 | |
219 | /// The type of a method |
220 | #[cfg_attr (feature = "extra-traits" , derive(Debug, PartialEq, Eq))] |
221 | #[derive (Clone)] |
222 | pub enum MethodKind { |
223 | /// A class constructor |
224 | Constructor, |
225 | /// Any other kind of method |
226 | Operation(Operation), |
227 | } |
228 | |
229 | /// The operation performed by a class method |
230 | #[cfg_attr (feature = "extra-traits" , derive(Debug, PartialEq, Eq))] |
231 | #[derive (Clone)] |
232 | pub struct Operation { |
233 | /// Whether this method is static |
234 | pub is_static: bool, |
235 | /// The internal kind of this Operation |
236 | pub kind: OperationKind, |
237 | } |
238 | |
239 | /// The kind of operation performed by a method |
240 | #[cfg_attr (feature = "extra-traits" , derive(Debug, PartialEq, Eq))] |
241 | #[derive (Clone)] |
242 | pub enum OperationKind { |
243 | /// A standard method, nothing special |
244 | Regular, |
245 | /// A method for getting the value of the provided Ident |
246 | Getter(Option<Ident>), |
247 | /// A method for setting the value of the provided Ident |
248 | Setter(Option<Ident>), |
249 | /// A dynamically intercepted getter |
250 | IndexingGetter, |
251 | /// A dynamically intercepted setter |
252 | IndexingSetter, |
253 | /// A dynamically intercepted deleter |
254 | IndexingDeleter, |
255 | } |
256 | |
257 | /// The type of a static being imported |
258 | #[cfg_attr (feature = "extra-traits" , derive(Debug, PartialEq, Eq))] |
259 | #[derive (Clone)] |
260 | pub struct ImportStatic { |
261 | /// The visibility of this static in Rust |
262 | pub vis: syn::Visibility, |
263 | /// The type of static being imported |
264 | pub ty: syn::Type, |
265 | /// The name of the shim function used to access this static |
266 | pub shim: Ident, |
267 | /// The name of this static on the Rust side |
268 | pub rust_name: Ident, |
269 | /// The name of this static on the JS side |
270 | pub js_name: String, |
271 | /// Path to wasm_bindgen |
272 | pub wasm_bindgen: Path, |
273 | } |
274 | |
275 | /// The metadata for a type being imported |
276 | #[cfg_attr (feature = "extra-traits" , derive(Debug, PartialEq, Eq))] |
277 | #[derive (Clone)] |
278 | pub struct ImportType { |
279 | /// The visibility of this type in Rust |
280 | pub vis: syn::Visibility, |
281 | /// The name of this type on the Rust side |
282 | pub rust_name: Ident, |
283 | /// The name of this type on the JS side |
284 | pub js_name: String, |
285 | /// The custom attributes to apply to this type |
286 | pub attrs: Vec<syn::Attribute>, |
287 | /// The TS definition to generate for this type |
288 | pub typescript_type: Option<String>, |
289 | /// The doc comment applied to this type, if one exists |
290 | pub doc_comment: Option<String>, |
291 | /// The name of the shim to check instanceof for this type |
292 | pub instanceof_shim: String, |
293 | /// The name of the remote function to use for the generated is_type_of |
294 | pub is_type_of: Option<syn::Expr>, |
295 | /// The list of classes this extends, if any |
296 | pub extends: Vec<syn::Path>, |
297 | /// A custom prefix to add and attempt to fall back to, if the type isn't found |
298 | pub vendor_prefixes: Vec<Ident>, |
299 | /// If present, don't generate a `Deref` impl |
300 | pub no_deref: bool, |
301 | /// Path to wasm_bindgen |
302 | pub wasm_bindgen: Path, |
303 | } |
304 | |
305 | /// The metadata for an Enum being imported |
306 | #[cfg_attr (feature = "extra-traits" , derive(Debug, PartialEq, Eq))] |
307 | #[derive (Clone)] |
308 | pub struct ImportEnum { |
309 | /// The Rust enum's visibility |
310 | pub vis: syn::Visibility, |
311 | /// The Rust enum's identifiers |
312 | pub name: Ident, |
313 | /// The Rust identifiers for the variants |
314 | pub variants: Vec<Ident>, |
315 | /// The JS string values of the variants |
316 | pub variant_values: Vec<String>, |
317 | /// Attributes to apply to the Rust enum |
318 | pub rust_attrs: Vec<syn::Attribute>, |
319 | /// Path to wasm_bindgen |
320 | pub wasm_bindgen: Path, |
321 | } |
322 | |
323 | /// Information about a function being imported or exported |
324 | #[cfg_attr (feature = "extra-traits" , derive(Debug))] |
325 | #[derive (Clone)] |
326 | pub struct Function { |
327 | /// The name of the function |
328 | pub name: String, |
329 | /// The span of the function's name in Rust code |
330 | pub name_span: Span, |
331 | /// Whether the function has a js_name attribute |
332 | pub renamed_via_js_name: bool, |
333 | /// The arguments to the function |
334 | pub arguments: Vec<syn::PatType>, |
335 | /// The return type of the function, if provided |
336 | pub ret: Option<syn::Type>, |
337 | /// Any custom attributes being applied to the function |
338 | pub rust_attrs: Vec<syn::Attribute>, |
339 | /// The visibility of this function in Rust |
340 | pub rust_vis: syn::Visibility, |
341 | /// Whether this is an `unsafe` function |
342 | pub r#unsafe: bool, |
343 | /// Whether this is an `async` function |
344 | pub r#async: bool, |
345 | /// Whether to generate a typescript definition for this function |
346 | pub generate_typescript: bool, |
347 | /// Whether to generate jsdoc documentation for this function |
348 | pub generate_jsdoc: bool, |
349 | /// Whether this is a function with a variadict parameter |
350 | pub variadic: bool, |
351 | } |
352 | |
353 | /// Information about a Struct being exported |
354 | #[cfg_attr (feature = "extra-traits" , derive(Debug))] |
355 | #[derive (Clone)] |
356 | pub struct Struct { |
357 | /// The name of the struct in Rust code |
358 | pub rust_name: Ident, |
359 | /// The name of the struct in JS code |
360 | pub js_name: String, |
361 | /// All the fields of this struct to export |
362 | pub fields: Vec<StructField>, |
363 | /// The doc comments on this struct, if provided |
364 | pub comments: Vec<String>, |
365 | /// Whether this struct is inspectable (provides toJSON/toString properties to JS) |
366 | pub is_inspectable: bool, |
367 | /// Whether to generate a typescript definition for this struct |
368 | pub generate_typescript: bool, |
369 | /// Path to wasm_bindgen |
370 | pub wasm_bindgen: Path, |
371 | } |
372 | |
373 | /// The field of a struct |
374 | #[cfg_attr (feature = "extra-traits" , derive(Debug))] |
375 | #[derive (Clone)] |
376 | pub struct StructField { |
377 | /// The name of the field in Rust code |
378 | pub rust_name: syn::Member, |
379 | /// The name of the field in JS code |
380 | pub js_name: String, |
381 | /// The name of the struct this field is part of |
382 | pub struct_name: Ident, |
383 | /// Whether this value is read-only to JS |
384 | pub readonly: bool, |
385 | /// The type of this field |
386 | pub ty: syn::Type, |
387 | /// The name of the getter shim for this field |
388 | pub getter: Ident, |
389 | /// The name of the setter shim for this field |
390 | pub setter: Ident, |
391 | /// The doc comments on this field, if any |
392 | pub comments: Vec<String>, |
393 | /// Whether to generate a typescript definition for this field |
394 | pub generate_typescript: bool, |
395 | /// Whether to generate jsdoc documentation for this field |
396 | pub generate_jsdoc: bool, |
397 | /// The span of the `#[wasm_bindgen(getter_with_clone)]` attribute applied |
398 | /// to this field, if any. |
399 | /// |
400 | /// If this is `Some`, the auto-generated getter for this field must clone |
401 | /// the field instead of copying it. |
402 | pub getter_with_clone: Option<Span>, |
403 | /// Path to wasm_bindgen |
404 | pub wasm_bindgen: Path, |
405 | } |
406 | |
407 | /// Information about an Enum being exported |
408 | #[cfg_attr (feature = "extra-traits" , derive(Debug, PartialEq, Eq))] |
409 | #[derive (Clone)] |
410 | pub struct Enum { |
411 | /// The name of this enum in Rust code |
412 | pub rust_name: Ident, |
413 | /// The name of this enum in JS code |
414 | pub js_name: String, |
415 | /// The variants provided by this enum |
416 | pub variants: Vec<Variant>, |
417 | /// The doc comments on this enum, if any |
418 | pub comments: Vec<String>, |
419 | /// The value to use for a `none` variant of the enum |
420 | pub hole: u32, |
421 | /// Whether to generate a typescript definition for this enum |
422 | pub generate_typescript: bool, |
423 | /// Path to wasm_bindgen |
424 | pub wasm_bindgen: Path, |
425 | } |
426 | |
427 | /// The variant of an enum |
428 | #[cfg_attr (feature = "extra-traits" , derive(Debug, PartialEq, Eq))] |
429 | #[derive (Clone)] |
430 | pub struct Variant { |
431 | /// The name of this variant |
432 | pub name: Ident, |
433 | /// The backing value of this variant |
434 | pub value: u32, |
435 | /// The doc comments on this variant, if any |
436 | pub comments: Vec<String>, |
437 | } |
438 | |
439 | /// Unused, the type of an argument to / return from a function |
440 | #[derive (Copy, Clone, Debug, PartialEq, Eq)] |
441 | pub enum TypeKind { |
442 | /// A by-reference arg, EG `&T` |
443 | ByRef, |
444 | /// A by-mutable-reference arg, EG `&mut T` |
445 | ByMutRef, |
446 | /// A by-value arg, EG `T` |
447 | ByValue, |
448 | } |
449 | |
450 | /// Unused, the location of a type for a function argument (import/export, argument/ret) |
451 | #[derive (Copy, Clone, Debug, PartialEq, Eq)] |
452 | pub enum TypeLocation { |
453 | /// An imported argument (JS side type) |
454 | ImportArgument, |
455 | /// An imported return |
456 | ImportRet, |
457 | /// An exported argument (Rust side type) |
458 | ExportArgument, |
459 | /// An exported return |
460 | ExportRet, |
461 | } |
462 | |
463 | impl Export { |
464 | /// Mangles a rust -> javascript export, so that the created Ident will be unique over function |
465 | /// name and class name, if the function belongs to a javascript class. |
466 | pub(crate) fn rust_symbol(&self) -> Ident { |
467 | let mut generated_name = String::from("__wasm_bindgen_generated" ); |
468 | if let Some(class) = &self.js_class { |
469 | generated_name.push('_' ); |
470 | generated_name.push_str(class); |
471 | } |
472 | generated_name.push('_' ); |
473 | generated_name.push_str(&self.function.name.to_string()); |
474 | Ident::new(&generated_name, Span::call_site()) |
475 | } |
476 | |
477 | /// This is the name of the shim function that gets exported and takes the raw |
478 | /// ABI form of its arguments and converts them back into their normal, |
479 | /// "high level" form before calling the actual function. |
480 | pub(crate) fn export_name(&self) -> String { |
481 | let fn_name = self.function.name.to_string(); |
482 | match &self.js_class { |
483 | Some(class) => shared::struct_function_export_name(class, &fn_name), |
484 | None => shared::free_function_export_name(&fn_name), |
485 | } |
486 | } |
487 | } |
488 | |
489 | impl ImportKind { |
490 | /// Whether this type can be inside an `impl` block. |
491 | pub fn fits_on_impl(&self) -> bool { |
492 | match *self { |
493 | ImportKind::Function(_) => true, |
494 | ImportKind::Static(_) => false, |
495 | ImportKind::Type(_) => false, |
496 | ImportKind::Enum(_) => false, |
497 | } |
498 | } |
499 | } |
500 | |
501 | impl Function { |
502 | /// If the rust object has a `fn xxx(&self) -> MyType` method, get the name for a getter in |
503 | /// javascript (in this case `xxx`, so you can write `val = obj.xxx`) |
504 | pub fn infer_getter_property(&self) -> &str { |
505 | &self.name |
506 | } |
507 | |
508 | /// If the rust object has a `fn set_xxx(&mut self, MyType)` style method, get the name |
509 | /// for a setter in javascript (in this case `xxx`, so you can write `obj.xxx = val`) |
510 | pub fn infer_setter_property(&self) -> Result<String, Diagnostic> { |
511 | let name: String = self.name.to_string(); |
512 | |
513 | // Otherwise we infer names based on the Rust function name. |
514 | if !name.starts_with("set_" ) { |
515 | bail_span!( |
516 | syn::token::Pub(self.name_span), |
517 | "setters must start with `set_`, found: {}" , |
518 | name, |
519 | ); |
520 | } |
521 | Ok(name[4..].to_string()) |
522 | } |
523 | } |
524 | |