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