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

Configure Feed

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

Merge `LetPrinter` with `CasePrinter` in javascript code generation

+200 -449
+59
compiler-core/src/ast.rs
··· 2321 2321 | Pattern::Invalid { .. } => false, 2322 2322 } 2323 2323 } 2324 + 2325 + pub fn bound_variables(&self) -> Vec<EcoString> { 2326 + let mut variables = Vec::new(); 2327 + self.collect_bound_variables(&mut variables); 2328 + variables 2329 + } 2330 + 2331 + fn collect_bound_variables(&self, variables: &mut Vec<EcoString>) { 2332 + match self { 2333 + Pattern::Int { .. } 2334 + | Pattern::Float { .. } 2335 + | Pattern::String { .. } 2336 + | Pattern::Discard { .. } 2337 + | Pattern::Invalid { .. } => {} 2338 + 2339 + Pattern::Variable { name, .. } => variables.push(name.clone()), 2340 + Pattern::VarUsage { .. } => {} 2341 + Pattern::Assign { name, pattern, .. } => { 2342 + variables.push(name.clone()); 2343 + pattern.collect_bound_variables(variables); 2344 + } 2345 + Pattern::List { elements, tail, .. } => { 2346 + for element in elements { 2347 + element.collect_bound_variables(variables); 2348 + } 2349 + if let Some(tail) = tail { 2350 + tail.collect_bound_variables(variables); 2351 + } 2352 + } 2353 + Pattern::Constructor { arguments, .. } => { 2354 + for argument in arguments { 2355 + argument.value.collect_bound_variables(variables); 2356 + } 2357 + } 2358 + Pattern::Tuple { elements, .. } => { 2359 + for element in elements { 2360 + element.collect_bound_variables(variables); 2361 + } 2362 + } 2363 + Pattern::BitArray { segments, .. } => { 2364 + for segment in segments { 2365 + segment.value.collect_bound_variables(variables); 2366 + } 2367 + } 2368 + Pattern::StringPrefix { 2369 + left_side_assignment, 2370 + right_side_assignment, 2371 + .. 2372 + } => { 2373 + if let Some((left_variable, _)) = left_side_assignment { 2374 + variables.push(left_variable.clone()); 2375 + } 2376 + match right_side_assignment { 2377 + AssignName::Variable(name) => variables.push(name.clone()), 2378 + AssignName::Discard(_) => {} 2379 + } 2380 + } 2381 + } 2382 + } 2324 2383 } 2325 2384 2326 2385 #[derive(Debug, Default)]
+140 -448
compiler-core/src/javascript/decision.rs
··· 3 3 expression::{self, Generator, Ordering, float, int}, 4 4 }; 5 5 use crate::{ 6 - ast::{AssignmentKind, Endianness, SrcSpan, TypedClause, TypedExpr}, 6 + ast::{AssignmentKind, Endianness, SrcSpan, TypedClause, TypedExpr, TypedPattern}, 7 7 docvec, 8 8 exhaustiveness::{ 9 9 BitArrayMatchedValue, BitArrayTest, Body, BoundValue, CompiledCase, Decision, ··· 15 15 expression::{eco_string_int, string}, 16 16 maybe_escape_property, 17 17 }, 18 - pretty::{Document, Documentable, break_, join, line, nil}, 18 + pretty::{Document, Documentable, break_, concat, join, line, nil}, 19 19 strings::{ 20 20 convert_string_escape_chars, length_utf16, string_to_utf16_bytes, string_to_utf32_bytes, 21 21 }, ··· 23 23 use ecow::{EcoString, eco_format}; 24 24 use itertools::Itertools; 25 25 use num_bigint::BigInt; 26 - use std::{ 27 - collections::{HashMap, VecDeque}, 28 - sync::OnceLock, 29 - }; 26 + use std::{collections::HashMap, sync::OnceLock}; 30 27 31 28 pub static ASSIGNMENT_VAR: &str = "$"; 32 29 ··· 36 33 subjects: &'a [TypedExpr], 37 34 expression_generator: &mut Generator<'_, 'a>, 38 35 ) -> Document<'a> { 39 - let mut variables = Variables::new(expression_generator); 36 + let mut variables = Variables::new(expression_generator, false); 40 37 let assignments = variables.assign_case_subjects(compiled_case, subjects); 41 - let decision = CasePrinter { clauses, variables }.decision(&compiled_case.tree); 38 + let decision = CasePrinter { 39 + variables, 40 + kind: DecisionKind::Case { clauses }, 41 + } 42 + .decision(&compiled_case.tree); 42 43 docvec![assignments_to_doc(assignments), decision.into_doc()].force_break() 43 44 } 44 45 ··· 128 129 } 129 130 130 131 struct CasePrinter<'module, 'generator, 'a> { 131 - clauses: &'a [TypedClause], 132 132 variables: Variables<'generator, 'module, 'a>, 133 + kind: DecisionKind<'a>, 134 + } 135 + 136 + enum DecisionKind<'a> { 137 + Case { 138 + clauses: &'a [TypedClause], 139 + }, 140 + LetAssert { 141 + kind: &'a AssignmentKind<TypedExpr>, 142 + subject_location: SrcSpan, 143 + pattern_location: SrcSpan, 144 + subject: EcoString, 145 + }, 133 146 } 134 147 135 148 /// Code generation for decision trees can look a bit daunting at a first glance ··· 196 209 impl<'a> CasePrinter<'_, '_, 'a> { 197 210 fn decision(&mut self, decision: &'a Decision) -> CaseBody<'a> { 198 211 match decision { 199 - Decision::Fail => unreachable!("Invalid decision tree reached code generation"), 212 + Decision::Fail => { 213 + if let DecisionKind::LetAssert { 214 + kind, 215 + subject_location, 216 + pattern_location, 217 + subject, 218 + } = &self.kind 219 + { 220 + CaseBody::Statements(self.assignment_no_match( 221 + subject.to_doc(), 222 + kind, 223 + *subject_location, 224 + *pattern_location, 225 + )) 226 + } else { 227 + unreachable!("Invalid decision tree reached code generation") 228 + } 229 + } 200 230 Decision::Run { body } => { 201 231 let bindings = self.variables.bindings_doc(&body.bindings); 202 232 let body = self.body_expression(body.clause_index); ··· 217 247 } 218 248 219 249 fn body_expression(&mut self, clause_index: usize) -> Document<'a> { 220 - let body = &self 221 - .clauses 250 + let DecisionKind::Case { clauses } = &self.kind else { 251 + return nil(); 252 + }; 253 + 254 + let body = &clauses 222 255 .get(clause_index) 223 256 .expect("invalid clause index") 224 257 .then; ··· 272 305 // referenced by this check 273 306 let (check_doc, body, mut segment_assignments) = self.inside_new_scope(|this| { 274 307 let segment_assignments = this.variables.bit_array_segment_assignments(check); 275 - let check_doc = this 276 - .variables 277 - .runtime_check(var, check, CheckNegation::NotNegated); 308 + let check_doc = this.variables.runtime_check(var, check); 278 309 let body = this.decision(decision); 279 310 (check_doc, body, segment_assignments) 280 311 }); ··· 447 478 if_true: &'a Body, 448 479 if_false: &'a Decision, 449 480 ) -> CaseBody<'a> { 450 - let guard = self 451 - .clauses 481 + let DecisionKind::Case { clauses } = &self.kind else { 482 + unreachable!("Guards cannot appear in let assert decision trees") 483 + }; 484 + 485 + let guard = clauses 452 486 .get(guard) 453 487 .expect("invalid clause index") 454 488 .guard ··· 496 530 CaseBody::Statements(join_with_line(check_bindings, if_.into_doc())) 497 531 } 498 532 } 499 - } 500 533 501 - /// Prints the code for a let assignment (it could either be a let assert or 502 - /// just a let, either cases are handled correctly by the decision 503 - /// tree!) 504 - /// 505 - pub fn let_<'a>( 506 - compiled_case: &'a CompiledCase, 507 - subject: &'a TypedExpr, 508 - kind: &'a AssignmentKind<TypedExpr>, 509 - expression_generator: &mut Generator<'_, 'a>, 510 - pattern_location: SrcSpan, 511 - ) -> Document<'a> { 512 - let scope_position = expression_generator.scope_position.clone(); 513 - let mut variables = Variables::new(expression_generator); 514 - let assignment = variables.assign_let_subject(compiled_case, subject); 515 - let assignment_name = assignment.name(); 516 - let decision = LetPrinter::new(variables, kind, pattern_location, subject.location()) 517 - .decision(assignment_name.clone().to_doc(), &compiled_case.tree); 518 - 519 - let doc = docvec![assignments_to_doc(vec![assignment]), decision]; 520 - match scope_position { 521 - expression::Position::NotTail(_ordering) => doc, 522 - expression::Position::Tail => docvec![doc, line(), "return ", assignment_name, ";"], 523 - expression::Position::Assign(variable) => { 524 - docvec![doc, line(), variable, " = ", assignment_name, ";"] 525 - } 526 - } 527 - } 528 - 529 - struct LetPrinter<'generator, 'module, 'a> { 530 - variables: Variables<'generator, 'module, 'a>, 531 - kind: &'a AssignmentKind<TypedExpr>, 532 - // If the let assert is always going to fail it is redundant and we can 533 - // directly generate an exception instead of first performing some checks 534 - // that we know are going to match. 535 - is_redundant: bool, 536 - pattern_location: SrcSpan, 537 - subject_location: SrcSpan, 538 - } 539 - 540 - /// Generating code for a let (or let assert) assignment is quite different from 541 - /// case expressions. A lot of code can be shared between the two but the 542 - /// generated `if-else` code will look a lot different. 543 - /// 544 - /// A let is always guaranteed by the type system to succeed, and 545 - /// we don't want to generate any redundant checks for those. At the same time 546 - /// we want to turn a let-assert into a single if statement that looks like this: 547 - /// 548 - /// ```gleam 549 - /// let assert Ok(1) = result 550 - /// ``` 551 - /// 552 - /// ```js 553 - /// if (!result.isOk() || result[0] !== 1) { 554 - /// // throw exception 555 - /// } 556 - /// 557 - /// // keep going here... 558 - /// ``` 559 - /// 560 - /// So we have to throw an exception if any of the checks that we need to perform 561 - /// would fail. Otherwise the let assert is successfull and we can get a hold of 562 - /// the variables that were bound in the pattern. 563 - /// 564 - impl<'generator, 'module, 'a> LetPrinter<'generator, 'module, 'a> { 565 - fn new( 566 - variables: Variables<'generator, 'module, 'a>, 534 + fn assignment_no_match( 535 + &mut self, 536 + subject: Document<'a>, 567 537 kind: &'a AssignmentKind<TypedExpr>, 568 - pattern_location: SrcSpan, 569 538 subject_location: SrcSpan, 570 - ) -> Self { 571 - Self { 572 - variables, 573 - kind, 574 - is_redundant: true, 575 - pattern_location, 576 - subject_location, 577 - } 578 - } 579 - 580 - fn decision(&mut self, subject: Document<'a>, decision: &'a Decision) -> Document<'a> { 581 - let Some(ChecksAndBindings { checks, bindings }) = 582 - self.positive_checks_and_bindings(decision) 583 - else { 584 - // In case we never reach a body, we know that this let assert will 585 - // always throw an exception! 586 - self.variables.expression_generator.let_assert_always_panics = true; 587 - return self.assignment_no_match(subject); 588 - }; 589 - 590 - for (variable, check) in checks.iter() { 591 - self.variables.record_check_assignments(variable, check); 592 - } 593 - 594 - let checks = if self.is_redundant { 595 - nil() 596 - } else { 597 - let checks = checks.iter().filter_map(|(variable, check)| { 598 - // Just like with a case expression, we still need to keep track 599 - // of all the variables introduced by successful checks. 600 - self.variables.record_check_assignments(variable, check); 601 - // We then generate a negated version of the runtime check: if 602 - // any of these evaluates to false we know we can throw an 603 - // exception. 604 - match check { 605 - // We never generate runtime checks for tuples, so we skip 606 - // those altogether here. 607 - RuntimeCheck::Tuple { .. } => None, 608 - RuntimeCheck::Variant { .. } if variable.type_.is_nil() => None, 609 - _ => Some(self.variables.runtime_check( 610 - variable, 611 - check, 612 - CheckNegation::Negated, 613 - )), 614 - } 615 - }); 616 - 617 - docvec![break_("", ""), join(checks, break_(" ||", " || "))] 618 - .nest(INDENT) 619 - .append(break_("", "")) 620 - .group() 621 - }; 622 - 623 - let doc = if checks.is_empty() { 624 - nil() 625 - } else { 626 - let exception = self.assignment_no_match(subject); 627 - docvec!["if (", checks, ") ", break_block(exception)] 628 - }; 629 - 630 - let body_bindings = bindings 631 - .iter() 632 - .map(|(name, value)| self.variables.body_binding_doc(name, value)); 633 - let body_bindings = join(body_bindings, line()); 634 - join_with_line(doc, body_bindings) 635 - } 636 - 637 - fn assignment_no_match(&mut self, subject: Document<'a>) -> Document<'a> { 539 + pattern_location: SrcSpan, 540 + ) -> Document<'a> { 638 541 let AssignmentKind::Assert { 639 542 location, message, .. 640 - } = self.kind 543 + } = kind 641 544 else { 642 545 unreachable!("inexhaustive let made it to code generation"); 643 546 }; ··· 655 558 [ 656 559 ("value", subject), 657 560 ("start", location.start.to_doc()), 658 - ("end", self.subject_location.end.to_doc()), 659 - ("pattern_start", self.pattern_location.start.to_doc()), 660 - ("pattern_end", self.pattern_location.end.to_doc()), 561 + ("end", subject_location.end.to_doc()), 562 + ("pattern_start", pattern_location.start.to_doc()), 563 + ("pattern_end", pattern_location.end.to_doc()), 661 564 ], 662 565 ) 663 566 } 664 - 665 - /// A let decision tree has a very precise structure since it's made of a 666 - /// single pattern. It will always be a very narrow tree that has a singe 667 - /// success node with a series of checks leading down to it. If any of those 668 - /// checks fails it immediately leads to a `Fail` node that has to result 669 - /// in an exception. For example: 670 - /// 671 - /// ```gleam 672 - /// let assert [1, 2, ..] = list 673 - /// ``` 674 - /// 675 - /// Will have to check that the first item is `1` and the second one is `2`, 676 - /// if any of those checks fail the entire assignment fails. 677 - /// 678 - /// So we can traverse such a decision tree and collect the sequence of checks 679 - /// that need to succeed in order for the assignment to succeed. We also return 680 - /// all the bindings that we discover in the final `Run` block so they can be 681 - /// used by later code generation steps without having to traverse the whole 682 - /// decision tree once again! 683 - /// 684 - /// If there's no `Run` node it means that this pattern will _always_ fail 685 - /// and there's no useful data we could ever return. 686 - /// This could happen due to variant inference (e.g. `let assert Wibble = Wobble`), 687 - /// in that case the assert is redundant. 688 - /// 689 - fn positive_checks_and_bindings( 690 - &mut self, 691 - decision: &'a Decision, 692 - ) -> Option<ChecksAndBindings<'a>> { 693 - let ChecksAndBindings { 694 - mut checks, 695 - bindings, 696 - } = self.checks_and_bindings_loop(decision)?; 697 - 698 - // Now we try and reduce the number of size checks that bit array patterns 699 - // need to perform. In particular if all size checks do not depend on any 700 - // previous segment then we can remove all the size checks except the last 701 - // one, as it implies all the other ones! 702 - 703 - // First, we create a map from variable IDs to information about the 704 - // size check we need to perform, as different bit array variables must 705 - // still be checked separately. 706 - let mut size_checks = HashMap::new(); 707 - // Since we are removing some checks, the check list will get shorter, 708 - // shifting all the indices down. This variable keeps track of the current 709 - // offset so we can correctly calculate the new index of each check. 710 - let mut index_offset = 0; 711 - 712 - for (index, (variable, check)) in checks.iter().enumerate() { 713 - if !check.referenced_segment_patterns().is_empty() { 714 - // If any of the checks does reference a previous segment then 715 - // it's not safe to remove intermediate checks! In that case we 716 - // do not try and perform any optimisation and return all the 717 - // checks as they are. 718 - return Some(ChecksAndBindings { checks, bindings }); 719 - } 720 - 721 - if let RuntimeCheck::BitArray { 722 - test: BitArrayTest::Size(_), 723 - } = check 724 - { 725 - match size_checks.get_mut(&variable.id) { 726 - // If this is the first occurrence of a size check for this 727 - // variable, we record its position as well as the other 728 - // information. 729 - None => { 730 - _ = size_checks.insert( 731 - variable.id, 732 - SizeCheck { 733 - first_occurrence: index - index_offset, 734 - variable: variable.clone(), 735 - check, 736 - }, 737 - ) 738 - } 739 - // If another size check has already appeared, we simply replace 740 - // the check itself, as the later one is a more restrictive check. 741 - Some(size_check) => { 742 - size_check.check = *check; 743 - // We also increment the offset as the previous size check will 744 - // be removed, further offsetting any checks that come after this. 745 - index_offset += 1; 746 - } 747 - } 748 - } 749 - } 567 + } 750 568 751 - if size_checks.is_empty() { 752 - // If there's no size test at all, then there's no meaningful optimisation 753 - // we can apply! 754 - return Some(ChecksAndBindings { checks, bindings }); 755 - }; 756 - 757 - checks.retain(|(_variable, check)| { 758 - if let RuntimeCheck::BitArray { 759 - test: BitArrayTest::Size(_), 760 - } = check 761 - { 762 - false 763 - } else { 764 - true 765 - } 766 - }); 767 - 768 - let mut size_checks = size_checks.into_values().collect_vec(); 769 - // We must insert the size checks in the order they appear, to ensure the 770 - // correct ordering of the final checks. 771 - size_checks.sort_by_key(|check| check.first_occurrence); 569 + pub fn let_<'a>( 570 + compiled_case: &'a CompiledCase, 571 + subject: &'a TypedExpr, 572 + kind: &'a AssignmentKind<TypedExpr>, 573 + expression_generator: &mut Generator<'_, 'a>, 574 + pattern: &'a TypedPattern, 575 + ) -> Document<'a> { 576 + let _ = pattern; 577 + let scope_position = expression_generator.scope_position.clone(); 578 + let mut variables = Variables::new(expression_generator, true); 772 579 773 - for size_check in size_checks { 774 - checks.insert( 775 - size_check.first_occurrence, 776 - (size_check.variable, size_check.check), 777 - ); 778 - } 580 + let assignment = variables.assign_let_subject(compiled_case, subject); 581 + let assignment_name = assignment.name(); 779 582 780 - Some(ChecksAndBindings { checks, bindings }) 583 + let decision = CasePrinter { 584 + variables, 585 + kind: DecisionKind::LetAssert { 586 + kind, 587 + subject_location: subject.location(), 588 + pattern_location: pattern.location(), 589 + subject: assignment_name.clone(), 590 + }, 781 591 } 592 + .decision(&compiled_case.tree); 782 593 783 - fn checks_and_bindings_loop( 784 - &mut self, 785 - decision: &'a Decision, 786 - ) -> Option<ChecksAndBindings<'a>> { 787 - match decision { 788 - Decision::Run { body, .. } => Some(ChecksAndBindings::new(&body.bindings)), 789 - Decision::Guard { .. } => unreachable!("guard in let assert decision tree"), 790 - Decision::Fail => { 791 - // If the check could fail it means that the entire let is not 792 - // redundant! 793 - self.is_redundant = false; 794 - None 795 - } 796 - Decision::Switch { 797 - var, 798 - choices, 799 - fallback, 800 - fallback_check, 801 - } => { 802 - let mut result = None; 594 + let beginning_assignments = pattern.bound_variables().into_iter().map(|variable| { 595 + docvec![ 596 + "let ", 597 + expression_generator.local_var(&variable), 598 + ";", 599 + line() 600 + ] 601 + }); 803 602 804 - // We go over all the decision to record all the bindings and 805 - // see if we can get to a failing node. We will only keep the 806 - // results coming from the positive path! 807 - for (check, decision) in choices { 808 - if let Some(mut checks) = self.checks_and_bindings_loop(decision) { 809 - checks.add_check(var.clone(), check); 810 - result = Some(checks); 811 - }; 812 - } 603 + let doc = docvec![ 604 + assignments_to_doc(vec![assignment]), 605 + concat(beginning_assignments), 606 + decision.into_doc() 607 + ]; 813 608 814 - // Important not to forget the fallback check, that's a path we 815 - // might have to go down to as well! 816 - if let Some(mut checks) = self.checks_and_bindings_loop(fallback) { 817 - if let FallbackCheck::RuntimeCheck { check } = fallback_check { 818 - checks.add_check(var.clone(), check); 819 - result = Some(checks); 820 - }; 821 - }; 822 - 823 - result 824 - } 609 + match scope_position { 610 + expression::Position::NotTail(_ordering) => doc, 611 + expression::Position::Tail => docvec![doc, line(), "return ", assignment_name, ";"], 612 + expression::Position::Assign(variable) => { 613 + docvec![doc, line(), variable, " = ", assignment_name, ";"] 825 614 } 826 615 } 827 616 } 828 617 829 - #[derive(Debug)] 830 - struct SizeCheck<'a> { 831 - first_occurrence: usize, 832 - variable: Variable, 833 - check: &'a RuntimeCheck, 834 - } 835 - 836 - /// The result we get from inspecting a `let`'s decision tree: it contains all 837 - /// the checks that lead down to the only possible successfull `Body` node and 838 - /// the bindings found inside it. 839 - /// 840 - struct ChecksAndBindings<'a> { 841 - checks: VecDeque<(Variable, &'a RuntimeCheck)>, 842 - bindings: &'a Vec<(EcoString, BoundValue)>, 843 - } 844 - 845 - impl<'a> ChecksAndBindings<'a> { 846 - fn new(bindings: &'a Vec<(EcoString, BoundValue)>) -> Self { 847 - Self { 848 - checks: VecDeque::new(), 849 - bindings, 850 - } 851 - } 852 - 853 - fn add_check(&mut self, variable: Variable, check: &'a RuntimeCheck) { 854 - self.checks.push_front((variable, check)); 855 - } 856 - } 857 - 858 618 /// This is a useful piece of state that is kept separate from the generator 859 619 /// itself so we can reuse it both with `case`s and `let`s without rewriting 860 620 /// everything from scratch. 861 621 /// 862 622 struct Variables<'generator, 'module, 'a> { 863 623 expression_generator: &'generator mut Generator<'module, 'a>, 624 + use_reassignment: bool, 864 625 865 626 /// All the pattern variables will be assigned a specific value: being bound 866 627 /// to a constructor field, tuple element and so on. Pattern variables never ··· 930 691 } 931 692 932 693 impl<'generator, 'module, 'a> Variables<'generator, 'module, 'a> { 933 - fn new(expression_generator: &'generator mut Generator<'module, 'a>) -> Self { 694 + fn new( 695 + expression_generator: &'generator mut Generator<'module, 'a>, 696 + use_reassignment: bool, 697 + ) -> Self { 934 698 Variables { 935 699 expression_generator, 700 + use_reassignment, 936 701 variable_values: HashMap::new(), 937 702 scoped_variable_names: HashMap::new(), 938 703 segment_values: HashMap::new(), ··· 1113 878 .get_segment_value(variable_name) 1114 879 .unwrap_or_else(|| self.read_action_to_doc(bit_array, read_action)), 1115 880 }; 1116 - let_doc(local_variable_name.clone(), assigned_value) 881 + 882 + if self.use_reassignment { 883 + reassignment_doc(local_variable_name.clone(), assigned_value) 884 + } else { 885 + let_doc(local_variable_name.clone(), assigned_value) 886 + } 1117 887 } 1118 888 1119 889 /// Generates the document to perform a (possibly negated) runtime check on ··· 1123 893 &mut self, 1124 894 variable: &Variable, 1125 895 runtime_check: &'a RuntimeCheck, 1126 - negation: CheckNegation, 1127 896 ) -> Document<'a> { 1128 897 let value = self.get_value(variable); 1129 898 1130 - let equality = if negation.is_negated() { 1131 - " !== " 1132 - } else { 1133 - " === " 1134 - }; 899 + let equality = " === "; 1135 900 1136 901 match runtime_check { 1137 902 RuntimeCheck::String { value: expected } => docvec![value, equality, string(expected)], 1138 903 RuntimeCheck::Float { value: expected } => docvec![value, equality, float(expected)], 1139 904 RuntimeCheck::Int { value: expected } => docvec![value, equality, int(expected)], 1140 905 RuntimeCheck::StringPrefix { prefix, .. } => { 1141 - let negation = if negation.is_negated() { 1142 - "!".to_doc() 1143 - } else { 1144 - nil() 1145 - }; 1146 - 1147 - docvec![negation, value, ".startsWith(", string(prefix), ")"] 906 + docvec![value, ".startsWith(", string(prefix), ")"] 1148 907 } 1149 908 1150 909 RuntimeCheck::BitArray { test } => match test { ··· 1161 920 } 1162 921 1163 922 BitArrayTest::VariableIsNotNegative { variable } => { 1164 - if negation.is_negated() { 1165 - docvec![self.local_var(variable.name()), " < 0"] 1166 - } else { 1167 - docvec![self.local_var(variable.name()), " >= 0"] 1168 - } 923 + docvec![self.local_var(variable.name()), " >= 0"] 1169 924 } 1170 925 1171 926 BitArrayTest::SegmentIsFiniteFloat { ··· 1187 942 }; 1188 943 let check = self.bit_array_slice_to_float(value, start_doc, end, endianness); 1189 944 1190 - if negation.is_negated() { 1191 - docvec!["!Number.isFinite(", check, ")"] 1192 - } else { 1193 - docvec!["Number.isFinite(", check, ")"] 1194 - } 945 + docvec!["Number.isFinite(", check, ")"] 1195 946 } 1196 947 1197 948 // Here we need to make sure that the bit array has a specific 1198 949 // size. 1199 950 BitArrayTest::Size(SizeTest { operator, size }) => { 1200 951 let operator = match operator { 1201 - SizeOperator::GreaterEqual if negation.is_negated() => " < ", 1202 952 SizeOperator::GreaterEqual => " >= ", 1203 953 SizeOperator::Equal => equality, 1204 954 }; ··· 1219 969 value, 1220 970 expected, 1221 971 read_action, 1222 - negation, 1223 972 *encoding, 1224 973 ), 1225 - BitArrayMatchedValue::LiteralFloat(expected) => self 1226 - .literal_float_segment_bytes_check(value, expected, read_action, negation), 1227 - BitArrayMatchedValue::LiteralInt(expected) => self 1228 - .literal_int_segment_bytes_check( 1229 - value, 1230 - expected.clone(), 1231 - read_action, 1232 - negation, 1233 - ), 974 + BitArrayMatchedValue::LiteralFloat(expected) => { 975 + self.literal_float_segment_bytes_check(value, expected, read_action) 976 + } 977 + BitArrayMatchedValue::LiteralInt(expected) => { 978 + self.literal_int_segment_bytes_check(value, expected.clone(), read_action) 979 + } 1234 980 BitArrayMatchedValue::Variable(..) 1235 981 | BitArrayMatchedValue::Discard(..) 1236 982 | BitArrayMatchedValue::Assign { .. } => { ··· 1247 993 // Some variants like `Bool` and `Result` are special cased and checked 1248 994 // in a different way from all other variants. 1249 995 RuntimeCheck::Variant { match_, .. } if variable.type_.is_bool() => { 1250 - match (match_.name().as_str(), negation) { 1251 - ("True", CheckNegation::NotNegated) => value.to_doc(), 1252 - ("True", CheckNegation::Negated) => docvec!["!", value], 1253 - (_, CheckNegation::NotNegated) => docvec!["!", value], 1254 - (_, CheckNegation::Negated) => value.to_doc(), 996 + match match_.name().as_str() { 997 + "True" => value.to_doc(), 998 + _ => docvec!["!", value], 1255 999 } 1256 1000 } 1257 1001 ··· 1269 1013 .map(|module| eco_format!("${module}.")) 1270 1014 .unwrap_or_default(); 1271 1015 1272 - let check = docvec![value, " instanceof ", qualification, match_.name()]; 1273 - if negation.is_negated() { 1274 - docvec!["!(", check, ")"] 1275 - } else { 1276 - check 1277 - } 1016 + docvec![value, " instanceof ", qualification, match_.name()] 1278 1017 } 1279 1018 1280 1019 RuntimeCheck::NonEmptyList { .. } => { 1281 - if negation.is_negated() { 1282 - self.expression_generator.tracker.list_empty_class_used = true; 1283 - docvec![value, " instanceof $Empty"] 1284 - } else { 1285 - self.expression_generator.tracker.list_non_empty_class_used = true; 1286 - docvec![value, " instanceof $NonEmpty"] 1287 - } 1020 + self.expression_generator.tracker.list_non_empty_class_used = true; 1021 + docvec![value, " instanceof $NonEmpty"] 1288 1022 } 1289 1023 1290 1024 RuntimeCheck::EmptyList => { 1291 - if negation.is_negated() { 1292 - self.expression_generator.tracker.list_non_empty_class_used = true; 1293 - docvec![value, " instanceof $NonEmpty"] 1294 - } else { 1295 - self.expression_generator.tracker.list_empty_class_used = true; 1296 - docvec![value, " instanceof $Empty"] 1297 - } 1025 + self.expression_generator.tracker.list_empty_class_used = true; 1026 + docvec![value, " instanceof $Empty"] 1298 1027 } 1299 1028 } 1300 1029 } ··· 1525 1254 bit_array: EcoString, 1526 1255 literal_string: &EcoString, 1527 1256 read_action: &ReadAction, 1528 - check_negation: CheckNegation, 1529 1257 encoding: StringEncoding, 1530 1258 ) -> Document<'a> { 1531 1259 let ReadAction { ··· 1536 1264 } = read_action; 1537 1265 let mut checks = vec![]; 1538 1266 1539 - let equality = if check_negation.is_negated() { 1540 - " !== " 1541 - } else { 1542 - " === " 1543 - }; 1267 + let equality = " === "; 1544 1268 1545 1269 let escaped = convert_string_escape_chars(literal_string); 1546 1270 // We need to have this vector here so that we don't run into lifetime ··· 1580 1304 } 1581 1305 } 1582 1306 1583 - if check_negation.is_negated() { 1584 - // If the check is negated, it fails if any of the byte checks fail. 1585 - join(checks, break_(" ||", " || ")).group() 1586 - } else { 1587 - // Otherwise the check succeeds if all the byte checks succeed. 1588 - join(checks, break_(" &&", " && ")).nest(INDENT).group() 1589 - } 1307 + // Otherwise the check succeeds if all the byte checks succeed. 1308 + join(checks, break_(" &&", " && ")).nest(INDENT).group() 1590 1309 } 1591 1310 1592 1311 /// This generates all the checks that need to be performed to make sure a ··· 1599 1318 bit_array: EcoString, 1600 1319 literal_int: BigInt, 1601 1320 read_action: &ReadAction, 1602 - check_negation: CheckNegation, 1603 1321 ) -> Document<'a> { 1604 1322 let ReadAction { 1605 1323 from: start, ··· 1609 1327 .. 1610 1328 } = read_action; 1611 1329 1612 - let equality = if check_negation.is_negated() { 1613 - " !== " 1614 - } else { 1615 - " === " 1616 - }; 1330 + let equality = " === "; 1617 1331 1618 1332 if let (Some(mut from_byte), Some(size)) = (start.constant_bytes(), size.constant_bytes()) { 1619 1333 // If the number starts at a byte-aligned offset and is made of a ··· 1626 1340 from_byte += 1; 1627 1341 } 1628 1342 1629 - if check_negation.is_negated() { 1630 - join(checks, break_(" ||", " || ")).group() 1631 - } else { 1632 - join(checks, break_(" &&", " && ")).nest(INDENT).group() 1633 - } 1343 + join(checks, break_(" &&", " && ")).nest(INDENT).group() 1634 1344 } else { 1635 1345 // Otherwise we have to take an int slice out of the bit array and 1636 1346 // check it matches the expected value. ··· 1657 1367 bit_array: EcoString, 1658 1368 expected: &EcoString, 1659 1369 read_action: &ReadAction, 1660 - check_negation: CheckNegation, 1661 1370 ) -> Document<'a> { 1662 1371 let ReadAction { 1663 1372 from: start, ··· 1666 1375 .. 1667 1376 } = read_action; 1668 1377 1669 - let equality = if check_negation.is_negated() { 1670 - " !== " 1671 - } else { 1672 - " === " 1673 - }; 1378 + let equality = " === "; 1674 1379 1675 1380 // Unlike literal integers and strings, for now we don't try and apply any 1676 1381 // optimisation in the way we match on those: we take an entire slice, ··· 1799 1504 } 1800 1505 } 1801 1506 1802 - #[derive(Eq, PartialEq)] 1803 - /// Wether a runtime check should be negated or not. 1804 - /// 1805 - enum CheckNegation { 1806 - Negated, 1807 - NotNegated, 1808 - } 1809 - 1810 - impl CheckNegation { 1811 - fn is_negated(&self) -> bool { 1812 - match self { 1813 - CheckNegation::Negated => true, 1814 - CheckNegation::NotNegated => false, 1815 - } 1816 - } 1817 - } 1818 - 1819 1507 /// When going over the subjects of a case expression/let we might end up in two 1820 1508 /// situation: the subject might be a variable or it could be a more complex 1821 1509 /// expression (like a function call, a complex expression, ...). ··· 1930 1618 } else { 1931 1619 docvec![one, line(), other] 1932 1620 } 1621 + } 1622 + 1623 + fn reassignment_doc(variable_name: EcoString, value: Document<'_>) -> Document<'_> { 1624 + docvec![variable_name, " = ", value, ";"] 1933 1625 } 1934 1626 1935 1627 fn let_doc(variable_name: EcoString, value: Document<'_>) -> Document<'_> {
+1 -1
compiler-core/src/javascript/expression.rs
··· 869 869 return self.simple_variable_assignment(name, value); 870 870 } 871 871 872 - decision::let_(compiled_case, value, kind, self, pattern.location()) 872 + decision::let_(compiled_case, value, kind, self, pattern) 873 873 } 874 874 875 875 fn assert(&mut self, assert: &'a TypedAssert) -> Document<'a> {