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 args =
219        func.arguments
220            .iter()
221            .enumerate()
222            .map(|(idx, arg)| FunctionArgumentData {
223                // use argument's "js_name" if it was provided via attributes
224                // if not use the original Rust argument ident
225                name: arg.js_name.clone().unwrap_or(
226                    if let syn::Pat::Ident(x) = &*arg.pat_type.pat {
227                        x.ident.unraw().to_string()
228                    } else {
229                        format!("arg{}", idx)
230                    },
231                ),
232                ty_override: arg.js_type.as_deref(),
233                desc: arg.desc.as_deref(),
234            })
235            .collect::<Vec<_>>();
236
237    Function {
238        args,
239        asyncness: func.r#async,
240        name: &func.name,
241        generate_typescript: func.generate_typescript,
242        generate_jsdoc: func.generate_jsdoc,
243        variadic: func.variadic,
244        ret_ty_override: func.ret.as_ref().and_then(|v| v.js_type.as_deref()),
245        ret_desc: func.ret.as_ref().and_then(|v| v.desc.as_deref()),
246    }
247}
248
249fn shared_enum<'a>(e: &'a ast::Enum, intern: &'a Interner) -> Enum<'a> {
250    Enum {
251        name: &e.js_name,
252        signed: e.signed,
253        variants: e
254            .variants
255            .iter()
256            .map(|v| shared_variant(v, intern))
257            .collect(),
258        comments: e.comments.iter().map(|s| &**s).collect(),
259        generate_typescript: e.generate_typescript,
260    }
261}
262
263fn shared_variant<'a>(v: &'a ast::Variant, intern: &'a Interner) -> EnumVariant<'a> {
264    EnumVariant {
265        name: intern.intern(&v.name),
266        value: v.value,
267        comments: v.comments.iter().map(|s| &**s).collect(),
268    }
269}
270
271fn shared_import<'a>(i: &'a ast::Import, intern: &'a Interner) -> Result<Import<'a>, Diagnostic> {
272    Ok(Import {
273        module: i
274            .module
275            .as_ref()
276            .map(|m| shared_module(m, intern, false))
277            .transpose()?,
278        js_namespace: i.js_namespace.clone(),
279        kind: shared_import_kind(&i.kind, intern)?,
280    })
281}
282
283fn shared_lit_or_expr<'a>(i: &'a ast::LitOrExpr, _intern: &'a Interner) -> LitOrExpr<'a> {
284    match i {
285        ast::LitOrExpr::Lit(lit) => LitOrExpr::Lit(lit),
286        ast::LitOrExpr::Expr(expr) => LitOrExpr::Expr(expr),
287    }
288}
289
290fn shared_linked_module<'a>(
291    name: &str,
292    i: &'a ast::ImportModule,
293    intern: &'a Interner,
294) -> Result<LinkedModule<'a>, Diagnostic> {
295    Ok(LinkedModule {
296        module: shared_module(i, intern, true)?,
297        link_function_name: intern.intern_str(name),
298    })
299}
300
301fn shared_module<'a>(
302    m: &'a ast::ImportModule,
303    intern: &'a Interner,
304    linked_module: bool,
305) -> Result<ImportModule<'a>, Diagnostic> {
306    Ok(match m {
307        ast::ImportModule::Named(m, span) => {
308            intern.resolve_import_module(m, *span, linked_module)?
309        }
310        ast::ImportModule::RawNamed(m, _span) => ImportModule::RawNamed(intern.intern_str(m)),
311        ast::ImportModule::Inline(idx, _) => ImportModule::Inline(*idx as u32),
312    })
313}
314
315fn shared_import_kind<'a>(
316    i: &'a ast::ImportKind,
317    intern: &'a Interner,
318) -> Result<ImportKind<'a>, Diagnostic> {
319    Ok(match i {
320        ast::ImportKind::Function(f) => ImportKind::Function(shared_import_function(f, intern)?),
321        ast::ImportKind::Static(f) => ImportKind::Static(shared_import_static(f, intern)),
322        ast::ImportKind::String(f) => ImportKind::String(shared_import_string(f, intern)),
323        ast::ImportKind::Type(f) => ImportKind::Type(shared_import_type(f, intern)),
324        ast::ImportKind::Enum(f) => ImportKind::Enum(shared_import_enum(f, intern)),
325    })
326}
327
328fn shared_import_function<'a>(
329    i: &'a ast::ImportFunction,
330    intern: &'a Interner,
331) -> Result<ImportFunction<'a>, Diagnostic> {
332    let method = match &i.kind {
333        ast::ImportFunctionKind::Method { class, kind, .. } => {
334            let kind = from_ast_method_kind(&i.function, intern, kind)?;
335            Some(MethodData { class, kind })
336        }
337        ast::ImportFunctionKind::Normal => None,
338    };
339
340    Ok(ImportFunction {
341        shim: intern.intern(&i.shim),
342        catch: i.catch,
343        method,
344        assert_no_shim: i.assert_no_shim,
345        structural: i.structural,
346        function: shared_function(&i.function, intern),
347        variadic: i.variadic,
348    })
349}
350
351fn shared_import_static<'a>(i: &'a ast::ImportStatic, intern: &'a Interner) -> ImportStatic<'a> {
352    ImportStatic {
353        name: &i.js_name,
354        shim: intern.intern(&i.shim),
355    }
356}
357
358fn shared_import_string<'a>(i: &'a ast::ImportString, intern: &'a Interner) -> ImportString<'a> {
359    ImportString {
360        shim: intern.intern(&i.shim),
361        string: &i.string,
362    }
363}
364
365fn shared_import_type<'a>(i: &'a ast::ImportType, intern: &'a Interner) -> ImportType<'a> {
366    ImportType {
367        name: &i.js_name,
368        instanceof_shim: &i.instanceof_shim,
369        vendor_prefixes: i.vendor_prefixes.iter().map(|x| intern.intern(x)).collect(),
370    }
371}
372
373fn shared_import_enum<'a>(i: &'a ast::StringEnum, _intern: &'a Interner) -> StringEnum<'a> {
374    StringEnum {
375        name: &i.js_name,
376        generate_typescript: i.generate_typescript,
377        variant_values: i.variant_values.iter().map(|x| &**x).collect(),
378        comments: i.comments.iter().map(|s| &**s).collect(),
379    }
380}
381
382fn shared_struct<'a>(s: &'a ast::Struct, intern: &'a Interner) -> Struct<'a> {
383    Struct {
384        name: &s.js_name,
385        fields: s
386            .fields
387            .iter()
388            .map(|s| shared_struct_field(s, intern))
389            .collect(),
390        comments: s.comments.iter().map(|s| &**s).collect(),
391        is_inspectable: s.is_inspectable,
392        generate_typescript: s.generate_typescript,
393    }
394}
395
396fn shared_struct_field<'a>(s: &'a ast::StructField, _intern: &'a Interner) -> StructField<'a> {
397    StructField {
398        name: &s.js_name,
399        readonly: s.readonly,
400        comments: s.comments.iter().map(|s| &**s).collect(),
401        generate_typescript: s.generate_typescript,
402        generate_jsdoc: s.generate_jsdoc,
403    }
404}
405
406trait Encode {
407    fn encode(&self, dst: &mut Encoder);
408}
409
410struct Encoder {
411    dst: Vec<EncodeChunk>,
412}
413
414enum LitOrExpr<'a> {
415    Expr(&'a syn::Expr),
416    Lit(&'a str),
417}
418
419impl Encode for LitOrExpr<'_> {
420    fn encode(&self, dst: &mut Encoder) {
421        match self {
422            LitOrExpr::Expr(expr) => {
423                dst.dst.push(EncodeChunk::StrExpr((*expr).clone()));
424            }
425            LitOrExpr::Lit(s) => s.encode(dst),
426        }
427    }
428}
429
430impl Encoder {
431    fn new() -> Encoder {
432        Encoder { dst: vec![] }
433    }
434
435    fn finish(self) -> Vec<EncodeChunk> {
436        self.dst
437    }
438
439    fn byte(&mut self, byte: u8) {
440        if let Some(EncodeChunk::EncodedBuf(buf)) = self.dst.last_mut() {
441            buf.push(byte);
442        } else {
443            self.dst.push(EncodeChunk::EncodedBuf(vec![byte]));
444        }
445    }
446
447    fn extend_from_slice(&mut self, slice: &[u8]) {
448        if let Some(EncodeChunk::EncodedBuf(buf)) = self.dst.last_mut() {
449            buf.extend_from_slice(slice);
450        } else {
451            self.dst.push(EncodeChunk::EncodedBuf(slice.to_owned()));
452        }
453    }
454}
455
456impl Encode for bool {
457    fn encode(&self, dst: &mut Encoder) {
458        dst.byte(*self as u8);
459    }
460}
461
462impl Encode for u32 {
463    fn encode(&self, dst: &mut Encoder) {
464        let mut val = *self;
465        while (val >> 7) != 0 {
466            dst.byte((val as u8) | 0x80);
467            val >>= 7;
468        }
469        assert_eq!(val >> 7, 0);
470        dst.byte(val as u8);
471    }
472}
473
474impl Encode for usize {
475    fn encode(&self, dst: &mut Encoder) {
476        assert!(*self <= u32::MAX as usize);
477        (*self as u32).encode(dst);
478    }
479}
480
481impl Encode for &[u8] {
482    fn encode(&self, dst: &mut Encoder) {
483        self.len().encode(dst);
484        dst.extend_from_slice(self);
485    }
486}
487
488impl Encode for &str {
489    fn encode(&self, dst: &mut Encoder) {
490        self.as_bytes().encode(dst);
491    }
492}
493
494impl Encode for String {
495    fn encode(&self, dst: &mut Encoder) {
496        self.as_bytes().encode(dst);
497    }
498}
499
500impl<T: Encode> Encode for Vec<T> {
501    fn encode(&self, dst: &mut Encoder) {
502        self.len().encode(dst);
503        for item in self {
504            item.encode(dst);
505        }
506    }
507}
508
509impl<T: Encode> Encode for Option<T> {
510    fn encode(&self, dst: &mut Encoder) {
511        match self {
512            None => dst.byte(0),
513            Some(val) => {
514                dst.byte(1);
515                val.encode(dst)
516            }
517        }
518    }
519}
520
521macro_rules! encode_struct {
522    ($name:ident ($($lt:tt)*) $($field:ident: $ty:ty,)*) => {
523        struct $name $($lt)* {
524            $($field: $ty,)*
525        }
526
527        impl $($lt)* Encode for $name $($lt)* {
528            fn encode(&self, _dst: &mut Encoder) {
529                $(self.$field.encode(_dst);)*
530            }
531        }
532    }
533}
534
535macro_rules! encode_enum {
536    ($name:ident ($($lt:tt)*) $($fields:tt)*) => (
537        enum $name $($lt)* { $($fields)* }
538
539        impl$($lt)* Encode for $name $($lt)* {
540            fn encode(&self, dst: &mut Encoder) {
541                use self::$name::*;
542                encode_enum!(@arms self dst (0) () $($fields)*)
543            }
544        }
545    );
546
547    (@arms $me:ident $dst:ident ($cnt:expr) ($($arms:tt)*)) => (
548        encode_enum!(@expr match $me { $($arms)* })
549    );
550
551    (@arms $me:ident $dst:ident ($cnt:expr) ($($arms:tt)*) $name:ident, $($rest:tt)*) => (
552        encode_enum!(
553            @arms
554            $me
555            $dst
556            ($cnt+1)
557            ($($arms)* $name => $dst.byte($cnt),)
558            $($rest)*
559        )
560    );
561
562    (@arms $me:ident $dst:ident ($cnt:expr) ($($arms:tt)*) $name:ident($t:ty), $($rest:tt)*) => (
563        encode_enum!(
564            @arms
565            $me
566            $dst
567            ($cnt+1)
568            ($($arms)* $name(val) => { $dst.byte($cnt); val.encode($dst) })
569            $($rest)*
570        )
571    );
572
573    (@expr $e:expr) => ($e);
574}
575
576macro_rules! encode_api {
577    () => ();
578    (struct $name:ident<'a> { $($fields:tt)* } $($rest:tt)*) => (
579        encode_struct!($name (<'a>) $($fields)*);
580        encode_api!($($rest)*);
581    );
582    (struct $name:ident { $($fields:tt)* } $($rest:tt)*) => (
583        encode_struct!($name () $($fields)*);
584        encode_api!($($rest)*);
585    );
586    (enum $name:ident<'a> { $($variants:tt)* } $($rest:tt)*) => (
587        encode_enum!($name (<'a>) $($variants)*);
588        encode_api!($($rest)*);
589    );
590    (enum $name:ident { $($variants:tt)* } $($rest:tt)*) => (
591        encode_enum!($name () $($variants)*);
592        encode_api!($($rest)*);
593    );
594}
595wasm_bindgen_shared::shared_api!(encode_api);
596
597fn from_ast_method_kind<'a>(
598    function: &'a ast::Function,
599    intern: &'a Interner,
600    method_kind: &'a ast::MethodKind,
601) -> Result<MethodKind<'a>, Diagnostic> {
602    Ok(match method_kind {
603        ast::MethodKind::Constructor => MethodKind::Constructor,
604        ast::MethodKind::Operation(ast::Operation { is_static, kind }) => {
605            let is_static = *is_static;
606            let kind = match kind {
607                ast::OperationKind::Getter(g) => {
608                    let g = g.as_ref().map(|g| intern.intern_str(g));
609                    OperationKind::Getter(g.unwrap_or_else(|| function.infer_getter_property()))
610                }
611                ast::OperationKind::Regular => OperationKind::Regular,
612                ast::OperationKind::Setter(s) => {
613                    let s = s.as_ref().map(|s| intern.intern_str(s));
614                    OperationKind::Setter(match s {
615                        Some(s) => s,
616                        None => intern.intern_str(&function.infer_setter_property()?),
617                    })
618                }
619                ast::OperationKind::IndexingGetter => OperationKind::IndexingGetter,
620                ast::OperationKind::IndexingSetter => OperationKind::IndexingSetter,
621                ast::OperationKind::IndexingDeleter => OperationKind::IndexingDeleter,
622            };
623            MethodKind::Operation(Operation { is_static, kind })
624        }
625    })
626}