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 }
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!("{}{}", 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 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}