wasm_bindgen_backend/
encode.rs

1use crate::util::ShortHash;
2use proc_macro2::{Ident, Span};
3use std::cell::{Cell, RefCell};
4use std::collections::HashMap;
5use std::env;
6use std::fs;
7use std::path::PathBuf;
8use syn::ext::IdentExt;
9
10use crate::ast;
11use crate::Diagnostic;
12
13#[derive(Clone)]
14pub enum EncodeChunk {
15    EncodedBuf(Vec<u8>),
16    StrExpr(syn::Expr),
17    // TODO: support more expr type;
18}
19
20pub struct EncodeResult {
21    pub custom_section: Vec<EncodeChunk>,
22    pub included_files: Vec<PathBuf>,
23}
24
25pub fn encode(program: &ast::Program) -> Result<EncodeResult, Diagnostic> {
26    let mut e = Encoder::new();
27    let i = Interner::new();
28    shared_program(program, &i)?.encode(&mut e);
29    let custom_section = e.finish();
30    let included_files = i
31        .files
32        .borrow()
33        .values()
34        .map(|p| &p.path)
35        .cloned()
36        .collect();
37    Ok(EncodeResult {
38        custom_section,
39        included_files,
40    })
41}
42
43struct Interner {
44    bump: bumpalo::Bump,
45    files: RefCell<HashMap<String, LocalFile>>,
46    root: PathBuf,
47    crate_name: String,
48    has_package_json: Cell<bool>,
49}
50
51struct LocalFile {
52    path: PathBuf,
53    definition: Span,
54    new_identifier: String,
55}
56
57impl Interner {
58    fn new() -> Interner {
59        let root = env::var_os("CARGO_MANIFEST_DIR")
60            .expect("should have CARGO_MANIFEST_DIR env var")
61            .into();
62        let crate_name = env::var("CARGO_PKG_NAME").expect("should have CARGO_PKG_NAME env var");
63        Interner {
64            bump: bumpalo::Bump::new(),
65            files: RefCell::new(HashMap::new()),
66            root,
67            crate_name,
68            has_package_json: Cell::new(false),
69        }
70    }
71
72    fn intern(&self, s: &Ident) -> &str {
73        self.intern_str(&s.to_string())
74    }
75
76    fn intern_str(&self, s: &str) -> &str {
77        // NB: eventually this could be used to intern `s` to only allocate one
78        // copy, but for now let's just "transmute" `s` to have the same
79        // lifetime as this struct itself (which is our main goal here)
80        self.bump.alloc_str(s)
81    }
82
83    /// Given an import to a local module `id` this generates a unique module id
84    /// to assign to the contents of `id`.
85    ///
86    /// Note that repeated invocations of this function will be memoized, so the
87    /// same `id` will always return the same resulting unique `id`.
88    fn resolve_import_module(&self, id: &str, span: Span) -> Result<ImportModule, Diagnostic> {
89        let mut files = self.files.borrow_mut();
90        if let Some(file) = files.get(id) {
91            return Ok(ImportModule::Named(self.intern_str(&file.new_identifier)));
92        }
93        self.check_for_package_json();
94        let path = if let Some(id) = id.strip_prefix('/') {
95            self.root.join(id)
96        } else if id.starts_with("./") || id.starts_with("../") {
97            let msg = "relative module paths aren't supported yet";
98            return Err(Diagnostic::span_error(span, msg));
99        } else {
100            return Ok(ImportModule::RawNamed(self.intern_str(id)));
101        };
102
103        // Generate a unique ID which is somewhat readable as well, so mix in
104        // the crate name, hash to make it unique, and then the original path.
105        let new_identifier = format!("{}{}", self.unique_crate_identifier(), id);
106        let file = LocalFile {
107            path,
108            definition: span,
109            new_identifier,
110        };
111        files.insert(id.to_string(), file);
112        drop(files);
113        self.resolve_import_module(id, span)
114    }
115
116    fn unique_crate_identifier(&self) -> String {
117        format!("{}-{}", self.crate_name, ShortHash(0))
118    }
119
120    fn check_for_package_json(&self) {
121        if self.has_package_json.get() {
122            return;
123        }
124        let path = self.root.join("package.json");
125        if path.exists() {
126            self.has_package_json.set(true);
127        }
128    }
129}
130
131fn shared_program<'a>(
132    prog: &'a ast::Program,
133    intern: &'a Interner,
134) -> Result<Program<'a>, Diagnostic> {
135    Ok(Program {
136        exports: prog
137            .exports
138            .iter()
139            .map(|a| shared_export(a, intern))
140            .collect::<Result<Vec<_>, _>>()?,
141        structs: prog
142            .structs
143            .iter()
144            .map(|a| shared_struct(a, intern))
145            .collect(),
146        enums: prog.enums.iter().map(|a| shared_enum(a, intern)).collect(),
147        imports: prog
148            .imports
149            .iter()
150            .map(|a| shared_import(a, intern))
151            .collect::<Result<Vec<_>, _>>()?,
152        typescript_custom_sections: prog
153            .typescript_custom_sections
154            .iter()
155            .map(|x| shared_lit_or_expr(x, intern))
156            .collect(),
157        linked_modules: prog
158            .linked_modules
159            .iter()
160            .enumerate()
161            .map(|(i, a)| shared_linked_module(&prog.link_function_name(i), a, intern))
162            .collect::<Result<Vec<_>, _>>()?,
163        local_modules: intern
164            .files
165            .borrow()
166            .values()
167            .map(|file| {
168                fs::read_to_string(&file.path)
169                    .map(|s| LocalModule {
170                        identifier: intern.intern_str(&file.new_identifier),
171                        contents: intern.intern_str(&s),
172                    })
173                    .map_err(|e| {
174                        let msg = format!("failed to read file `{}`: {}", file.path.display(), e);
175                        Diagnostic::span_error(file.definition, msg)
176                    })
177            })
178            .collect::<Result<Vec<_>, _>>()?,
179        inline_js: prog
180            .inline_js
181            .iter()
182            .map(|js| intern.intern_str(js))
183            .collect(),
184        unique_crate_identifier: intern.intern_str(&intern.unique_crate_identifier()),
185        package_json: if intern.has_package_json.get() {
186            Some(intern.intern_str(intern.root.join("package.json").to_str().unwrap()))
187        } else {
188            None
189        },
190    })
191}
192
193fn shared_export<'a>(
194    export: &'a ast::Export,
195    intern: &'a Interner,
196) -> Result<Export<'a>, Diagnostic> {
197    let consumed = matches!(export.method_self, Some(ast::MethodSelf::ByValue));
198    let method_kind = from_ast_method_kind(&export.function, intern, &export.method_kind)?;
199    Ok(Export {
200        class: export.js_class.as_deref(),
201        comments: export.comments.iter().map(|s| &**s).collect(),
202        consumed,
203        function: shared_function(&export.function, intern),
204        method_kind,
205        start: export.start,
206    })
207}
208
209fn shared_function<'a>(func: &'a ast::Function, _intern: &'a Interner) -> Function<'a> {
210    let arg_names = func
211        .arguments
212        .iter()
213        .enumerate()
214        .map(|(idx, arg)| {
215            if let syn::Pat::Ident(x) = &*arg.pat {
216                return x.ident.unraw().to_string();
217            }
218            format!("arg{}", idx)
219        })
220        .collect::<Vec<_>>();
221    Function {
222        arg_names,
223        asyncness: func.r#async,
224        name: &func.name,
225        generate_typescript: func.generate_typescript,
226        generate_jsdoc: func.generate_jsdoc,
227        variadic: func.variadic,
228    }
229}
230
231fn shared_enum<'a>(e: &'a ast::Enum, intern: &'a Interner) -> Enum<'a> {
232    Enum {
233        name: &e.js_name,
234        variants: e
235            .variants
236            .iter()
237            .map(|v| shared_variant(v, intern))
238            .collect(),
239        comments: e.comments.iter().map(|s| &**s).collect(),
240        generate_typescript: e.generate_typescript,
241    }
242}
243
244fn shared_variant<'a>(v: &'a ast::Variant, intern: &'a Interner) -> EnumVariant<'a> {
245    EnumVariant {
246        name: intern.intern(&v.name),
247        value: v.value,
248        comments: v.comments.iter().map(|s| &**s).collect(),
249    }
250}
251
252fn shared_import<'a>(i: &'a ast::Import, intern: &'a Interner) -> Result<Import<'a>, Diagnostic> {
253    Ok(Import {
254        module: i
255            .module
256            .as_ref()
257            .map(|m| shared_module(m, intern))
258            .transpose()?,
259        js_namespace: i.js_namespace.clone(),
260        kind: shared_import_kind(&i.kind, intern)?,
261    })
262}
263
264fn shared_lit_or_expr<'a>(i: &'a ast::LitOrExpr, _intern: &'a Interner) -> LitOrExpr<'a> {
265    match i {
266        ast::LitOrExpr::Lit(lit) => LitOrExpr::Lit(lit),
267        ast::LitOrExpr::Expr(expr) => LitOrExpr::Expr(expr),
268    }
269}
270
271fn shared_linked_module<'a>(
272    name: &str,
273    i: &'a ast::ImportModule,
274    intern: &'a Interner,
275) -> Result<LinkedModule<'a>, Diagnostic> {
276    Ok(LinkedModule {
277        module: shared_module(i, intern)?,
278        link_function_name: intern.intern_str(name),
279    })
280}
281
282fn shared_module<'a>(
283    m: &'a ast::ImportModule,
284    intern: &'a Interner,
285) -> Result<ImportModule<'a>, Diagnostic> {
286    Ok(match m {
287        ast::ImportModule::Named(m, span) => intern.resolve_import_module(m, *span)?,
288        ast::ImportModule::RawNamed(m, _span) => ImportModule::RawNamed(intern.intern_str(m)),
289        ast::ImportModule::Inline(idx, _) => ImportModule::Inline(*idx as u32),
290    })
291}
292
293fn shared_import_kind<'a>(
294    i: &'a ast::ImportKind,
295    intern: &'a Interner,
296) -> Result<ImportKind<'a>, Diagnostic> {
297    Ok(match i {
298        ast::ImportKind::Function(f) => ImportKind::Function(shared_import_function(f, intern)?),
299        ast::ImportKind::Static(f) => ImportKind::Static(shared_import_static(f, intern)),
300        ast::ImportKind::String(f) => ImportKind::String(shared_import_string(f, intern)),
301        ast::ImportKind::Type(f) => ImportKind::Type(shared_import_type(f, intern)),
302        ast::ImportKind::Enum(f) => ImportKind::Enum(shared_import_enum(f, intern)),
303    })
304}
305
306fn shared_import_function<'a>(
307    i: &'a ast::ImportFunction,
308    intern: &'a Interner,
309) -> Result<ImportFunction<'a>, Diagnostic> {
310    let method = match &i.kind {
311        ast::ImportFunctionKind::Method { class, kind, .. } => {
312            let kind = from_ast_method_kind(&i.function, intern, kind)?;
313            Some(MethodData { class, kind })
314        }
315        ast::ImportFunctionKind::Normal => None,
316    };
317
318    Ok(ImportFunction {
319        shim: intern.intern(&i.shim),
320        catch: i.catch,
321        method,
322        assert_no_shim: i.assert_no_shim,
323        structural: i.structural,
324        function: shared_function(&i.function, intern),
325        variadic: i.variadic,
326    })
327}
328
329fn shared_import_static<'a>(i: &'a ast::ImportStatic, intern: &'a Interner) -> ImportStatic<'a> {
330    ImportStatic {
331        name: &i.js_name,
332        shim: intern.intern(&i.shim),
333    }
334}
335
336fn shared_import_string<'a>(i: &'a ast::ImportString, intern: &'a Interner) -> ImportString<'a> {
337    ImportString {
338        shim: intern.intern(&i.shim),
339        string: &i.string,
340    }
341}
342
343fn shared_import_type<'a>(i: &'a ast::ImportType, intern: &'a Interner) -> ImportType<'a> {
344    ImportType {
345        name: &i.js_name,
346        instanceof_shim: &i.instanceof_shim,
347        vendor_prefixes: i.vendor_prefixes.iter().map(|x| intern.intern(x)).collect(),
348    }
349}
350
351fn shared_import_enum<'a>(_i: &'a ast::StringEnum, _intern: &'a Interner) -> StringEnum {
352    StringEnum {}
353}
354
355fn shared_struct<'a>(s: &'a ast::Struct, intern: &'a Interner) -> Struct<'a> {
356    Struct {
357        name: &s.js_name,
358        fields: s
359            .fields
360            .iter()
361            .map(|s| shared_struct_field(s, intern))
362            .collect(),
363        comments: s.comments.iter().map(|s| &**s).collect(),
364        is_inspectable: s.is_inspectable,
365        generate_typescript: s.generate_typescript,
366    }
367}
368
369fn shared_struct_field<'a>(s: &'a ast::StructField, _intern: &'a Interner) -> StructField<'a> {
370    StructField {
371        name: &s.js_name,
372        readonly: s.readonly,
373        comments: s.comments.iter().map(|s| &**s).collect(),
374        generate_typescript: s.generate_typescript,
375        generate_jsdoc: s.generate_jsdoc,
376    }
377}
378
379trait Encode {
380    fn encode(&self, dst: &mut Encoder);
381}
382
383struct Encoder {
384    dst: Vec<EncodeChunk>,
385}
386
387enum LitOrExpr<'a> {
388    Expr(&'a syn::Expr),
389    Lit(&'a str),
390}
391
392impl<'a> Encode for LitOrExpr<'a> {
393    fn encode(&self, dst: &mut Encoder) {
394        match self {
395            LitOrExpr::Expr(expr) => {
396                dst.dst.push(EncodeChunk::StrExpr((*expr).clone()));
397            }
398            LitOrExpr::Lit(s) => s.encode(dst),
399        }
400    }
401}
402
403impl Encoder {
404    fn new() -> Encoder {
405        Encoder { dst: vec![] }
406    }
407
408    fn finish(self) -> Vec<EncodeChunk> {
409        self.dst
410    }
411
412    fn byte(&mut self, byte: u8) {
413        if let Some(EncodeChunk::EncodedBuf(buf)) = self.dst.last_mut() {
414            buf.push(byte);
415        } else {
416            self.dst.push(EncodeChunk::EncodedBuf(vec![byte]));
417        }
418    }
419
420    fn extend_from_slice(&mut self, slice: &[u8]) {
421        if let Some(EncodeChunk::EncodedBuf(buf)) = self.dst.last_mut() {
422            buf.extend_from_slice(slice);
423        } else {
424            self.dst.push(EncodeChunk::EncodedBuf(slice.to_owned()));
425        }
426    }
427}
428
429impl Encode for bool {
430    fn encode(&self, dst: &mut Encoder) {
431        dst.byte(*self as u8);
432    }
433}
434
435impl Encode for u32 {
436    fn encode(&self, dst: &mut Encoder) {
437        let mut val = *self;
438        while (val >> 7) != 0 {
439            dst.byte((val as u8) | 0x80);
440            val >>= 7;
441        }
442        assert_eq!(val >> 7, 0);
443        dst.byte(val as u8);
444    }
445}
446
447impl Encode for usize {
448    fn encode(&self, dst: &mut Encoder) {
449        assert!(*self <= u32::MAX as usize);
450        (*self as u32).encode(dst);
451    }
452}
453
454impl<'a> Encode for &'a [u8] {
455    fn encode(&self, dst: &mut Encoder) {
456        self.len().encode(dst);
457        dst.extend_from_slice(self);
458    }
459}
460
461impl<'a> Encode for &'a str {
462    fn encode(&self, dst: &mut Encoder) {
463        self.as_bytes().encode(dst);
464    }
465}
466
467impl Encode for String {
468    fn encode(&self, dst: &mut Encoder) {
469        self.as_bytes().encode(dst);
470    }
471}
472
473impl<T: Encode> Encode for Vec<T> {
474    fn encode(&self, dst: &mut Encoder) {
475        self.len().encode(dst);
476        for item in self {
477            item.encode(dst);
478        }
479    }
480}
481
482impl<T: Encode> Encode for Option<T> {
483    fn encode(&self, dst: &mut Encoder) {
484        match self {
485            None => dst.byte(0),
486            Some(val) => {
487                dst.byte(1);
488                val.encode(dst)
489            }
490        }
491    }
492}
493
494macro_rules! encode_struct {
495    ($name:ident ($($lt:tt)*) $($field:ident: $ty:ty,)*) => {
496        struct $name $($lt)* {
497            $($field: $ty,)*
498        }
499
500        impl $($lt)* Encode for $name $($lt)* {
501            fn encode(&self, _dst: &mut Encoder) {
502                $(self.$field.encode(_dst);)*
503            }
504        }
505    }
506}
507
508macro_rules! encode_enum {
509    ($name:ident ($($lt:tt)*) $($fields:tt)*) => (
510        enum $name $($lt)* { $($fields)* }
511
512        impl$($lt)* Encode for $name $($lt)* {
513            fn encode(&self, dst: &mut Encoder) {
514                use self::$name::*;
515                encode_enum!(@arms self dst (0) () $($fields)*)
516            }
517        }
518    );
519
520    (@arms $me:ident $dst:ident ($cnt:expr) ($($arms:tt)*)) => (
521        encode_enum!(@expr match $me { $($arms)* })
522    );
523
524    (@arms $me:ident $dst:ident ($cnt:expr) ($($arms:tt)*) $name:ident, $($rest:tt)*) => (
525        encode_enum!(
526            @arms
527            $me
528            $dst
529            ($cnt+1)
530            ($($arms)* $name => $dst.byte($cnt),)
531            $($rest)*
532        )
533    );
534
535    (@arms $me:ident $dst:ident ($cnt:expr) ($($arms:tt)*) $name:ident($t:ty), $($rest:tt)*) => (
536        encode_enum!(
537            @arms
538            $me
539            $dst
540            ($cnt+1)
541            ($($arms)* $name(val) => { $dst.byte($cnt); val.encode($dst) })
542            $($rest)*
543        )
544    );
545
546    (@expr $e:expr) => ($e);
547}
548
549macro_rules! encode_api {
550    () => ();
551    (struct $name:ident<'a> { $($fields:tt)* } $($rest:tt)*) => (
552        encode_struct!($name (<'a>) $($fields)*);
553        encode_api!($($rest)*);
554    );
555    (struct $name:ident { $($fields:tt)* } $($rest:tt)*) => (
556        encode_struct!($name () $($fields)*);
557        encode_api!($($rest)*);
558    );
559    (enum $name:ident<'a> { $($variants:tt)* } $($rest:tt)*) => (
560        encode_enum!($name (<'a>) $($variants)*);
561        encode_api!($($rest)*);
562    );
563    (enum $name:ident { $($variants:tt)* } $($rest:tt)*) => (
564        encode_enum!($name () $($variants)*);
565        encode_api!($($rest)*);
566    );
567}
568wasm_bindgen_shared::shared_api!(encode_api);
569
570fn from_ast_method_kind<'a>(
571    function: &'a ast::Function,
572    intern: &'a Interner,
573    method_kind: &'a ast::MethodKind,
574) -> Result<MethodKind<'a>, Diagnostic> {
575    Ok(match method_kind {
576        ast::MethodKind::Constructor => MethodKind::Constructor,
577        ast::MethodKind::Operation(ast::Operation { is_static, kind }) => {
578            let is_static = *is_static;
579            let kind = match kind {
580                ast::OperationKind::Getter(g) => {
581                    let g = g.as_ref().map(|g| intern.intern_str(g));
582                    OperationKind::Getter(g.unwrap_or_else(|| function.infer_getter_property()))
583                }
584                ast::OperationKind::Regular => OperationKind::Regular,
585                ast::OperationKind::Setter(s) => {
586                    let s = s.as_ref().map(|s| intern.intern_str(s));
587                    OperationKind::Setter(match s {
588                        Some(s) => s,
589                        None => intern.intern_str(&function.infer_setter_property()?),
590                    })
591                }
592                ast::OperationKind::IndexingGetter => OperationKind::IndexingGetter,
593                ast::OperationKind::IndexingSetter => OperationKind::IndexingSetter,
594                ast::OperationKind::IndexingDeleter => OperationKind::IndexingDeleter,
595            };
596            MethodKind::Operation(Operation { is_static, kind })
597        }
598    })
599}