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 }
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 self.bump.alloc_str(s)
82 }
83
84 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 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 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 let reexport = i.reexport.as_ref().map(|rename_opt| {
284 rename_opt.clone().unwrap_or_else(|| {
285 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}