Skip to main content

wasm_bindgen_macro_support/
encode.rs

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