Fork of daniellemaywood.uk/gleam — Wasm codegen work
2

Configure Feed

Select the types of activity you want to include in your feed.

gleam / compiler-core / src / javascript.rs
29 kB 923 lines
1mod decision; 2mod expression; 3mod import; 4mod pattern; 5#[cfg(test)] 6mod tests; 7mod typescript; 8 9use num_bigint::BigInt; 10use num_traits::ToPrimitive; 11 12use crate::analyse::TargetSupport; 13use crate::build::Target; 14use crate::build::package_compiler::StdlibPackage; 15use crate::codegen::TypeScriptDeclarations; 16use crate::type_::PRELUDE_MODULE_NAME; 17use crate::{ 18 ast::{CustomType, Function, Import, ModuleConstant, TypeAlias, *}, 19 docvec, 20 line_numbers::LineNumbers, 21 pretty::*, 22}; 23use camino::Utf8Path; 24use ecow::{EcoString, eco_format}; 25use expression::Context; 26use itertools::Itertools; 27 28use self::import::{Imports, Member}; 29 30const INDENT: isize = 2; 31 32pub const PRELUDE: &str = include_str!("../templates/prelude.mjs"); 33pub const PRELUDE_TS_DEF: &str = include_str!("../templates/prelude.d.mts"); 34 35pub type Output<'a> = Result<Document<'a>, Error>; 36 37#[derive(Debug, Clone, Copy, PartialEq, Eq)] 38pub enum JavaScriptCodegenTarget { 39 JavaScript, 40 TypeScriptDeclarations, 41} 42 43#[derive(Debug)] 44pub struct Generator<'a> { 45 line_numbers: &'a LineNumbers, 46 module: &'a TypedModule, 47 project_root: &'a Utf8Path, 48 tracker: UsageTracker, 49 module_scope: im::HashMap<EcoString, usize>, 50 current_module_name_segments_count: usize, 51 target_support: TargetSupport, 52 typescript: TypeScriptDeclarations, 53 stdlib_package: StdlibPackage, 54} 55 56impl<'a> Generator<'a> { 57 pub fn new(config: ModuleConfig<'a>) -> Self { 58 let ModuleConfig { 59 target_support, 60 typescript, 61 stdlib_package, 62 module, 63 line_numbers, 64 src: _, 65 path: _, 66 project_root, 67 } = config; 68 let current_module_name_segments_count = module.name.split('/').count(); 69 70 Self { 71 current_module_name_segments_count, 72 line_numbers, 73 project_root, 74 module, 75 tracker: UsageTracker::default(), 76 module_scope: Default::default(), 77 target_support, 78 typescript, 79 stdlib_package, 80 } 81 } 82 83 fn type_reference(&self) -> Document<'a> { 84 if self.typescript == TypeScriptDeclarations::None { 85 return "".to_doc(); 86 } 87 88 // Get the name of the module relative the directory (similar to basename) 89 let module = self 90 .module 91 .name 92 .as_str() 93 .split('/') 94 .next_back() 95 .expect("JavaScript generator could not identify imported module name."); 96 97 docvec!["/// <reference types=\"./", module, ".d.mts\" />", line()] 98 } 99 100 pub fn compile(&mut self) -> Output<'a> { 101 let type_reference = self.type_reference(); 102 103 // Determine what JavaScript imports we need to generate 104 let mut imports = self.collect_imports(); 105 106 // Determine what names are defined in the module scope so we know to 107 // rename any variables that are defined within functions using the same 108 // names. 109 self.register_module_definitions_in_scope(); 110 111 // Generate JavaScript code for each statement 112 let statements = self.collect_definitions().into_iter().chain( 113 self.module 114 .definitions 115 .iter() 116 .flat_map(|s| self.statement(s)), 117 ); 118 119 // Two lines between each statement 120 let mut statements: Vec<_> = 121 Itertools::intersperse(statements, Ok(lines(2))).try_collect()?; 122 123 // Import any prelude functions that have been used 124 125 if self.tracker.ok_used { 126 self.register_prelude_usage(&mut imports, "Ok", None); 127 }; 128 129 if self.tracker.error_used { 130 self.register_prelude_usage(&mut imports, "Error", None); 131 }; 132 133 if self.tracker.list_used { 134 self.register_prelude_usage(&mut imports, "toList", None); 135 }; 136 137 if self.tracker.list_empty_class_used { 138 self.register_prelude_usage(&mut imports, "Empty", Some("$Empty")); 139 }; 140 141 if self.tracker.list_non_empty_class_used { 142 self.register_prelude_usage(&mut imports, "NonEmpty", Some("$NonEmpty")); 143 }; 144 145 if self.tracker.prepend_used { 146 self.register_prelude_usage(&mut imports, "prepend", Some("listPrepend")); 147 }; 148 149 if self.tracker.custom_type_used || self.tracker.echo_used { 150 self.register_prelude_usage(&mut imports, "CustomType", Some("$CustomType")); 151 }; 152 153 if self.tracker.make_error_used { 154 self.register_prelude_usage(&mut imports, "makeError", None); 155 }; 156 157 if self.tracker.int_remainder_used { 158 self.register_prelude_usage(&mut imports, "remainderInt", None); 159 }; 160 161 if self.tracker.float_division_used { 162 self.register_prelude_usage(&mut imports, "divideFloat", None); 163 }; 164 165 if self.tracker.int_division_used { 166 self.register_prelude_usage(&mut imports, "divideInt", None); 167 }; 168 169 if self.tracker.object_equality_used { 170 self.register_prelude_usage(&mut imports, "isEqual", None); 171 }; 172 173 if self.tracker.bit_array_literal_used { 174 self.register_prelude_usage(&mut imports, "toBitArray", None); 175 } 176 177 if self.tracker.bit_array_slice_used || self.tracker.echo_used { 178 self.register_prelude_usage(&mut imports, "bitArraySlice", None); 179 } 180 181 if self.tracker.bit_array_slice_to_float_used { 182 self.register_prelude_usage(&mut imports, "bitArraySliceToFloat", None); 183 } 184 185 if self.tracker.bit_array_slice_to_int_used || self.tracker.echo_used { 186 self.register_prelude_usage(&mut imports, "bitArraySliceToInt", None); 187 } 188 189 if self.tracker.sized_integer_segment_used { 190 self.register_prelude_usage(&mut imports, "sizedInt", None); 191 }; 192 193 if self.tracker.string_bit_array_segment_used { 194 self.register_prelude_usage(&mut imports, "stringBits", None); 195 }; 196 197 if self.tracker.codepoint_bit_array_segment_used { 198 self.register_prelude_usage(&mut imports, "codepointBits", None); 199 }; 200 201 if self.tracker.float_bit_array_segment_used { 202 self.register_prelude_usage(&mut imports, "sizedFloat", None); 203 }; 204 205 let echo = if self.tracker.echo_used { 206 if StdlibPackage::Present == self.stdlib_package { 207 self.register_import( 208 &mut imports, 209 "gleam_stdlib", 210 "dict", 211 &Some(( 212 AssignName::Variable("stdlib$dict".into()), 213 SrcSpan::default(), 214 )), 215 &[], 216 ); 217 } 218 self.register_prelude_usage(&mut imports, "BitArray", Some("$BitArray")); 219 self.register_prelude_usage(&mut imports, "List", Some("$List")); 220 self.register_prelude_usage(&mut imports, "UtfCodepoint", Some("$UtfCodepoint")); 221 docvec![line(), std::include_str!("../templates/echo.mjs"), line()] 222 } else { 223 nil() 224 }; 225 226 // Put it all together 227 228 if imports.is_empty() && statements.is_empty() { 229 Ok(docvec![type_reference, "export {}", line(), echo]) 230 } else if imports.is_empty() { 231 statements.push(line()); 232 Ok(docvec![type_reference, statements, echo]) 233 } else if statements.is_empty() { 234 Ok(docvec![ 235 type_reference, 236 imports.into_doc(JavaScriptCodegenTarget::JavaScript), 237 echo, 238 ]) 239 } else { 240 Ok(docvec![ 241 type_reference, 242 imports.into_doc(JavaScriptCodegenTarget::JavaScript), 243 line(), 244 statements, 245 line(), 246 echo 247 ]) 248 } 249 } 250 251 fn register_prelude_usage( 252 &self, 253 imports: &mut Imports<'a>, 254 name: &'static str, 255 alias: Option<&'static str>, 256 ) { 257 let path = self.import_path(&self.module.type_info.package, PRELUDE_MODULE_NAME); 258 let member = Member { 259 name: name.to_doc(), 260 alias: alias.map(|a| a.to_doc()), 261 }; 262 imports.register_module(path, [], [member]); 263 } 264 265 pub fn statement(&mut self, statement: &'a TypedDefinition) -> Option<Output<'a>> { 266 match statement { 267 Definition::TypeAlias(TypeAlias { .. }) => None, 268 269 // Handled in collect_imports 270 Definition::Import(Import { .. }) => None, 271 272 // Handled in collect_definitions 273 Definition::CustomType(CustomType { .. }) => None, 274 275 Definition::ModuleConstant(ModuleConstant { 276 publicity, 277 name, 278 value, 279 .. 280 }) => Some(self.module_constant(*publicity, name, value)), 281 282 Definition::Function(function) => { 283 // If there's an external JavaScript implementation then it will be imported, 284 // so we don't need to generate a function definition. 285 if function.external_javascript.is_some() { 286 return None; 287 } 288 289 // If the function does not support JavaScript then we don't need to generate 290 // a function definition. 291 if !function.implementations.supports(Target::JavaScript) { 292 return None; 293 } 294 295 self.module_function(function) 296 } 297 } 298 } 299 300 fn custom_type_definition( 301 &mut self, 302 constructors: &'a [TypedRecordConstructor], 303 publicity: Publicity, 304 opaque: bool, 305 ) -> Vec<Output<'a>> { 306 // If there's no constructors then there's nothing to do here. 307 if constructors.is_empty() { 308 return vec![]; 309 } 310 311 self.tracker.custom_type_used = true; 312 constructors 313 .iter() 314 .map(|constructor| Ok(self.record_definition(constructor, publicity, opaque))) 315 .collect() 316 } 317 318 fn record_definition( 319 &self, 320 constructor: &'a TypedRecordConstructor, 321 publicity: Publicity, 322 opaque: bool, 323 ) -> Document<'a> { 324 fn parameter((i, arg): (usize, &TypedRecordConstructorArg)) -> Document<'_> { 325 arg.label 326 .as_ref() 327 .map(|(_, s)| maybe_escape_identifier(s)) 328 .unwrap_or_else(|| eco_format!("x{i}")) 329 .to_doc() 330 } 331 332 let head = if publicity.is_private() || opaque { 333 "class " 334 } else { 335 "export class " 336 }; 337 let head = docvec![head, &constructor.name, " extends $CustomType {"]; 338 339 if constructor.arguments.is_empty() { 340 return head.append("}"); 341 }; 342 343 let parameters = join( 344 constructor.arguments.iter().enumerate().map(parameter), 345 break_(",", ", "), 346 ); 347 348 let constructor_body = join( 349 constructor.arguments.iter().enumerate().map(|(i, arg)| { 350 let var = parameter((i, arg)); 351 match &arg.label { 352 None => docvec!["this[", i, "] = ", var, ";"], 353 Some((_, name)) => { 354 docvec!["this.", maybe_escape_property_doc(name), " = ", var, ";"] 355 } 356 } 357 }), 358 line(), 359 ); 360 361 let class_body = docvec![ 362 line(), 363 "constructor(", 364 parameters, 365 ") {", 366 docvec![line(), "super();", line(), constructor_body].nest(INDENT), 367 line(), 368 "}", 369 ] 370 .nest(INDENT); 371 372 docvec![head, class_body, line(), "}"] 373 } 374 375 fn collect_definitions(&mut self) -> Vec<Output<'a>> { 376 self.module 377 .definitions 378 .iter() 379 .flat_map(|statement| match statement { 380 Definition::CustomType(CustomType { 381 publicity, 382 constructors, 383 opaque, 384 .. 385 }) => self.custom_type_definition(constructors, *publicity, *opaque), 386 387 Definition::Function(Function { .. }) 388 | Definition::TypeAlias(TypeAlias { .. }) 389 | Definition::Import(Import { .. }) 390 | Definition::ModuleConstant(ModuleConstant { .. }) => vec![], 391 }) 392 .collect() 393 } 394 395 fn collect_imports(&mut self) -> Imports<'a> { 396 let mut imports = Imports::new(); 397 398 for statement in &self.module.definitions { 399 match statement { 400 Definition::Import(Import { 401 module, 402 as_name, 403 unqualified_values: unqualified, 404 package, 405 .. 406 }) => { 407 self.register_import(&mut imports, package, module, as_name, unqualified); 408 } 409 410 Definition::Function(Function { 411 name: Some((_, name)), 412 publicity, 413 external_javascript: Some((module, function, _location)), 414 .. 415 }) => { 416 self.register_external_function( 417 &mut imports, 418 *publicity, 419 name, 420 module, 421 function, 422 ); 423 } 424 425 Definition::Function(Function { .. }) 426 | Definition::TypeAlias(TypeAlias { .. }) 427 | Definition::CustomType(CustomType { .. }) 428 | Definition::ModuleConstant(ModuleConstant { .. }) => (), 429 } 430 } 431 432 imports 433 } 434 435 fn import_path(&self, package: &'a str, module: &'a str) -> EcoString { 436 // TODO: strip shared prefixed between current module and imported 437 // module to avoid descending and climbing back out again 438 if package == self.module.type_info.package || package.is_empty() { 439 // Same package 440 match self.current_module_name_segments_count { 441 1 => eco_format!("./{module}.mjs"), 442 _ => { 443 let prefix = "../".repeat(self.current_module_name_segments_count - 1); 444 eco_format!("{prefix}{module}.mjs") 445 } 446 } 447 } else { 448 // Different package 449 let prefix = "../".repeat(self.current_module_name_segments_count); 450 eco_format!("{prefix}{package}/{module}.mjs") 451 } 452 } 453 454 fn register_import( 455 &mut self, 456 imports: &mut Imports<'a>, 457 package: &'a str, 458 module: &'a str, 459 as_name: &Option<(AssignName, SrcSpan)>, 460 unqualified: &[UnqualifiedImport], 461 ) { 462 let get_name = |module: &'a str| { 463 module 464 .split('/') 465 .next_back() 466 .expect("JavaScript generator could not identify imported module name.") 467 }; 468 469 let (discarded, module_name) = match as_name { 470 None => (false, get_name(module)), 471 Some((AssignName::Discard(_), _)) => (true, get_name(module)), 472 Some((AssignName::Variable(name), _)) => (false, name.as_str()), 473 }; 474 475 let module_name = eco_format!("${module_name}"); 476 let path = self.import_path(package, module); 477 let unqualified_imports = unqualified.iter().map(|i| { 478 let alias = i.as_name.as_ref().map(|n| { 479 self.register_in_scope(n); 480 maybe_escape_identifier(n).to_doc() 481 }); 482 let name = maybe_escape_identifier(&i.name).to_doc(); 483 Member { name, alias } 484 }); 485 486 let aliases = if discarded { vec![] } else { vec![module_name] }; 487 imports.register_module(path, aliases, unqualified_imports); 488 } 489 490 fn register_external_function( 491 &mut self, 492 imports: &mut Imports<'a>, 493 publicity: Publicity, 494 name: &'a str, 495 module: &'a str, 496 fun: &'a str, 497 ) { 498 let needs_escaping = !is_usable_js_identifier(name); 499 let member = Member { 500 name: fun.to_doc(), 501 alias: if name == fun && !needs_escaping { 502 None 503 } else if needs_escaping { 504 Some(escape_identifier(name).to_doc()) 505 } else { 506 Some(name.to_doc()) 507 }, 508 }; 509 if publicity.is_importable() { 510 imports.register_export(maybe_escape_identifier_string(name)) 511 } 512 imports.register_module(EcoString::from(module), [], [member]); 513 } 514 515 fn module_constant( 516 &mut self, 517 publicity: Publicity, 518 name: &'a EcoString, 519 value: &'a TypedConstant, 520 ) -> Output<'a> { 521 let head = if publicity.is_private() { 522 "const " 523 } else { 524 "export const " 525 }; 526 527 let mut generator = expression::Generator::new( 528 self.module.name.clone(), 529 &self.module.type_info.src_path, 530 self.project_root, 531 self.line_numbers, 532 "".into(), 533 vec![], 534 &mut self.tracker, 535 self.module_scope.clone(), 536 ); 537 538 let document = generator.constant_expression(Context::Constant, value)?; 539 540 Ok(docvec![ 541 head, 542 maybe_escape_identifier(name), 543 " = ", 544 document, 545 ";", 546 ]) 547 } 548 549 fn register_in_scope(&mut self, name: &str) { 550 let _ = self.module_scope.insert(name.into(), 0); 551 } 552 553 fn module_function(&mut self, function: &'a TypedFunction) -> Option<Output<'a>> { 554 let (_, name) = function 555 .name 556 .as_ref() 557 .expect("A module's function must be named"); 558 let argument_names = function 559 .arguments 560 .iter() 561 .map(|arg| arg.names.get_variable_name()) 562 .collect(); 563 let mut generator = expression::Generator::new( 564 self.module.name.clone(), 565 &self.module.type_info.src_path, 566 self.project_root, 567 self.line_numbers, 568 name.clone(), 569 argument_names, 570 &mut self.tracker, 571 self.module_scope.clone(), 572 ); 573 let head = if function.publicity.is_private() { 574 "function " 575 } else { 576 "export function " 577 }; 578 579 let body = match generator.function_body(&function.body, function.arguments.as_slice()) { 580 // No error, let's continue! 581 Ok(body) => body, 582 583 // There is an error coming from some expression that is not supported on JavaScript 584 // and the target support is not enforced. In this case we do not error, instead 585 // returning nothing which will cause no function to be generated. 586 Err(error) if error.is_unsupported() && !self.target_support.is_enforced() => { 587 return None; 588 } 589 590 // Some other error case which will be returned to the user. 591 Err(error) => return Some(Err(error)), 592 }; 593 594 let document = docvec![ 595 head, 596 maybe_escape_identifier(name.as_str()), 597 fun_args(function.arguments.as_slice(), generator.tail_recursion_used), 598 " {", 599 docvec![line(), body].nest(INDENT).group(), 600 line(), 601 "}", 602 ]; 603 Some(Ok(document)) 604 } 605 606 fn register_module_definitions_in_scope(&mut self) { 607 for statement in self.module.definitions.iter() { 608 match statement { 609 Definition::ModuleConstant(ModuleConstant { name, .. }) => { 610 self.register_in_scope(name) 611 } 612 613 Definition::Function(Function { name, .. }) => self.register_in_scope( 614 name.as_ref() 615 .map(|(_, name)| name) 616 .expect("Function in a definition must be named"), 617 ), 618 619 Definition::Import(Import { 620 unqualified_values: unqualified, 621 .. 622 }) => unqualified 623 .iter() 624 .for_each(|unq_import| self.register_in_scope(unq_import.used_name())), 625 626 Definition::TypeAlias(TypeAlias { .. }) 627 | Definition::CustomType(CustomType { .. }) => (), 628 } 629 } 630 } 631} 632 633#[derive(Debug)] 634pub struct ModuleConfig<'a> { 635 pub module: &'a TypedModule, 636 pub line_numbers: &'a LineNumbers, 637 pub src: &'a EcoString, 638 pub target_support: TargetSupport, 639 pub typescript: TypeScriptDeclarations, 640 pub stdlib_package: StdlibPackage, 641 pub path: &'a Utf8Path, 642 pub project_root: &'a Utf8Path, 643} 644 645pub fn module(config: ModuleConfig<'_>) -> Result<String, crate::Error> { 646 let path = config.path.to_path_buf(); 647 let src = config.src.clone(); 648 let document = Generator::new(config) 649 .compile() 650 .map_err(|error| crate::Error::JavaScript { path, src, error })?; 651 Ok(document.to_pretty_string(80)) 652} 653 654pub fn ts_declaration( 655 module: &TypedModule, 656 path: &Utf8Path, 657 src: &EcoString, 658) -> Result<String, crate::Error> { 659 let document = typescript::TypeScriptGenerator::new(module) 660 .compile() 661 .map_err(|error| crate::Error::JavaScript { 662 path: path.to_path_buf(), 663 src: src.clone(), 664 error, 665 })?; 666 Ok(document.to_pretty_string(80)) 667} 668 669#[derive(Debug, Clone, PartialEq, Eq)] 670pub enum Error { 671 Unsupported { feature: String, location: SrcSpan }, 672} 673 674impl Error { 675 /// Returns `true` if the error is [`Unsupported`]. 676 /// 677 /// [`Unsupported`]: Error::Unsupported 678 #[must_use] 679 pub fn is_unsupported(&self) -> bool { 680 matches!(self, Self::Unsupported { .. }) 681 } 682} 683 684fn fun_args(args: &'_ [TypedArg], tail_recursion_used: bool) -> Document<'_> { 685 let mut discards = 0; 686 wrap_args(args.iter().map(|a| match a.get_variable_name() { 687 None => { 688 let doc = if discards == 0 { 689 "_".to_doc() 690 } else { 691 eco_format!("_{discards}").to_doc() 692 }; 693 discards += 1; 694 doc 695 } 696 Some(name) if tail_recursion_used => eco_format!("loop${name}").to_doc(), 697 Some(name) => maybe_escape_identifier(name).to_doc(), 698 })) 699} 700 701fn wrap_args<'a, I>(args: I) -> Document<'a> 702where 703 I: IntoIterator<Item = Document<'a>>, 704{ 705 break_("", "") 706 .append(join(args, break_(",", ", "))) 707 .nest(INDENT) 708 .append(break_("", "")) 709 .surround("(", ")") 710 .group() 711} 712 713fn wrap_object<'a>( 714 items: impl IntoIterator<Item = (Document<'a>, Option<Document<'a>>)>, 715) -> Document<'a> { 716 let mut empty = true; 717 let fields = items.into_iter().map(|(key, value)| { 718 empty = false; 719 match value { 720 Some(value) => docvec![key, ": ", value], 721 None => key.to_doc(), 722 } 723 }); 724 let fields = join(fields, break_(",", ", ")); 725 726 if empty { 727 "{}".to_doc() 728 } else { 729 docvec![ 730 docvec!["{", break_("", " "), fields] 731 .nest(INDENT) 732 .append(break_("", " ")) 733 .group(), 734 "}" 735 ] 736 } 737} 738 739fn is_usable_js_identifier(word: &str) -> bool { 740 !matches!( 741 word, 742 // Keywords and reserved words 743 // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar 744 "await" 745 | "arguments" 746 | "break" 747 | "case" 748 | "catch" 749 | "class" 750 | "const" 751 | "continue" 752 | "debugger" 753 | "default" 754 | "delete" 755 | "do" 756 | "else" 757 | "enum" 758 | "export" 759 | "extends" 760 | "eval" 761 | "false" 762 | "finally" 763 | "for" 764 | "function" 765 | "if" 766 | "implements" 767 | "import" 768 | "in" 769 | "instanceof" 770 | "interface" 771 | "let" 772 | "new" 773 | "null" 774 | "package" 775 | "private" 776 | "protected" 777 | "public" 778 | "return" 779 | "static" 780 | "super" 781 | "switch" 782 | "this" 783 | "throw" 784 | "true" 785 | "try" 786 | "typeof" 787 | "var" 788 | "void" 789 | "while" 790 | "with" 791 | "yield" 792 // `undefined` to avoid any unintentional overriding. 793 | "undefined" 794 // `then` to avoid a module that defines a `then` function being 795 // used as a `thenable` in JavaScript when the module is imported 796 // dynamically, which results in unexpected behaviour. 797 // It is rather unfortunate that we have to do this. 798 | "then" 799 ) 800} 801 802fn is_usable_js_property(label: &str) -> bool { 803 !matches!( 804 label, 805 // `then` to avoid a custom type that defines a `then` function being used as a `thenable` 806 // in Javascript. 807 "then" 808 // `constructor` to avoid unintentional overriding of the constructor of records, 809 // leading to potential runtime crashes while using `withFields`. 810 | "constructor" 811 // `prototype` and `__proto__` to avoid unintentionally overriding the prototype chain 812 | "prototype" 813 | "__proto__" 814 ) 815} 816 817fn maybe_escape_identifier_string(word: &str) -> EcoString { 818 if is_usable_js_identifier(word) { 819 EcoString::from(word) 820 } else { 821 escape_identifier(word) 822 } 823} 824 825fn escape_identifier(word: &str) -> EcoString { 826 eco_format!("{word}$") 827} 828 829fn maybe_escape_identifier(word: &str) -> EcoString { 830 if is_usable_js_identifier(word) { 831 EcoString::from(word) 832 } else { 833 escape_identifier(word) 834 } 835} 836 837fn maybe_escape_property_doc(label: &str) -> Document<'_> { 838 if is_usable_js_property(label) { 839 label.to_doc() 840 } else { 841 escape_identifier(label).to_doc() 842 } 843} 844 845#[derive(Debug, Default)] 846pub(crate) struct UsageTracker { 847 pub ok_used: bool, 848 pub list_used: bool, 849 pub list_empty_class_used: bool, 850 pub list_non_empty_class_used: bool, 851 pub prepend_used: bool, 852 pub error_used: bool, 853 pub int_remainder_used: bool, 854 pub make_error_used: bool, 855 pub custom_type_used: bool, 856 pub int_division_used: bool, 857 pub float_division_used: bool, 858 pub object_equality_used: bool, 859 pub bit_array_literal_used: bool, 860 pub bit_array_slice_used: bool, 861 pub bit_array_slice_to_float_used: bool, 862 pub bit_array_slice_to_int_used: bool, 863 pub sized_integer_segment_used: bool, 864 pub string_bit_array_segment_used: bool, 865 pub codepoint_bit_array_segment_used: bool, 866 pub float_bit_array_segment_used: bool, 867 pub echo_used: bool, 868} 869 870fn bool(bool: bool) -> Document<'static> { 871 match bool { 872 true => "true".to_doc(), 873 false => "false".to_doc(), 874 } 875} 876 877/// Int segments <= 48 bits wide in bit arrays are within JavaScript's safe range and are evaluated 878/// at compile time when all inputs are known. This is done for both bit array expressions and 879/// pattern matching. 880/// 881/// Int segments of any size could be evaluated at compile time, but currently aren't due to the 882/// potential for causing large generated JS for inputs such as `<<0:8192>>`. 883/// 884pub(crate) const SAFE_INT_SEGMENT_MAX_SIZE: usize = 48; 885 886/// Evaluates the value of an Int segment in a bit array into its corresponding bytes. This avoids 887/// needing to do the evaluation at runtime when all inputs are known at compile-time. 888/// 889pub(crate) fn bit_array_segment_int_value_to_bytes( 890 mut value: BigInt, 891 size: BigInt, 892 endianness: Endianness, 893) -> Result<Vec<u8>, Error> { 894 // Clamp negative sizes to zero 895 let size = size.max(BigInt::ZERO); 896 897 // Convert size to u32. This is safe because this function isn't called with a size greater 898 // than `SAFE_INT_SEGMENT_MAX_SIZE`. 899 let size = size 900 .to_u32() 901 .expect("bit array segment size to be a valid u32"); 902 903 // Convert negative number to two's complement representation 904 if value < BigInt::ZERO { 905 let value_modulus = BigInt::from(2).pow(size); 906 value = &value_modulus + (value % &value_modulus); 907 } 908 909 // Convert value to the desired number of bytes 910 let mut bytes = vec![0u8; size as usize / 8]; 911 for byte in bytes.iter_mut() { 912 *byte = (&value % BigInt::from(256)) 913 .to_u8() 914 .expect("modulo result to be a valid u32"); 915 value /= BigInt::from(256); 916 } 917 918 if endianness.is_big() { 919 bytes.reverse(); 920 } 921 922 Ok(bytes) 923}