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

Configure Feed

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

first go at code gen with decision trees (bit arrays are missing!)

+1447 -814
+62
compiler-core/src/ast.rs
··· 25 25 use std::sync::Arc; 26 26 27 27 use ecow::EcoString; 28 + use im::{HashSet, hashset}; 28 29 use num_bigint::{BigInt, Sign}; 29 30 use num_traits::{One, ToPrimitive}; 30 31 #[cfg(test)] ··· 1868 1869 | ClauseGuard::LtEqFloat { .. } => type_::bool(), 1869 1870 } 1870 1871 } 1872 + 1873 + pub(crate) fn referenced_variables(&self) -> HashSet<&EcoString> { 1874 + match self { 1875 + ClauseGuard::Var { name, .. } => hashset![name], 1876 + 1877 + ClauseGuard::Not { expression, .. } => expression.referenced_variables(), 1878 + ClauseGuard::TupleIndex { tuple, .. } => tuple.referenced_variables(), 1879 + ClauseGuard::FieldAccess { container, .. } => container.referenced_variables(), 1880 + ClauseGuard::Constant(constant) => constant.referenced_variables(), 1881 + ClauseGuard::ModuleSelect { .. } => HashSet::new(), 1882 + 1883 + ClauseGuard::Equals { left, right, .. } 1884 + | ClauseGuard::NotEquals { left, right, .. } 1885 + | ClauseGuard::GtInt { left, right, .. } 1886 + | ClauseGuard::GtEqInt { left, right, .. } 1887 + | ClauseGuard::LtInt { left, right, .. } 1888 + | ClauseGuard::LtEqInt { left, right, .. } 1889 + | ClauseGuard::GtFloat { left, right, .. } 1890 + | ClauseGuard::GtEqFloat { left, right, .. } 1891 + | ClauseGuard::LtFloat { left, right, .. } 1892 + | ClauseGuard::LtEqFloat { left, right, .. } 1893 + | ClauseGuard::AddInt { left, right, .. } 1894 + | ClauseGuard::AddFloat { left, right, .. } 1895 + | ClauseGuard::SubInt { left, right, .. } 1896 + | ClauseGuard::SubFloat { left, right, .. } 1897 + | ClauseGuard::MultInt { left, right, .. } 1898 + | ClauseGuard::MultFloat { left, right, .. } 1899 + | ClauseGuard::DivInt { left, right, .. } 1900 + | ClauseGuard::DivFloat { left, right, .. } 1901 + | ClauseGuard::RemainderInt { left, right, .. } 1902 + | ClauseGuard::And { left, right, .. } 1903 + | ClauseGuard::Or { left, right, .. } => left 1904 + .referenced_variables() 1905 + .union(right.referenced_variables()), 1906 + } 1907 + } 1871 1908 } 1872 1909 1873 1910 #[derive(Debug, PartialEq, Eq, Default, Clone, Copy, serde::Serialize, serde::Deserialize)] ··· 2658 2695 | BitArrayOption::Native { .. } 2659 2696 | BitArrayOption::Size { .. } 2660 2697 | BitArrayOption::Unit { .. } => false, 2698 + } 2699 + } 2700 + } 2701 + 2702 + impl BitArrayOption<TypedConstant> { 2703 + fn referenced_variables(&self) -> HashSet<&EcoString> { 2704 + match self { 2705 + BitArrayOption::Bytes { .. } 2706 + | BitArrayOption::Int { .. } 2707 + | BitArrayOption::Float { .. } 2708 + | BitArrayOption::Bits { .. } 2709 + | BitArrayOption::Utf8 { .. } 2710 + | BitArrayOption::Utf16 { .. } 2711 + | BitArrayOption::Utf32 { .. } 2712 + | BitArrayOption::Utf8Codepoint { .. } 2713 + | BitArrayOption::Utf16Codepoint { .. } 2714 + | BitArrayOption::Utf32Codepoint { .. } 2715 + | BitArrayOption::Signed { .. } 2716 + | BitArrayOption::Unsigned { .. } 2717 + | BitArrayOption::Big { .. } 2718 + | BitArrayOption::Little { .. } 2719 + | BitArrayOption::Unit { .. } 2720 + | BitArrayOption::Native { .. } => hashset![], 2721 + 2722 + BitArrayOption::Size { value, .. } => value.referenced_variables(), 2661 2723 } 2662 2724 } 2663 2725 }
+36
compiler-core/src/ast/constant.rs
··· 140 140 .map(|constructor| constructor.definition_location()), 141 141 } 142 142 } 143 + 144 + pub(crate) fn referenced_variables(&self) -> HashSet<&EcoString> { 145 + match self { 146 + Constant::Var { name, .. } => hashset![name], 147 + 148 + Constant::Invalid { .. } 149 + | Constant::Int { .. } 150 + | Constant::Float { .. } 151 + | Constant::String { .. } => hashset![], 152 + 153 + Constant::List { elements, .. } | Constant::Tuple { elements, .. } => elements 154 + .iter() 155 + .map(|element| element.referenced_variables()) 156 + .fold(hashset![], HashSet::union), 157 + 158 + Constant::Record { args, .. } => args 159 + .iter() 160 + .map(|arg| arg.value.referenced_variables()) 161 + .fold(hashset![], HashSet::union), 162 + 163 + Constant::BitArray { segments, .. } => segments 164 + .iter() 165 + .map(|segment| { 166 + segment 167 + .options 168 + .iter() 169 + .map(|option| option.referenced_variables()) 170 + .fold(segment.value.referenced_variables(), HashSet::union) 171 + }) 172 + .fold(hashset![], HashSet::union), 173 + 174 + Constant::StringConcatenation { left, right, .. } => left 175 + .referenced_variables() 176 + .union(right.referenced_variables()), 177 + } 178 + } 143 179 } 144 180 145 181 impl HasType for TypedConstant {
+13 -2
compiler-core/src/javascript.rs
··· 1 + mod decision; 1 2 mod expression; 2 3 mod import; 3 4 mod pattern; ··· 515 516 "export const " 516 517 }; 517 518 518 - let document = 519 - expression::constant_expression(Context::Constant, &mut self.tracker, value)?; 519 + let mut generator = expression::Generator::new( 520 + self.module.name.clone(), 521 + &self.module.type_info.src_path, 522 + self.project_root, 523 + self.line_numbers, 524 + "".into(), 525 + vec![], 526 + &mut self.tracker, 527 + self.module_scope.clone(), 528 + ); 529 + 530 + let document = generator.constant_expression(Context::Constant, value)?; 520 531 521 532 Ok(docvec![ 522 533 head,
+387
compiler-core/src/javascript/decision.rs
··· 1 + use super::{ 2 + Output, 3 + expression::{Generator, Ordering}, 4 + }; 5 + use crate::{ 6 + ast::{TypedClauseGuard, TypedExpr}, 7 + docvec, 8 + exhaustiveness::{ 9 + Body, BoundValue, CompiledCase, Decision, FallbackCheck, RuntimeCheck, Variable, 10 + }, 11 + format::break_block, 12 + javascript::expression::{string, string_from_eco}, 13 + pretty::{Document, Documentable, join, line, nil}, 14 + strings::convert_string_escape_chars, 15 + }; 16 + use ecow::{EcoString, eco_format}; 17 + use std::{collections::HashMap, sync::OnceLock}; 18 + 19 + pub static ASSIGNMENT_VAR: &str = "$"; 20 + 21 + pub fn print<'a, 'generator, 'module>( 22 + compiled_case: &'a CompiledCase, 23 + clauses: Vec<(&'a TypedExpr, Option<&'a TypedClauseGuard>)>, 24 + subjects: &'a [TypedExpr], 25 + expression_generator: &'generator mut Generator<'module, 'a>, 26 + ) -> Output<'a> { 27 + // The case subjects might be repeated in the generated code, so we want to 28 + // assign those to variables (if they're not already ones) and use those; 29 + // otherwise we'd end up calling the same functions multiple times, which 30 + // would change the program's meaning! 31 + let subjects_assignments = assign_subjects(expression_generator, subjects); 32 + 33 + let mut printer = DecisionPrinter { 34 + clauses, 35 + expression_generator, 36 + variable_values: HashMap::new(), 37 + }; 38 + 39 + // Might have to add those to the scope!!!! 40 + for (var, (subject_value, _assignment)) in compiled_case 41 + .subject_variables 42 + .iter() 43 + .zip(subjects_assignments.iter()) 44 + { 45 + printer.set_pattern_variable_value(var, subject_value.clone()); 46 + } 47 + 48 + let decision = printer.decision(&compiled_case.tree)?; 49 + 50 + // Then if there's any assignment we write those before the generated 51 + // decision tree. 52 + let mut subject_assignments_docs = vec![]; 53 + for ((_, assignment), subject) in subjects_assignments.into_iter().zip(subjects) { 54 + let Some(var) = assignment else { continue }; 55 + let value = expression_generator 56 + .not_in_tail_position(Some(Ordering::Strict), |this| this.wrap_expression(subject))?; 57 + subject_assignments_docs.push(let_(var, value).append(line())) 58 + } 59 + 60 + Ok(docvec![subject_assignments_docs, decision].force_break()) 61 + } 62 + 63 + pub struct DecisionPrinter<'module, 'generator, 'a> { 64 + clauses: Vec<(&'a TypedExpr, Option<&'a TypedClauseGuard>)>, 65 + expression_generator: &'generator mut Generator<'module, 'a>, 66 + 67 + /// All the pattern variables will be assigned a specific value: being bound 68 + /// to a constructor field, tuple element and so on. Pattern variables never 69 + /// end up in the generated code but we replace them with their actual value. 70 + /// We store those values as we find them in this map. 71 + variable_values: HashMap<usize, EcoString>, 72 + } 73 + 74 + impl<'module, 'generator, 'a> DecisionPrinter<'module, 'generator, 'a> { 75 + fn set_pattern_variable_value(&mut self, variable: &Variable, value: EcoString) { 76 + let _ = self.variable_values.insert(variable.id, value); 77 + } 78 + 79 + fn get_variable_value(&self, variable: &Variable) -> EcoString { 80 + self.variable_values 81 + .get(&variable.id) 82 + .expect("pattern variable used before assignment") 83 + .clone() 84 + } 85 + 86 + fn decision(&mut self, decision: &'a Decision) -> Output<'a> { 87 + match decision { 88 + Decision::Fail => unreachable!("Invalid decision tree reached code generation"), 89 + Decision::Run { body } => { 90 + let bindings = self.bindings(&body.bindings); 91 + let body = self.body_expression(body.clause_index)?; 92 + Ok(join_with_line(bindings, body)) 93 + } 94 + Decision::Switch { 95 + var, 96 + choices, 97 + fallback, 98 + fallback_check, 99 + } => self.switch(var, choices, fallback, fallback_check), 100 + Decision::Guard { 101 + guard, 102 + if_true, 103 + if_false, 104 + } => self.decision_guard(*guard, if_true, if_false), 105 + } 106 + } 107 + 108 + fn bindings(&mut self, bindings: &'a Vec<(EcoString, BoundValue)>) -> Document<'a> { 109 + let bindings = (bindings.iter()).map(|(variable, value)| self.binding(variable, value)); 110 + join(bindings, line()) 111 + } 112 + 113 + fn bindings_ref(&mut self, bindings: &Vec<&'a (EcoString, BoundValue)>) -> Document<'a> { 114 + let bindings = (bindings.iter()).map(|(variable, value)| self.binding(variable, value)); 115 + join(bindings, line()) 116 + } 117 + 118 + fn body_expression(&mut self, clause_index: usize) -> Output<'a> { 119 + let (body, _) = &self 120 + .clauses 121 + .get(clause_index) 122 + .expect("decision tree invalid clause index"); 123 + 124 + self.expression_generator.expression_flattening_blocks(body) 125 + } 126 + 127 + fn binding(&mut self, variable_name: &'a EcoString, value: &'a BoundValue) -> Document<'a> { 128 + let variable_name = self.expression_generator.next_local_var(variable_name); 129 + let assigned_value = match value { 130 + BoundValue::Variable(variable) => self.get_variable_value(variable).to_doc(), 131 + BoundValue::LiteralString(value) => string(value), 132 + }; 133 + let_(variable_name.clone(), assigned_value) 134 + } 135 + 136 + fn switch( 137 + &mut self, 138 + var: &'a Variable, 139 + choices: &'a [(RuntimeCheck, Box<Decision>)], 140 + fallback: &'a Decision, 141 + fallback_check: &'a FallbackCheck, 142 + ) -> Output<'a> { 143 + match choices { 144 + // If there's just a single choice we can just generate the code for 145 + // it: no need to do any checking, we know it must match! 146 + [] => { 147 + if let FallbackCheck::RuntimeCheck { check } = fallback_check { 148 + self.record_check_assignments(var, check); 149 + } 150 + self.decision(fallback) 151 + } 152 + _ => { 153 + let mut first = true; 154 + let mut if_ = nil(); 155 + for (check, decision) in choices { 156 + self.record_check_assignments(var, check); 157 + let body = self.inside_new_scope(|this| this.decision(decision))?; 158 + let check_doc = self.runtime_check(var, check); 159 + let branch = if first { 160 + first = false; 161 + docvec!["if (", check_doc, ") "] 162 + } else { 163 + docvec![" else if (", check_doc, ") "] 164 + }; 165 + 166 + if_ = if_.append(docvec![branch, break_block(body)]); 167 + } 168 + 169 + // In case there's some new variables we can extract after the 170 + // successful check we store those. But we don't need to perform 171 + // the check itself: the type system makes sure that, if we ever 172 + // get here, the check is going to match no matter what! 173 + if let FallbackCheck::RuntimeCheck { check } = fallback_check { 174 + self.record_check_assignments(var, check); 175 + } 176 + 177 + let body = self.inside_new_scope(|this| this.decision(fallback))?; 178 + if body.is_empty() { 179 + Ok(if_) 180 + } else { 181 + let else_ = docvec![" else ", break_block(body)]; 182 + Ok(docvec![if_, else_]) 183 + } 184 + } 185 + } 186 + } 187 + 188 + fn inside_new_scope<A, F>(&mut self, run: F) -> A 189 + where 190 + F: Fn(&mut Self) -> A, 191 + { 192 + let old_scope = self.expression_generator.current_scope_vars.clone(); 193 + let output = run(self); 194 + self.expression_generator.current_scope_vars = old_scope; 195 + output 196 + } 197 + 198 + fn decision_guard( 199 + &mut self, 200 + guard: usize, 201 + if_true: &'a Body, 202 + if_false: &'a Decision, 203 + ) -> Output<'a> { 204 + let guard = self 205 + .clauses 206 + .get(guard) 207 + .expect("invalid clause index") 208 + .1 209 + .expect("missing guard should"); 210 + 211 + // Before generating the if-else condition we want to generate all the 212 + // assignments that will be needed by the guard condition so we can rest 213 + // assured they are in scope for the if condition to use those. 214 + let guard_variables = guard.referenced_variables(); 215 + let (check_bindings, if_true_bindings): (Vec<_>, Vec<_>) = if_true 216 + .bindings 217 + .iter() 218 + .partition(|(variable, _)| guard_variables.contains(variable)); 219 + 220 + let check_bindings = self.bindings_ref(&check_bindings); 221 + let check = self.expression_generator.guard(guard)?; 222 + let if_true = self.inside_new_scope(|this| { 223 + // All the other bindings are not needed by the guard check will 224 + // end up directly in the body of the if clause to avoid doing any 225 + // extra work before making sure the condition is true and they are 226 + // actually needed. 227 + let if_true_bindings = this.bindings_ref(&if_true_bindings); 228 + let if_true_body = this.body_expression(if_true.clause_index)?; 229 + Ok(join_with_line(if_true_bindings, if_true_body)) 230 + })?; 231 + let if_false = self.inside_new_scope(|this| this.decision(if_false))?; 232 + 233 + // We can now piece everything together into a single document! 234 + let if_ = docvec!["if (", check, ") ", break_block(if_true)]; 235 + let if_ = join_with_line(check_bindings, if_); 236 + if if_false.is_empty() { 237 + Ok(docvec![if_]) 238 + } else { 239 + let else_ = docvec![" else ", break_block(if_false)]; 240 + Ok(docvec![if_, else_]) 241 + } 242 + } 243 + 244 + fn runtime_check(&mut self, var: &Variable, runtime_check: &RuntimeCheck) -> Document<'a> { 245 + let var_value = self.get_variable_value(var); 246 + match runtime_check { 247 + RuntimeCheck::String { value } => { 248 + docvec![var_value, " === ", string_from_eco(value.clone())] 249 + } 250 + 251 + RuntimeCheck::Int { value } | RuntimeCheck::Float { value } => { 252 + docvec![var_value, " === ", value] 253 + } 254 + 255 + RuntimeCheck::StringPrefix { prefix, .. } => docvec![ 256 + var_value, 257 + ".startsWith(", 258 + string_from_eco(prefix.clone()), 259 + ")" 260 + ], 261 + 262 + RuntimeCheck::BitArray { value } => docvec!["TODO"], 263 + 264 + // When checking on a tuple there's always going to be a single choice 265 + // and the code generation will always skip generating the check for it 266 + // as the type system ensures it must match. 267 + RuntimeCheck::Tuple { .. } => unreachable!("tried generating runtime check for tuple"), 268 + 269 + RuntimeCheck::Variant { match_, .. } if var.type_.is_bool() => { 270 + if match_.name() == "True" { 271 + var_value.to_doc() 272 + } else { 273 + docvec!["!", var_value] 274 + } 275 + } 276 + 277 + RuntimeCheck::Variant { match_, .. } if var.type_.is_result() => { 278 + docvec![var_value, ".is", match_.name(), "()"] 279 + } 280 + 281 + RuntimeCheck::Variant { match_, .. } => { 282 + let qualification = match_ 283 + .module() 284 + .map(|module| eco_format!("${module}.")) 285 + .unwrap_or_default(); 286 + 287 + docvec![var_value, " instanceof ", qualification, match_.name()] 288 + } 289 + RuntimeCheck::NonEmptyList { .. } => docvec!["!", var_value, ".hasLength(0)"], 290 + RuntimeCheck::EmptyList => docvec![var_value, ".hasLength(0)"], 291 + } 292 + } 293 + 294 + fn record_check_assignments(&mut self, var: &Variable, check: &RuntimeCheck) { 295 + let value = self.get_variable_value(var); 296 + match check { 297 + RuntimeCheck::Int { .. } 298 + | RuntimeCheck::Float { .. } 299 + | RuntimeCheck::String { .. } 300 + | RuntimeCheck::BitArray { .. } 301 + | RuntimeCheck::EmptyList => (), 302 + 303 + RuntimeCheck::StringPrefix { rest, prefix } => { 304 + let prefix_size = utf16_no_escape_len(prefix); 305 + self.set_pattern_variable_value(rest, eco_format!("{value}.slice({prefix_size})")); 306 + } 307 + 308 + RuntimeCheck::Tuple { elements, .. } => { 309 + for (i, element) in elements.iter().enumerate() { 310 + self.set_pattern_variable_value(element, eco_format!("{value}[{i}]")); 311 + } 312 + } 313 + 314 + RuntimeCheck::Variant { fields, labels, .. } => { 315 + for (i, field) in fields.iter().enumerate() { 316 + let access = match labels.get(&i) { 317 + Some(label) => eco_format!("{value}.{label}"), 318 + None => eco_format!("{value}[{i}]"), 319 + }; 320 + self.set_pattern_variable_value(field, access); 321 + } 322 + } 323 + 324 + RuntimeCheck::NonEmptyList { first, rest } => { 325 + self.set_pattern_variable_value(first, eco_format!("{value}.head")); 326 + self.set_pattern_variable_value(rest, eco_format!("{value}.tail")); 327 + } 328 + } 329 + } 330 + } 331 + 332 + fn let_<'a>(variable_name: EcoString, value: Document<'a>) -> Document<'a> { 333 + docvec!["let ", variable_name, " = ", value, ";"] 334 + } 335 + 336 + /// Calculates the length of str as utf16 without escape characters. 337 + /// 338 + fn utf16_no_escape_len(str: &EcoString) -> usize { 339 + convert_string_escape_chars(str).encode_utf16().count() 340 + } 341 + 342 + fn assign_subjects<'a>( 343 + expression_generator: &mut Generator<'_, 'a>, 344 + subjects: &'a [TypedExpr], 345 + ) -> Vec<(EcoString, Option<EcoString>)> { 346 + let mut out = Vec::with_capacity(subjects.len()); 347 + for subject in subjects { 348 + out.push(assign_subject(expression_generator, subject)) 349 + } 350 + out 351 + } 352 + 353 + fn assign_subject<'a>( 354 + expression_generator: &mut Generator<'_, 'a>, 355 + subject: &'a TypedExpr, 356 + ) -> (EcoString, Option<EcoString>) { 357 + static ASSIGNMENT_VAR_ECO_STR: OnceLock<EcoString> = OnceLock::new(); 358 + 359 + match subject { 360 + // If the value is a variable we don't need to assign it to a new 361 + // variable, we can the value expression safely without worrying about 362 + // performing computation or side effects multiple times. 363 + TypedExpr::Var { 364 + name, constructor, .. 365 + } if constructor.is_local_variable() => (expression_generator.local_var(name), None), 366 + // If it's not a variable we need to assign it to a variable 367 + // to avoid rendering the subject expression multiple times 368 + _ => { 369 + let subject = expression_generator 370 + .next_local_var(ASSIGNMENT_VAR_ECO_STR.get_or_init(|| ASSIGNMENT_VAR.into())); 371 + (subject.clone(), Some(subject)) 372 + } 373 + } 374 + } 375 + 376 + /// Appends the second document to the first one separating the two with a newline. 377 + /// However, if the second document is empty the empty line is not added. 378 + /// 379 + fn join_with_line<'a>(one: Document<'a>, other: Document<'a>) -> Document<'a> { 380 + if one.is_empty() { 381 + other 382 + } else if other.is_empty() { 383 + one 384 + } else { 385 + docvec![one, line(), other] 386 + } 387 + }
+518 -365
compiler-core/src/javascript/expression.rs
··· 87 87 line_numbers: &'module LineNumbers, 88 88 function_name: Option<EcoString>, 89 89 function_arguments: Vec<Option<&'module EcoString>>, 90 - current_scope_vars: im::HashMap<EcoString, usize>, 90 + pub current_scope_vars: im::HashMap<EcoString, usize>, 91 91 pub function_position: Position, 92 92 pub scope_position: Position, 93 93 // We register whether these features are used within an expression so that ··· 255 255 TypedExpr::TupleIndex { tuple, index, .. } => self.tuple_index(tuple, *index), 256 256 257 257 TypedExpr::Case { 258 - subjects, clauses, .. 259 - } => self.case(subjects, clauses), 258 + subjects, 259 + clauses, 260 + compiled_case, 261 + .. 262 + } => { 263 + let clauses = clauses 264 + .iter() 265 + .map(|clause| (&clause.then, clause.guard.as_ref())) 266 + .collect_vec(); 267 + decision::print(compiled_case, clauses, subjects, self) 268 + } 260 269 261 270 TypedExpr::Call { fun, args, .. } => self.call(fun, args), 262 271 TypedExpr::Fn { args, body, .. } => self.fn_(args, body), ··· 612 621 fn variable(&mut self, name: &'a EcoString, constructor: &'a ValueConstructor) -> Output<'a> { 613 622 match &constructor.variant { 614 623 ValueConstructorVariant::LocalConstant { literal } => { 615 - constant_expression(Context::Function, self.tracker, literal) 624 + self.constant_expression(Context::Function, literal) 616 625 } 617 626 ValueConstructorVariant::Record { arity, .. } => { 618 627 let type_ = constructor.type_.clone(); ··· 685 694 Ok(documents.to_doc().force_break()) 686 695 } 687 696 688 - fn expression_flattening_blocks(&mut self, expression: &'a TypedExpr) -> Output<'a> { 697 + pub(crate) fn expression_flattening_blocks(&mut self, expression: &'a TypedExpr) -> Output<'a> { 689 698 match expression { 690 699 TypedExpr::Block { statements, .. } => self.statements(statements), 691 700 _ => { ··· 815 824 } 816 825 817 826 // Otherwise we need to compile the patterns 827 + // 818 828 let (subject, subject_assignment) = pattern::assign_subject(self, value); 829 + let subject = subject.to_doc(); 819 830 // Value needs to be rendered before traversing pattern to have correctly incremented variables. 820 831 let value = 821 832 self.not_in_tail_position(Some(Ordering::Loose), |this| this.wrap_expression(value))?; ··· 829 840 Position::Tail => docvec![ 830 841 line(), 831 842 "return ", 832 - subject_assignment.clone().unwrap_or_else(|| value.clone()), 843 + subject_assignment 844 + .clone() 845 + .map_or_else(|| value.clone(), |s| s.to_doc()), 833 846 ";" 834 847 ], 835 848 Position::Assign(block_variable) => docvec![ ··· 1788 1801 join(assignments, line()) 1789 1802 } 1790 1803 1791 - fn pattern_take_assignments_doc( 1792 - &self, 1793 - compiled_pattern: &mut CompiledPattern<'a>, 1794 - ) -> Document<'a> { 1795 - let assignments = std::mem::take(&mut compiled_pattern.assignments); 1796 - Self::pattern_assignments_doc(assignments) 1797 - } 1798 - 1799 - fn pattern_take_checks_doc( 1800 - &self, 1801 - compiled_pattern: &mut CompiledPattern<'a>, 1802 - match_desired: bool, 1803 - ) -> Document<'a> { 1804 - let checks = std::mem::take(&mut compiled_pattern.checks); 1805 - self.pattern_checks_doc(checks, match_desired) 1806 - } 1807 - 1808 1804 fn pattern_checks_doc( 1809 1805 &self, 1810 1806 checks: Vec<pattern::Check<'a>>, ··· 1852 1848 ])?; 1853 1849 Ok(self.wrap_return(docvec!["echo", echo_argument])) 1854 1850 } 1851 + 1852 + pub(crate) fn constant_expression( 1853 + &mut self, 1854 + context: Context, 1855 + expression: &'a TypedConstant, 1856 + ) -> Output<'a> { 1857 + match expression { 1858 + Constant::Int { value, .. } => Ok(int(value)), 1859 + Constant::Float { value, .. } => Ok(float(value)), 1860 + Constant::String { value, .. } => Ok(string(value)), 1861 + Constant::Tuple { elements, .. } => array( 1862 + elements 1863 + .iter() 1864 + .map(|element| self.constant_expression(context, element)), 1865 + ), 1866 + 1867 + Constant::List { elements, .. } => { 1868 + self.tracker.list_used = true; 1869 + let list = list( 1870 + elements 1871 + .iter() 1872 + .map(|element| self.constant_expression(context, element)), 1873 + )?; 1874 + 1875 + match context { 1876 + Context::Constant => Ok(docvec!["/* @__PURE__ */ ", list]), 1877 + Context::Function => Ok(list), 1878 + } 1879 + } 1880 + 1881 + Constant::Record { type_, name, .. } if type_.is_bool() && name == "True" => { 1882 + Ok("true".to_doc()) 1883 + } 1884 + Constant::Record { type_, name, .. } if type_.is_bool() && name == "False" => { 1885 + Ok("false".to_doc()) 1886 + } 1887 + Constant::Record { type_, .. } if type_.is_nil() => Ok("undefined".to_doc()), 1888 + 1889 + Constant::Record { 1890 + args, 1891 + module, 1892 + name, 1893 + tag, 1894 + type_, 1895 + .. 1896 + } => { 1897 + if type_.is_result() { 1898 + if tag == "Ok" { 1899 + self.tracker.ok_used = true; 1900 + } else { 1901 + self.tracker.error_used = true; 1902 + } 1903 + } 1904 + 1905 + // If there's no arguments and the type is a function that takes 1906 + // arguments then this is the constructor being referenced, not the 1907 + // function being called. 1908 + if let Some(arity) = type_.fn_arity() { 1909 + if args.is_empty() && arity != 0 { 1910 + let arity = arity as u16; 1911 + return Ok(record_constructor( 1912 + type_.clone(), 1913 + None, 1914 + name, 1915 + arity, 1916 + self.tracker, 1917 + )); 1918 + } 1919 + } 1920 + 1921 + let field_values: Vec<_> = args 1922 + .iter() 1923 + .map(|arg| self.constant_expression(context, &arg.value)) 1924 + .try_collect()?; 1925 + 1926 + let constructor = construct_record( 1927 + module.as_ref().map(|(module, _)| module.as_str()), 1928 + name, 1929 + field_values, 1930 + ); 1931 + match context { 1932 + Context::Constant => Ok(docvec!["/* @__PURE__ */ ", constructor]), 1933 + Context::Function => Ok(constructor), 1934 + } 1935 + } 1936 + 1937 + Constant::BitArray { segments, .. } => { 1938 + let bit_array = self.constant_bit_array(segments)?; 1939 + match context { 1940 + Context::Constant => Ok(docvec!["/* @__PURE__ */ ", bit_array]), 1941 + Context::Function => Ok(bit_array), 1942 + } 1943 + } 1944 + 1945 + Constant::Var { name, module, .. } => Ok({ 1946 + match module { 1947 + None => maybe_escape_identifier(name).to_doc(), 1948 + Some((module, _)) => { 1949 + // JS keywords can be accessed here, but we must escape anyway 1950 + // as we escape when exporting such names in the first place, 1951 + // and the imported name has to match the exported name. 1952 + docvec!["$", module, ".", maybe_escape_identifier(name)] 1953 + } 1954 + } 1955 + }), 1956 + 1957 + Constant::StringConcatenation { left, right, .. } => { 1958 + let left = self.constant_expression(context, left)?; 1959 + let right = self.constant_expression(context, right)?; 1960 + Ok(docvec![left, " + ", right]) 1961 + } 1962 + 1963 + Constant::Invalid { .. } => { 1964 + panic!("invalid constants should not reach code generation") 1965 + } 1966 + } 1967 + } 1968 + 1969 + fn constant_bit_array(&mut self, segments: &'a [TypedConstantBitArraySegment]) -> Output<'a> { 1970 + use BitArrayOption as Opt; 1971 + 1972 + self.tracker.bit_array_literal_used = true; 1973 + let segments_array = array(segments.iter().map(|segment| { 1974 + let value = self.constant_expression(Context::Constant, &segment.value)?; 1975 + 1976 + if segment.has_native_option() { 1977 + return Err(Error::Unsupported { 1978 + feature: "This bit array segment option".into(), 1979 + location: segment.location, 1980 + }); 1981 + } 1982 + 1983 + match segment.options.as_slice() { 1984 + // Int segment 1985 + _ if segment.type_.is_int() => { 1986 + let details = self.constant_sized_bit_array_segment_details(segment)?; 1987 + match (details.size_value, segment.value.as_ref()) { 1988 + (Some(size_value), Constant::Int { int_value, .. }) 1989 + if size_value <= SAFE_INT_SEGMENT_MAX_SIZE.into() 1990 + && (&size_value % BigInt::from(8) == BigInt::ZERO) => 1991 + { 1992 + let bytes = bit_array_segment_int_value_to_bytes( 1993 + int_value.clone(), 1994 + size_value, 1995 + segment.endianness(), 1996 + )?; 1997 + 1998 + Ok(u8_slice(&bytes)) 1999 + } 2000 + 2001 + (Some(size_value), _) if size_value == 8.into() => Ok(value), 2002 + 2003 + (Some(size_value), _) if size_value <= 0.into() => Ok(nil()), 2004 + 2005 + _ => { 2006 + self.tracker.sized_integer_segment_used = true; 2007 + let size = details.size; 2008 + let is_big = bool(segment.endianness().is_big()); 2009 + Ok(docvec!["sizedInt(", value, ", ", size, ", ", is_big, ")"]) 2010 + } 2011 + } 2012 + } 2013 + 2014 + // Float segments 2015 + _ if segment.type_.is_float() => { 2016 + self.tracker.float_bit_array_segment_used = true; 2017 + let details = self.constant_sized_bit_array_segment_details(segment)?; 2018 + let size = details.size; 2019 + let is_big = bool(segment.endianness().is_big()); 2020 + Ok(docvec!["sizedFloat(", value, ", ", size, ", ", is_big, ")"]) 2021 + } 2022 + 2023 + // UTF8 strings 2024 + [Opt::Utf8 { .. }] => { 2025 + self.tracker.string_bit_array_segment_used = true; 2026 + Ok(docvec!["stringBits(", value, ")"]) 2027 + } 2028 + 2029 + // UTF8 codepoints 2030 + [Opt::Utf8Codepoint { .. }] => { 2031 + self.tracker.codepoint_bit_array_segment_used = true; 2032 + Ok(docvec!["codepointBits(", value, ")"]) 2033 + } 2034 + 2035 + // Bit arrays 2036 + [Opt::Bits { .. }] => Ok(value), 2037 + 2038 + // Bit arrays with explicit size. The explicit size slices the bit array to the 2039 + // specified size. A runtime exception is thrown if the size exceeds the number 2040 + // of bits in the bit array. 2041 + [Opt::Bits { .. }, Opt::Size { value: size, .. }] 2042 + | [Opt::Size { value: size, .. }, Opt::Bits { .. }] => match &**size { 2043 + Constant::Int { value: size, .. } => { 2044 + self.tracker.bit_array_slice_used = true; 2045 + Ok(docvec!["bitArraySlice(", value, ", 0, ", size, ")"]) 2046 + } 2047 + 2048 + _ => Err(Error::Unsupported { 2049 + feature: "This bit array segment option".into(), 2050 + location: segment.location, 2051 + }), 2052 + }, 2053 + 2054 + // Anything else 2055 + _ => Err(Error::Unsupported { 2056 + feature: "This bit array segment option".into(), 2057 + location: segment.location, 2058 + }), 2059 + } 2060 + }))?; 2061 + 2062 + Ok(docvec!["toBitArray(", segments_array, ")"]) 2063 + } 2064 + 2065 + fn constant_sized_bit_array_segment_details( 2066 + &mut self, 2067 + segment: &'a TypedConstantBitArraySegment, 2068 + ) -> Result<SizedBitArraySegmentDetails<'a>, Error> { 2069 + let size = segment.size(); 2070 + let unit = segment.unit(); 2071 + let (size_value, size) = match size { 2072 + Some(Constant::Int { int_value, .. }) => { 2073 + let size_value = int_value * unit; 2074 + let size = eco_format!("{}", size_value).to_doc(); 2075 + (Some(size_value), size) 2076 + } 2077 + 2078 + Some(size) => { 2079 + let mut size = self.constant_expression(Context::Constant, size)?; 2080 + if unit != 1 { 2081 + size = size.group().append(" * ".to_doc().append(unit.to_doc())); 2082 + } 2083 + 2084 + (None, size) 2085 + } 2086 + 2087 + None => { 2088 + let size_value: usize = if segment.type_.is_int() { 8 } else { 64 }; 2089 + (Some(BigInt::from(size_value)), docvec![size_value]) 2090 + } 2091 + }; 2092 + 2093 + Ok(SizedBitArraySegmentDetails { size, size_value }) 2094 + } 2095 + 2096 + pub(crate) fn guard(&mut self, guard: &'a TypedClauseGuard) -> Output<'a> { 2097 + match guard { 2098 + ClauseGuard::Equals { left, right, .. } if is_js_scalar(left.type_()) => { 2099 + let left = self.wrapped_guard(left)?; 2100 + let right = self.wrapped_guard(right)?; 2101 + Ok(docvec![left, " === ", right]) 2102 + } 2103 + 2104 + ClauseGuard::NotEquals { left, right, .. } if is_js_scalar(left.type_()) => { 2105 + let left = self.wrapped_guard(left)?; 2106 + let right = self.wrapped_guard(right)?; 2107 + Ok(docvec![left, " !== ", right]) 2108 + } 2109 + 2110 + ClauseGuard::Equals { left, right, .. } => { 2111 + let left = self.guard(left)?; 2112 + let right = self.guard(right)?; 2113 + Ok(self.prelude_equal_call(true, left, right)) 2114 + } 2115 + 2116 + ClauseGuard::NotEquals { left, right, .. } => { 2117 + let left = self.guard(left)?; 2118 + let right = self.guard(right)?; 2119 + Ok(self.prelude_equal_call(false, left, right)) 2120 + } 2121 + 2122 + ClauseGuard::GtFloat { left, right, .. } | ClauseGuard::GtInt { left, right, .. } => { 2123 + let left = self.wrapped_guard(left)?; 2124 + let right = self.wrapped_guard(right)?; 2125 + Ok(docvec![left, " > ", right]) 2126 + } 2127 + 2128 + ClauseGuard::GtEqFloat { left, right, .. } 2129 + | ClauseGuard::GtEqInt { left, right, .. } => { 2130 + let left = self.wrapped_guard(left)?; 2131 + let right = self.wrapped_guard(right)?; 2132 + Ok(docvec![left, " >= ", right]) 2133 + } 2134 + 2135 + ClauseGuard::LtFloat { left, right, .. } | ClauseGuard::LtInt { left, right, .. } => { 2136 + let left = self.wrapped_guard(left)?; 2137 + let right = self.wrapped_guard(right)?; 2138 + Ok(docvec![left, " < ", right]) 2139 + } 2140 + 2141 + ClauseGuard::LtEqFloat { left, right, .. } 2142 + | ClauseGuard::LtEqInt { left, right, .. } => { 2143 + let left = self.wrapped_guard(left)?; 2144 + let right = self.wrapped_guard(right)?; 2145 + Ok(docvec![left, " <= ", right]) 2146 + } 2147 + 2148 + ClauseGuard::AddFloat { left, right, .. } | ClauseGuard::AddInt { left, right, .. } => { 2149 + let left = self.wrapped_guard(left)?; 2150 + let right = self.wrapped_guard(right)?; 2151 + Ok(docvec![left, " + ", right]) 2152 + } 2153 + 2154 + ClauseGuard::SubFloat { left, right, .. } | ClauseGuard::SubInt { left, right, .. } => { 2155 + let left = self.wrapped_guard(left)?; 2156 + let right = self.wrapped_guard(right)?; 2157 + Ok(docvec![left, " - ", right]) 2158 + } 2159 + 2160 + ClauseGuard::MultFloat { left, right, .. } 2161 + | ClauseGuard::MultInt { left, right, .. } => { 2162 + let left = self.wrapped_guard(left)?; 2163 + let right = self.wrapped_guard(right)?; 2164 + Ok(docvec![left, " * ", right]) 2165 + } 2166 + 2167 + ClauseGuard::DivFloat { left, right, .. } => { 2168 + let left = self.wrapped_guard(left)?; 2169 + let right = self.wrapped_guard(right)?; 2170 + self.tracker.float_division_used = true; 2171 + Ok(docvec!["divideFloat", wrap_args([left, right])]) 2172 + } 2173 + 2174 + ClauseGuard::DivInt { left, right, .. } => { 2175 + let left = self.wrapped_guard(left)?; 2176 + let right = self.wrapped_guard(right)?; 2177 + self.tracker.int_division_used = true; 2178 + Ok(docvec!["divideInt", wrap_args([left, right])]) 2179 + } 2180 + 2181 + ClauseGuard::RemainderInt { left, right, .. } => { 2182 + let left = self.wrapped_guard(left)?; 2183 + let right = self.wrapped_guard(right)?; 2184 + self.tracker.int_remainder_used = true; 2185 + Ok(docvec!["remainderInt", wrap_args([left, right])]) 2186 + } 2187 + 2188 + ClauseGuard::Or { left, right, .. } => { 2189 + let left = self.wrapped_guard(left)?; 2190 + let right = self.wrapped_guard(right)?; 2191 + Ok(docvec![left, " || ", right]) 2192 + } 2193 + 2194 + ClauseGuard::And { left, right, .. } => { 2195 + let left = self.wrapped_guard(left)?; 2196 + let right = self.wrapped_guard(right)?; 2197 + Ok(docvec![left, " && ", right]) 2198 + } 2199 + 2200 + ClauseGuard::Var { name, .. } => Ok(self.local_var(name).to_doc()), 2201 + 2202 + ClauseGuard::TupleIndex { tuple, index, .. } => { 2203 + Ok(docvec![self.guard(tuple,)?, "[", index, "]"]) 2204 + } 2205 + 2206 + ClauseGuard::FieldAccess { 2207 + label, container, .. 2208 + } => Ok(docvec![ 2209 + self.guard(container,)?, 2210 + ".", 2211 + maybe_escape_property_doc(label) 2212 + ]), 2213 + 2214 + ClauseGuard::ModuleSelect { 2215 + module_alias, 2216 + label, 2217 + .. 2218 + } => Ok(docvec!["$", module_alias, ".", label]), 2219 + 2220 + ClauseGuard::Not { expression, .. } => Ok(docvec!["!", self.guard(expression,)?]), 2221 + 2222 + ClauseGuard::Constant(constant) => self.guard_constant_expression(constant), 2223 + } 2224 + } 2225 + 2226 + fn wrapped_guard(&mut self, guard: &'a TypedClauseGuard) -> Result<Document<'a>, Error> { 2227 + match guard { 2228 + ClauseGuard::Var { .. } 2229 + | ClauseGuard::TupleIndex { .. } 2230 + | ClauseGuard::Constant(_) 2231 + | ClauseGuard::Not { .. } 2232 + | ClauseGuard::FieldAccess { .. } => self.guard(guard), 2233 + 2234 + ClauseGuard::Equals { .. } 2235 + | ClauseGuard::NotEquals { .. } 2236 + | ClauseGuard::GtInt { .. } 2237 + | ClauseGuard::GtEqInt { .. } 2238 + | ClauseGuard::LtInt { .. } 2239 + | ClauseGuard::LtEqInt { .. } 2240 + | ClauseGuard::GtFloat { .. } 2241 + | ClauseGuard::GtEqFloat { .. } 2242 + | ClauseGuard::LtFloat { .. } 2243 + | ClauseGuard::LtEqFloat { .. } 2244 + | ClauseGuard::AddInt { .. } 2245 + | ClauseGuard::AddFloat { .. } 2246 + | ClauseGuard::SubInt { .. } 2247 + | ClauseGuard::SubFloat { .. } 2248 + | ClauseGuard::MultInt { .. } 2249 + | ClauseGuard::MultFloat { .. } 2250 + | ClauseGuard::DivInt { .. } 2251 + | ClauseGuard::DivFloat { .. } 2252 + | ClauseGuard::RemainderInt { .. } 2253 + | ClauseGuard::Or { .. } 2254 + | ClauseGuard::And { .. } 2255 + | ClauseGuard::ModuleSelect { .. } => Ok(docvec!["(", self.guard(guard,)?, ")"]), 2256 + } 2257 + } 2258 + 2259 + // TODO)) This could be simplified by making the var case a function to change as 2260 + // needed. 2261 + fn guard_constant_expression(&mut self, expression: &'a TypedConstant) -> Output<'a> { 2262 + match expression { 2263 + Constant::Tuple { elements, .. } => array( 2264 + elements 2265 + .iter() 2266 + .map(|element| self.guard_constant_expression(element)), 2267 + ), 2268 + 2269 + Constant::List { elements, .. } => { 2270 + self.tracker.list_used = true; 2271 + list( 2272 + elements 2273 + .iter() 2274 + .map(|element| self.guard_constant_expression(element)), 2275 + ) 2276 + } 2277 + Constant::Record { type_, name, .. } if type_.is_bool() && name == "True" => { 2278 + Ok("true".to_doc()) 2279 + } 2280 + Constant::Record { type_, name, .. } if type_.is_bool() && name == "False" => { 2281 + Ok("false".to_doc()) 2282 + } 2283 + Constant::Record { type_, .. } if type_.is_nil() => Ok("undefined".to_doc()), 2284 + 2285 + Constant::Record { 2286 + args, 2287 + module, 2288 + name, 2289 + tag, 2290 + type_, 2291 + .. 2292 + } => { 2293 + if type_.is_result() { 2294 + if tag == "Ok" { 2295 + self.tracker.ok_used = true; 2296 + } else { 2297 + self.tracker.error_used = true; 2298 + } 2299 + } 2300 + 2301 + // If there's no arguments and the type is a function that takes 2302 + // arguments then this is the constructor being referenced, not the 2303 + // function being called. 2304 + if let Some(arity) = type_.fn_arity() { 2305 + if args.is_empty() && arity != 0 { 2306 + let arity = arity as u16; 2307 + return Ok(record_constructor( 2308 + type_.clone(), 2309 + None, 2310 + name, 2311 + arity, 2312 + self.tracker, 2313 + )); 2314 + } 2315 + } 2316 + 2317 + let field_values: Vec<_> = args 2318 + .iter() 2319 + .map(|arg| self.guard_constant_expression(&arg.value)) 2320 + .try_collect()?; 2321 + Ok(construct_record( 2322 + module.as_ref().map(|(module, _)| module.as_str()), 2323 + name, 2324 + field_values, 2325 + )) 2326 + } 2327 + 2328 + Constant::BitArray { segments, .. } => self.constant_bit_array(segments), 2329 + 2330 + Constant::Var { name, .. } => Ok(self.local_var(name).to_doc()), 2331 + 2332 + expression => self.constant_expression(Context::Function, expression), 2333 + } 2334 + } 1855 2335 } 1856 2336 1857 2337 #[derive(Clone, Copy)] ··· 1928 2408 out.to_doc() 1929 2409 } 1930 2410 1931 - pub(crate) fn guard_constant_expression<'a>( 1932 - assignments: &mut Vec<Assignment<'a>>, 1933 - tracker: &mut UsageTracker, 1934 - expression: &'a TypedConstant, 1935 - ) -> Output<'a> { 1936 - match expression { 1937 - Constant::Tuple { elements, .. } => array( 1938 - elements 1939 - .iter() 1940 - .map(|element| guard_constant_expression(assignments, tracker, element)), 1941 - ), 1942 - 1943 - Constant::List { elements, .. } => { 1944 - tracker.list_used = true; 1945 - list( 1946 - elements 1947 - .iter() 1948 - .map(|element| guard_constant_expression(assignments, tracker, element)), 1949 - ) 1950 - } 1951 - Constant::Record { type_, name, .. } if type_.is_bool() && name == "True" => { 1952 - Ok("true".to_doc()) 1953 - } 1954 - Constant::Record { type_, name, .. } if type_.is_bool() && name == "False" => { 1955 - Ok("false".to_doc()) 1956 - } 1957 - Constant::Record { type_, .. } if type_.is_nil() => Ok("undefined".to_doc()), 1958 - 1959 - Constant::Record { 1960 - args, 1961 - module, 1962 - name, 1963 - tag, 1964 - type_, 1965 - .. 1966 - } => { 1967 - if type_.is_result() { 1968 - if tag == "Ok" { 1969 - tracker.ok_used = true; 1970 - } else { 1971 - tracker.error_used = true; 1972 - } 1973 - } 1974 - 1975 - // If there's no arguments and the type is a function that takes 1976 - // arguments then this is the constructor being referenced, not the 1977 - // function being called. 1978 - if let Some(arity) = type_.fn_arity() { 1979 - if args.is_empty() && arity != 0 { 1980 - let arity = arity as u16; 1981 - return Ok(record_constructor( 1982 - type_.clone(), 1983 - None, 1984 - name, 1985 - arity, 1986 - tracker, 1987 - )); 1988 - } 1989 - } 1990 - 1991 - let field_values: Vec<_> = args 1992 - .iter() 1993 - .map(|arg| guard_constant_expression(assignments, tracker, &arg.value)) 1994 - .try_collect()?; 1995 - Ok(construct_record( 1996 - module.as_ref().map(|(module, _)| module.as_str()), 1997 - name, 1998 - field_values, 1999 - )) 2000 - } 2001 - 2002 - Constant::BitArray { segments, .. } => bit_array(tracker, segments, |tracker, constant| { 2003 - guard_constant_expression(assignments, tracker, constant) 2004 - }), 2005 - 2006 - Constant::Var { name, .. } => Ok(assignments 2007 - .iter() 2008 - .find(|assignment| assignment.name == name) 2009 - .map(|assignment| assignment.subject.clone()) 2010 - .unwrap_or_else(|| maybe_escape_identifier(name).to_doc())), 2011 - 2012 - expression => constant_expression(Context::Function, tracker, expression), 2013 - } 2014 - } 2015 - 2016 - #[derive(Debug, Clone, Copy)] 2017 2411 /// The context where the constant expression is used, it might be inside a 2018 2412 /// function call, or in the definition of another constant. 2019 2413 /// 2020 2414 /// Based on the context we might want to annotate pure function calls as 2021 2415 /// "@__PURE__". 2022 2416 /// 2417 + #[derive(Debug, Clone, Copy)] 2023 2418 pub enum Context { 2024 2419 Constant, 2025 2420 Function, 2026 2421 } 2027 2422 2028 - pub(crate) fn constant_expression<'a>( 2029 - context: Context, 2030 - tracker: &mut UsageTracker, 2031 - expression: &'a TypedConstant, 2032 - ) -> Output<'a> { 2033 - match expression { 2034 - Constant::Int { value, .. } => Ok(int(value)), 2035 - Constant::Float { value, .. } => Ok(float(value)), 2036 - Constant::String { value, .. } => Ok(string(value)), 2037 - Constant::Tuple { elements, .. } => array( 2038 - elements 2039 - .iter() 2040 - .map(|element| constant_expression(context, tracker, element)), 2041 - ), 2042 - 2043 - Constant::List { elements, .. } => { 2044 - tracker.list_used = true; 2045 - let list = list( 2046 - elements 2047 - .iter() 2048 - .map(|element| constant_expression(context, tracker, element)), 2049 - )?; 2050 - 2051 - match context { 2052 - Context::Constant => Ok(docvec!["/* @__PURE__ */ ", list]), 2053 - Context::Function => Ok(list), 2054 - } 2055 - } 2056 - 2057 - Constant::Record { type_, name, .. } if type_.is_bool() && name == "True" => { 2058 - Ok("true".to_doc()) 2059 - } 2060 - Constant::Record { type_, name, .. } if type_.is_bool() && name == "False" => { 2061 - Ok("false".to_doc()) 2062 - } 2063 - Constant::Record { type_, .. } if type_.is_nil() => Ok("undefined".to_doc()), 2064 - 2065 - Constant::Record { 2066 - args, 2067 - module, 2068 - name, 2069 - tag, 2070 - type_, 2071 - .. 2072 - } => { 2073 - if type_.is_result() { 2074 - if tag == "Ok" { 2075 - tracker.ok_used = true; 2076 - } else { 2077 - tracker.error_used = true; 2078 - } 2079 - } 2080 - 2081 - // If there's no arguments and the type is a function that takes 2082 - // arguments then this is the constructor being referenced, not the 2083 - // function being called. 2084 - if let Some(arity) = type_.fn_arity() { 2085 - if args.is_empty() && arity != 0 { 2086 - let arity = arity as u16; 2087 - return Ok(record_constructor( 2088 - type_.clone(), 2089 - None, 2090 - name, 2091 - arity, 2092 - tracker, 2093 - )); 2094 - } 2095 - } 2096 - 2097 - let field_values: Vec<_> = args 2098 - .iter() 2099 - .map(|arg| constant_expression(context, tracker, &arg.value)) 2100 - .try_collect()?; 2101 - 2102 - let constructor = construct_record( 2103 - module.as_ref().map(|(module, _)| module.as_str()), 2104 - name, 2105 - field_values, 2106 - ); 2107 - match context { 2108 - Context::Constant => Ok(docvec!["/* @__PURE__ */ ", constructor]), 2109 - Context::Function => Ok(constructor), 2110 - } 2111 - } 2112 - 2113 - Constant::BitArray { segments, .. } => { 2114 - let bit_array = bit_array(tracker, segments, |tracker, expr| { 2115 - constant_expression(context, tracker, expr) 2116 - })?; 2117 - match context { 2118 - Context::Constant => Ok(docvec!["/* @__PURE__ */ ", bit_array]), 2119 - Context::Function => Ok(bit_array), 2120 - } 2121 - } 2122 - 2123 - Constant::Var { name, module, .. } => Ok({ 2124 - match module { 2125 - None => maybe_escape_identifier(name).to_doc(), 2126 - Some((module, _)) => { 2127 - // JS keywords can be accessed here, but we must escape anyway 2128 - // as we escape when exporting such names in the first place, 2129 - // and the imported name has to match the exported name. 2130 - docvec!["$", module, ".", maybe_escape_identifier(name)] 2131 - } 2132 - } 2133 - }), 2134 - 2135 - Constant::StringConcatenation { left, right, .. } => { 2136 - let left = constant_expression(context, tracker, left)?; 2137 - let right = constant_expression(context, tracker, right)?; 2138 - Ok(docvec![left, " + ", right]) 2139 - } 2140 - 2141 - Constant::Invalid { .. } => panic!("invalid constants should not reach code generation"), 2142 - } 2143 - } 2144 - 2145 - fn bit_array<'a>( 2146 - tracker: &mut UsageTracker, 2147 - segments: &'a [TypedConstantBitArraySegment], 2148 - mut constant_expr_fun: impl FnMut(&mut UsageTracker, &'a TypedConstant) -> Output<'a>, 2149 - ) -> Output<'a> { 2150 - use BitArrayOption as Opt; 2151 - 2152 - tracker.bit_array_literal_used = true; 2153 - let segments_array = array(segments.iter().map(|segment| { 2154 - let value = constant_expr_fun(tracker, &segment.value)?; 2155 - 2156 - if segment.has_native_option() { 2157 - return Err(Error::Unsupported { 2158 - feature: "This bit array segment option".into(), 2159 - location: segment.location, 2160 - }); 2161 - } 2162 - 2163 - match segment.options.as_slice() { 2164 - // Int segment 2165 - _ if segment.type_.is_int() => { 2166 - let details = 2167 - sized_bit_array_segment_details(segment, tracker, &mut constant_expr_fun)?; 2168 - match (details.size_value, segment.value.as_ref()) { 2169 - (Some(size_value), Constant::Int { int_value, .. }) 2170 - if size_value <= SAFE_INT_SEGMENT_MAX_SIZE.into() 2171 - && (&size_value % BigInt::from(8) == BigInt::ZERO) => 2172 - { 2173 - let bytes = bit_array_segment_int_value_to_bytes( 2174 - int_value.clone(), 2175 - size_value, 2176 - segment.endianness(), 2177 - )?; 2178 - 2179 - Ok(u8_slice(&bytes)) 2180 - } 2181 - 2182 - (Some(size_value), _) if size_value == 8.into() => Ok(value), 2183 - 2184 - (Some(size_value), _) if size_value <= 0.into() => Ok(nil()), 2185 - 2186 - _ => { 2187 - tracker.sized_integer_segment_used = true; 2188 - let size = details.size; 2189 - let is_big = bool(segment.endianness().is_big()); 2190 - Ok(docvec!["sizedInt(", value, ", ", size, ", ", is_big, ")"]) 2191 - } 2192 - } 2193 - } 2194 - 2195 - // Float segments 2196 - _ if segment.type_.is_float() => { 2197 - tracker.float_bit_array_segment_used = true; 2198 - let details = 2199 - sized_bit_array_segment_details(segment, tracker, &mut constant_expr_fun)?; 2200 - let size = details.size; 2201 - let is_big = bool(segment.endianness().is_big()); 2202 - Ok(docvec!["sizedFloat(", value, ", ", size, ", ", is_big, ")"]) 2203 - } 2204 - 2205 - // UTF8 strings 2206 - [Opt::Utf8 { .. }] => { 2207 - tracker.string_bit_array_segment_used = true; 2208 - Ok(docvec!["stringBits(", value, ")"]) 2209 - } 2210 - 2211 - // UTF8 codepoints 2212 - [Opt::Utf8Codepoint { .. }] => { 2213 - tracker.codepoint_bit_array_segment_used = true; 2214 - Ok(docvec!["codepointBits(", value, ")"]) 2215 - } 2216 - 2217 - // Bit arrays 2218 - [Opt::Bits { .. }] => Ok(value), 2219 - 2220 - // Bit arrays with explicit size. The explicit size slices the bit array to the 2221 - // specified size. A runtime exception is thrown if the size exceeds the number 2222 - // of bits in the bit array. 2223 - [Opt::Bits { .. }, Opt::Size { value: size, .. }] 2224 - | [Opt::Size { value: size, .. }, Opt::Bits { .. }] => match &**size { 2225 - Constant::Int { value: size, .. } => { 2226 - tracker.bit_array_slice_used = true; 2227 - Ok(docvec!["bitArraySlice(", value, ", 0, ", size, ")"]) 2228 - } 2229 - 2230 - _ => Err(Error::Unsupported { 2231 - feature: "This bit array segment option".into(), 2232 - location: segment.location, 2233 - }), 2234 - }, 2235 - 2236 - // Anything else 2237 - _ => Err(Error::Unsupported { 2238 - feature: "This bit array segment option".into(), 2239 - location: segment.location, 2240 - }), 2241 - } 2242 - }))?; 2243 - 2244 - Ok(docvec!["toBitArray(", segments_array, ")"]) 2245 - } 2246 - 2247 2423 #[derive(Debug)] 2248 2424 struct SizedBitArraySegmentDetails<'a> { 2249 2425 size: Document<'a>, ··· 2252 2428 size_value: Option<BigInt>, 2253 2429 } 2254 2430 2255 - fn sized_bit_array_segment_details<'a>( 2256 - segment: &'a TypedConstantBitArraySegment, 2257 - tracker: &mut UsageTracker, 2258 - constant_expr_fun: &mut impl FnMut(&mut UsageTracker, &'a TypedConstant) -> Output<'a>, 2259 - ) -> Result<SizedBitArraySegmentDetails<'a>, Error> { 2260 - let size = segment.size(); 2261 - let unit = segment.unit(); 2262 - let (size_value, size) = match size { 2263 - Some(Constant::Int { int_value, .. }) => { 2264 - let size_value = int_value * unit; 2265 - let size = eco_format!("{}", size_value).to_doc(); 2266 - (Some(size_value), size) 2267 - } 2268 - 2269 - Some(size) => { 2270 - let mut size = constant_expr_fun(tracker, size)?; 2271 - 2272 - if unit != 1 { 2273 - size = size.group().append(" * ".to_doc().append(unit.to_doc())); 2274 - } 2275 - 2276 - (None, size) 2277 - } 2278 - 2279 - None => { 2280 - let size_value: usize = if segment.type_.is_int() { 8 } else { 64 }; 2281 - (Some(BigInt::from(size_value)), docvec![size_value]) 2282 - } 2283 - }; 2284 - 2285 - Ok(SizedBitArraySegmentDetails { size, size_value }) 2286 - } 2287 - 2288 2431 pub fn string(value: &str) -> Document<'_> { 2289 2432 if value.contains('\n') { 2290 2433 EcoString::from(value.replace('\n', r"\n")) ··· 2295 2438 } 2296 2439 } 2297 2440 2298 - pub fn array<'a, Elements: IntoIterator<Item = Output<'a>>>(elements: Elements) -> Output<'a> { 2441 + pub fn string_from_eco<'a>(value: EcoString) -> Document<'a> { 2442 + if value.contains('\n') { 2443 + value.replace("\n", r"\n").to_doc().surround("\"", "\"") 2444 + } else { 2445 + value.to_doc().surround("\"", "\"") 2446 + } 2447 + } 2448 + 2449 + pub(crate) fn array<'a, Elements: IntoIterator<Item = Output<'a>>>( 2450 + elements: Elements, 2451 + ) -> Output<'a> { 2299 2452 let elements = Itertools::intersperse(elements.into_iter(), Ok(break_(",", ", "))) 2300 2453 .collect::<Result<Vec<_>, _>>()?; 2301 2454 if elements.is_empty() { ··· 2312 2465 } 2313 2466 } 2314 2467 2315 - fn list<'a, I: IntoIterator<Item = Output<'a>>>(elements: I) -> Output<'a> 2468 + pub(crate) fn list<'a, I: IntoIterator<Item = Output<'a>>>(elements: I) -> Output<'a> 2316 2469 where 2317 2470 I::IntoIter: DoubleEndedIterator + ExactSizeIterator, 2318 2471 { ··· 2346 2499 .group()) 2347 2500 } 2348 2501 2349 - fn construct_record<'a>( 2502 + pub(crate) fn construct_record<'a>( 2350 2503 module: Option<&'a str>, 2351 2504 name: &'a str, 2352 2505 arguments: impl IntoIterator<Item = Document<'a>>, ··· 2482 2635 .group() 2483 2636 } 2484 2637 2485 - fn record_constructor<'a>( 2638 + pub(crate) fn record_constructor<'a>( 2486 2639 type_: Arc<Type>, 2487 2640 qualifier: Option<&'a str>, 2488 2641 name: &'a str,
+5 -253
compiler-core/src/javascript/pattern.rs
··· 1 1 use num_bigint::BigInt; 2 2 use std::sync::OnceLock; 3 3 4 - use super::{expression::is_js_scalar, *}; 4 + use super::*; 5 5 use crate::{ 6 6 analyse::Inferred, 7 7 strings::convert_string_escape_chars, ··· 189 189 self.expression_generator.next_local_var(name) 190 190 } 191 191 192 - fn local_var(&mut self, name: &EcoString) -> EcoString { 193 - self.expression_generator.local_var(name) 194 - } 195 - 196 192 fn push_string(&mut self, s: &'a str) { 197 193 self.path.push(Index::String(s)); 198 194 } ··· 317 313 }) 318 314 } 319 315 320 - pub fn generate( 321 - &mut self, 322 - subjects: &[Document<'a>], 323 - patterns: &'a [TypedPattern], 324 - guard: Option<&'a TypedClauseGuard>, 325 - ) -> Result<CompiledPattern<'a>, Error> { 326 - for (subject, pattern) in subjects.iter().zip_eq(patterns) { 327 - self.traverse_pattern(subject, pattern)?; 328 - } 329 - if let Some(guard) = guard { 330 - self.push_guard_check(guard)?; 331 - } 332 - 333 - Ok(self.take_compiled()) 334 - } 335 - 336 316 pub fn take_compiled(&mut self) -> CompiledPattern<'a> { 337 317 CompiledPattern { 338 318 checks: std::mem::take(&mut self.checks), ··· 340 320 } 341 321 } 342 322 343 - fn push_guard_check(&mut self, guard: &'a TypedClauseGuard) -> Result<(), Error> { 344 - let expression = self.guard(guard)?; 345 - self.checks.push(Check::Guard { expression }); 346 - Ok(()) 347 - } 348 - 349 - fn wrapped_guard(&mut self, guard: &'a TypedClauseGuard) -> Result<Document<'a>, Error> { 350 - match guard { 351 - ClauseGuard::Var { .. } 352 - | ClauseGuard::TupleIndex { .. } 353 - | ClauseGuard::Constant(_) 354 - | ClauseGuard::Not { .. } 355 - | ClauseGuard::FieldAccess { .. } => self.guard(guard), 356 - 357 - ClauseGuard::Equals { .. } 358 - | ClauseGuard::NotEquals { .. } 359 - | ClauseGuard::GtInt { .. } 360 - | ClauseGuard::GtEqInt { .. } 361 - | ClauseGuard::LtInt { .. } 362 - | ClauseGuard::LtEqInt { .. } 363 - | ClauseGuard::GtFloat { .. } 364 - | ClauseGuard::GtEqFloat { .. } 365 - | ClauseGuard::LtFloat { .. } 366 - | ClauseGuard::LtEqFloat { .. } 367 - | ClauseGuard::AddInt { .. } 368 - | ClauseGuard::AddFloat { .. } 369 - | ClauseGuard::SubInt { .. } 370 - | ClauseGuard::SubFloat { .. } 371 - | ClauseGuard::MultInt { .. } 372 - | ClauseGuard::MultFloat { .. } 373 - | ClauseGuard::DivInt { .. } 374 - | ClauseGuard::DivFloat { .. } 375 - | ClauseGuard::RemainderInt { .. } 376 - | ClauseGuard::Or { .. } 377 - | ClauseGuard::And { .. } 378 - | ClauseGuard::ModuleSelect { .. } => Ok(docvec!["(", self.guard(guard)?, ")"]), 379 - } 380 - } 381 - 382 - fn guard(&mut self, guard: &'a TypedClauseGuard) -> Output<'a> { 383 - Ok(match guard { 384 - ClauseGuard::Equals { left, right, .. } if is_js_scalar(left.type_()) => { 385 - let left = self.wrapped_guard(left)?; 386 - let right = self.wrapped_guard(right)?; 387 - docvec![left, " === ", right] 388 - } 389 - 390 - ClauseGuard::NotEquals { left, right, .. } if is_js_scalar(left.type_()) => { 391 - let left = self.wrapped_guard(left)?; 392 - let right = self.wrapped_guard(right)?; 393 - docvec![left, " !== ", right] 394 - } 395 - 396 - ClauseGuard::Equals { left, right, .. } => { 397 - let left = self.guard(left)?; 398 - let right = self.guard(right)?; 399 - self.expression_generator 400 - .prelude_equal_call(true, left, right) 401 - } 402 - 403 - ClauseGuard::NotEquals { left, right, .. } => { 404 - let left = self.guard(left)?; 405 - let right = self.guard(right)?; 406 - self.expression_generator 407 - .prelude_equal_call(false, left, right) 408 - } 409 - 410 - ClauseGuard::GtFloat { left, right, .. } | ClauseGuard::GtInt { left, right, .. } => { 411 - let left = self.wrapped_guard(left)?; 412 - let right = self.wrapped_guard(right)?; 413 - docvec![left, " > ", right] 414 - } 415 - 416 - ClauseGuard::GtEqFloat { left, right, .. } 417 - | ClauseGuard::GtEqInt { left, right, .. } => { 418 - let left = self.wrapped_guard(left)?; 419 - let right = self.wrapped_guard(right)?; 420 - docvec![left, " >= ", right] 421 - } 422 - 423 - ClauseGuard::LtFloat { left, right, .. } | ClauseGuard::LtInt { left, right, .. } => { 424 - let left = self.wrapped_guard(left)?; 425 - let right = self.wrapped_guard(right)?; 426 - docvec![left, " < ", right] 427 - } 428 - 429 - ClauseGuard::LtEqFloat { left, right, .. } 430 - | ClauseGuard::LtEqInt { left, right, .. } => { 431 - let left = self.wrapped_guard(left)?; 432 - let right = self.wrapped_guard(right)?; 433 - docvec![left, " <= ", right] 434 - } 435 - 436 - ClauseGuard::AddFloat { left, right, .. } | ClauseGuard::AddInt { left, right, .. } => { 437 - let left = self.wrapped_guard(left)?; 438 - let right = self.wrapped_guard(right)?; 439 - docvec![left, " + ", right] 440 - } 441 - 442 - ClauseGuard::SubFloat { left, right, .. } | ClauseGuard::SubInt { left, right, .. } => { 443 - let left = self.wrapped_guard(left)?; 444 - let right = self.wrapped_guard(right)?; 445 - docvec![left, " - ", right] 446 - } 447 - 448 - ClauseGuard::MultFloat { left, right, .. } 449 - | ClauseGuard::MultInt { left, right, .. } => { 450 - let left = self.wrapped_guard(left)?; 451 - let right = self.wrapped_guard(right)?; 452 - docvec![left, " * ", right] 453 - } 454 - 455 - ClauseGuard::DivFloat { left, right, .. } => { 456 - let left = self.wrapped_guard(left)?; 457 - let right = self.wrapped_guard(right)?; 458 - self.expression_generator.tracker.float_division_used = true; 459 - docvec!["divideFloat", wrap_args([left, right])] 460 - } 461 - 462 - ClauseGuard::DivInt { left, right, .. } => { 463 - let left = self.wrapped_guard(left)?; 464 - let right = self.wrapped_guard(right)?; 465 - self.expression_generator.tracker.int_division_used = true; 466 - docvec!["divideInt", wrap_args([left, right])] 467 - } 468 - 469 - ClauseGuard::RemainderInt { left, right, .. } => { 470 - let left = self.wrapped_guard(left)?; 471 - let right = self.wrapped_guard(right)?; 472 - self.expression_generator.tracker.int_remainder_used = true; 473 - docvec!["remainderInt", wrap_args([left, right])] 474 - } 475 - 476 - ClauseGuard::Or { left, right, .. } => { 477 - let left = self.wrapped_guard(left)?; 478 - let right = self.wrapped_guard(right)?; 479 - docvec![left, " || ", right] 480 - } 481 - 482 - ClauseGuard::And { left, right, .. } => { 483 - let left = self.wrapped_guard(left)?; 484 - let right = self.wrapped_guard(right)?; 485 - docvec![left, " && ", right] 486 - } 487 - 488 - ClauseGuard::Var { name, .. } => self 489 - .path_doc_from_assignments(name) 490 - .unwrap_or_else(|| self.local_var(name).to_doc()), 491 - 492 - ClauseGuard::TupleIndex { tuple, index, .. } => { 493 - docvec![self.guard(tuple)?, "[", index, "]"] 494 - } 495 - 496 - ClauseGuard::FieldAccess { 497 - label, container, .. 498 - } => { 499 - docvec![ 500 - self.guard(container)?, 501 - ".", 502 - maybe_escape_property_doc(label) 503 - ] 504 - } 505 - 506 - ClauseGuard::ModuleSelect { 507 - module_alias, 508 - label, 509 - .. 510 - } => docvec!["$", module_alias, ".", label], 511 - 512 - ClauseGuard::Not { expression, .. } => { 513 - docvec!["!", self.guard(expression)?] 514 - } 515 - 516 - ClauseGuard::Constant(constant) => { 517 - return expression::guard_constant_expression( 518 - &mut self.assignments, 519 - self.expression_generator.tracker, 520 - constant, 521 - ); 522 - } 523 - }) 524 - } 525 - 526 - /// Get the path that would assign a variable, if there is one for the given name. 527 - /// This is in used in clause guards where may use variables defined in 528 - /// patterns can be referenced, but in the compiled JavaScript they have not 529 - /// yet been defined. 530 - fn path_doc_from_assignments(&self, name: &str) -> Option<Document<'a>> { 531 - self.assignments 532 - .iter() 533 - .find(|assignment| assignment.name == name) 534 - .map(|assignment| assignment.subject.clone()) 535 - } 536 - 537 323 pub fn traverse_pattern( 538 324 &mut self, 539 325 subject: &Document<'a>, ··· 655 441 let var = self.next_local_var(left).to_doc(); 656 442 self.assignments.push(Assignment { 657 443 subject: expression::string(left_side_string), 658 - name: left, 659 444 var, 660 445 }); 661 446 } ··· 1003 788 fn push_assignment(&mut self, subject: Document<'a>, name: &'a EcoString) { 1004 789 let var = self.next_local_var(name).to_doc(); 1005 790 let subject = self.apply_path_to_subject(subject); 1006 - self.assignments.push(Assignment { subject, var, name }); 791 + self.assignments.push(Assignment { subject, var }); 1007 792 } 1008 793 1009 794 fn push_string_prefix_check(&mut self, subject: Document<'a>, prefix: &'a str) { ··· 1074 859 pub assignments: Vec<Assignment<'a>>, 1075 860 } 1076 861 1077 - impl CompiledPattern<'_> { 1078 - pub fn has_assignments(&self) -> bool { 1079 - !self.assignments.is_empty() 1080 - } 1081 - } 1082 - 1083 862 #[derive(Debug)] 1084 863 pub struct Assignment<'a> { 1085 - pub name: &'a str, 1086 864 var: Document<'a>, 1087 865 pub subject: Document<'a>, 1088 866 } ··· 1125 903 subject: Document<'a>, 1126 904 expected_to_be_truthy: bool, 1127 905 }, 1128 - Guard { 1129 - expression: Document<'a>, 1130 - }, 1131 906 } 1132 907 1133 908 impl<'a> Check<'a> { 1134 909 pub fn into_doc(self, match_desired: bool) -> Document<'a> { 1135 910 match self { 1136 - Check::Guard { expression } => { 1137 - if match_desired { 1138 - expression 1139 - } else { 1140 - docvec!["!", expression] 1141 - } 1142 - } 1143 - 1144 911 Check::Booly { 1145 912 expected_to_be_truthy, 1146 913 subject, ··· 1245 1012 | Check::BitArrayBitSize { .. } 1246 1013 | Check::StringPrefix { .. } 1247 1014 | Check::Booly { .. } => false, 1248 - Check::Guard { .. } => true, 1249 1015 } 1250 1016 } 1251 1017 } ··· 1253 1019 pub(crate) fn assign_subject<'a>( 1254 1020 expression_generator: &mut expression::Generator<'_, 'a>, 1255 1021 subject: &'a TypedExpr, 1256 - ) -> (Document<'a>, Option<Document<'a>>) { 1022 + ) -> (EcoString, Option<EcoString>) { 1257 1023 static ASSIGNMENT_VAR_ECO_STR: OnceLock<EcoString> = OnceLock::new(); 1258 1024 1259 1025 match subject { ··· 1262 1028 // performing computation or side effects multiple times. 1263 1029 TypedExpr::Var { 1264 1030 name, constructor, .. 1265 - } if constructor.is_local_variable() => { 1266 - (expression_generator.local_var(name).to_doc(), None) 1267 - } 1031 + } if constructor.is_local_variable() => (expression_generator.local_var(name), None), 1268 1032 // If it's not a variable we need to assign it to a variable 1269 1033 // to avoid rendering the subject expression multiple times 1270 1034 _ => { 1271 1035 let subject = expression_generator 1272 - .next_local_var(ASSIGNMENT_VAR_ECO_STR.get_or_init(|| ASSIGNMENT_VAR.into())) 1273 - .to_doc(); 1036 + .next_local_var(ASSIGNMENT_VAR_ECO_STR.get_or_init(|| ASSIGNMENT_VAR.into())); 1274 1037 (subject.clone(), Some(subject)) 1275 1038 } 1276 1039 } 1277 - } 1278 - 1279 - pub(crate) fn assign_subjects<'a>( 1280 - expression_generator: &mut expression::Generator<'_, 'a>, 1281 - subjects: &'a [TypedExpr], 1282 - ) -> Vec<(Document<'a>, Option<Document<'a>>)> { 1283 - let mut out = Vec::with_capacity(subjects.len()); 1284 - for subject in subjects { 1285 - out.push(assign_subject(expression_generator, subject)) 1286 - } 1287 - out 1288 1040 } 1289 1041 1290 1042 /// Calculates the length of str as utf16 without escape characters.
+46
compiler-core/src/javascript/tests/case.rs
··· 1 1 use crate::assert_js; 2 2 3 + #[test] 4 + fn tuple_and_guard() { 5 + assert_js!( 6 + r#" 7 + fn go(x) { 8 + case #(1, 2) { 9 + #(1, a) if a == 2 -> 1 10 + #(_, _) -> 2 11 + } 12 + } 13 + "#, 14 + ) 15 + } 16 + 17 + #[test] 18 + fn guard_variable_only_brought_into_scope_when_needed() { 19 + assert_js!( 20 + r#" 21 + fn go(x) { 22 + case x { 23 + // We want `a` to be defined before the guard check, and 24 + // `b` to be defined only if the predicate on a matches! 25 + [a, b] if a == 1 -> a + b 26 + _ -> 2 27 + } 28 + } 29 + "# 30 + ) 31 + } 32 + 33 + // https://github.com/gleam-lang/gleam/issues/4221 34 + #[test] 35 + fn guard_variable_only_brought_into_scope_when_needed_1() { 36 + assert_js!( 37 + r#" 38 + pub fn main() { 39 + case 1 { 40 + i if i == 1 -> True 41 + i if i < 2 -> True 42 + _ -> False 43 + } 44 + } 45 + "# 46 + ) 47 + } 48 + 3 49 // https://github.com/gleam-lang/gleam/issues/1187 4 50 #[test] 5 51 fn pointless() {
+1 -3
compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__blocks__nested_multiexpr_blocks_with_case.snap
··· 26 26 { 27 27 2; 28 28 let $ = true; 29 - { 30 - _block = 3; 31 - } 29 + _block = 3; 32 30 } 33 31 } 34 32 let x = _block;
+1 -3
compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__bools__nil_case.snap
··· 13 13 14 14 ----- COMPILED JAVASCRIPT 15 15 function go(a) { 16 - { 17 - return 0; 18 - } 16 + return 0; 19 17 }
+8 -4
compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case__case_branches_guards_are_wrapped_in_parentheses.snap
··· 18 18 function anything() { 19 19 while (true) { 20 20 let $ = toList([]); 21 - if ($.hasLength(1) && (false || true)) { 22 - let a = $.head; 23 - return a; 21 + if ($.hasLength(0)) { 22 + 24 23 } else { 25 - 24 + if ($.tail.hasLength(0)) { 25 + if (false || true) { 26 + let a = $.head; 27 + return a; 28 + } 29 + } 26 30 } 27 31 } 28 32 }
+2 -2
compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case__case_local_var_in_tuple.snap
··· 19 19 function go(x, y) { 20 20 let z = false; 21 21 let $ = true; 22 - if (isEqual([$, z], [true, false])) { 23 - let x$1 = $; 22 + let x$1 = $; 23 + if (isEqual([x$1, z], [true, false])) { 24 24 return x$1; 25 25 } else { 26 26 return false;
+2 -5
compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case__deeply_nested_string_prefix_assignment.snap
··· 49 49 50 50 export function main() { 51 51 let tmp = new Wibble(new Wobble(new Wabble([42, "wibble"]))); 52 - if (tmp instanceof Wibble && 53 - tmp[0] instanceof Wobble && 54 - tmp[0].wabble instanceof Wabble && 55 - tmp[0].wabble.tuple[1].startsWith("w")) { 52 + if (tmp[0].wabble.tuple[1].startsWith("w")) { 53 + let wibble = "w"; 56 54 let rest = tmp[0].wabble.tuple[1].slice(1); 57 - let wibble = "w"; 58 55 return wibble + rest; 59 56 } else { 60 57 throw makeError(
+38
compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case__guard_variable_only_brought_into_scope_when_needed.snap
··· 1 + --- 2 + source: compiler-core/src/javascript/tests/case.rs 3 + expression: "\nfn go(x) {\n case x {\n // We want `a` to be defined before the guard check, and\n // `b` to be defined only if the predicate on a matches!\n [a, b] if a == 1 -> a + b\n _ -> 2\n }\n}\n" 4 + --- 5 + ----- SOURCE CODE 6 + 7 + fn go(x) { 8 + case x { 9 + // We want `a` to be defined before the guard check, and 10 + // `b` to be defined only if the predicate on a matches! 11 + [a, b] if a == 1 -> a + b 12 + _ -> 2 13 + } 14 + } 15 + 16 + 17 + ----- COMPILED JAVASCRIPT 18 + function go(x) { 19 + if (x.hasLength(0)) { 20 + return 2; 21 + } else { 22 + if (x.tail.hasLength(0)) { 23 + return 2; 24 + } else { 25 + if (x.tail.tail.hasLength(0)) { 26 + let a = x.head; 27 + if (a === 1) { 28 + let b = x.tail.head; 29 + return a + b; 30 + } else { 31 + return 2; 32 + } 33 + } else { 34 + return 2; 35 + } 36 + } 37 + } 38 + }
+30
compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case__guard_variable_only_brought_into_scope_when_needed_1.snap
··· 1 + --- 2 + source: compiler-core/src/javascript/tests/case.rs 3 + expression: "\npub fn main() {\n case 1 {\n i if i == 1 -> True\n i if i < 2 -> True\n _ -> False\n }\n}\n" 4 + --- 5 + ----- SOURCE CODE 6 + 7 + pub fn main() { 8 + case 1 { 9 + i if i == 1 -> True 10 + i if i < 2 -> True 11 + _ -> False 12 + } 13 + } 14 + 15 + 16 + ----- COMPILED JAVASCRIPT 17 + export function main() { 18 + let $ = 1; 19 + let i = $; 20 + if (i === 1) { 21 + return true; 22 + } else { 23 + let i$1 = $; 24 + if (i$1 < 2) { 25 + return true; 26 + } else { 27 + return false; 28 + } 29 + } 30 + }
+6 -2
compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case__multi_subject_catch_all.snap
··· 14 14 15 15 ----- COMPILED JAVASCRIPT 16 16 function go(x, y) { 17 - if (x && y) { 18 - return 1; 17 + if (y) { 18 + if (x) { 19 + return 1; 20 + } else { 21 + return 0; 22 + } 19 23 } else { 20 24 return 0; 21 25 }
+5 -3
compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case__multi_subject_no_catch_all.snap
··· 17 17 function go(x, y) { 18 18 if (x) { 19 19 return 1; 20 - } else if (y) { 21 - return 2; 22 20 } else { 23 - return 0; 21 + if (y) { 22 + return 2; 23 + } else { 24 + return 0; 25 + } 24 26 } 25 27 }
+5 -3
compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case__multi_subject_or.snap
··· 16 16 function go(x, y) { 17 17 if (x) { 18 18 return 1; 19 - } else if (y) { 20 - return 1; 21 19 } else { 22 - return 0; 20 + if (y) { 21 + return 1; 22 + } else { 23 + return 0; 24 + } 23 25 } 24 26 }
+1 -5
compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case__multi_subject_subject_assignments.snap
··· 16 16 function go() { 17 17 let $ = true; 18 18 let $1 = false; 19 - if ($ && $1) { 20 - return 1; 21 - } else { 22 - return 0; 23 - } 19 + return 0; 24 20 }
+2 -2
compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case__nested_string_prefix_assignment.snap
··· 29 29 30 30 export function main() { 31 31 let tmp = new Wibble("wibble"); 32 - if (tmp instanceof Wibble && tmp.wobble.startsWith("w")) { 33 - let rest = tmp.wobble.slice(1); 32 + if (tmp.wobble.startsWith("w")) { 34 33 let wibble = "w"; 34 + let rest = tmp.wobble.slice(1); 35 35 return wibble + rest; 36 36 } else { 37 37 throw makeError(
+26 -6
compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case__nested_string_prefix_match.snap
··· 17 17 18 18 function main() { 19 19 let $ = new Ok(toList(["a", "b c", "d"])); 20 - if ($.isOk() && 21 - $[0].hasLength(3) && 22 - $[0].head === "a" && 23 - $[0].tail.head.startsWith("b ") && 24 - $[0].tail.tail.head === "d") { 20 + if ($[0].hasLength(0)) { 25 21 return 1; 26 22 } else { 27 - return 1; 23 + if ($[0].tail.hasLength(0)) { 24 + return 1; 25 + } else { 26 + if ($[0].tail.tail.hasLength(0)) { 27 + return 1; 28 + } else { 29 + if ($[0].tail.tail.tail.hasLength(0)) { 30 + if ($[0].tail.tail.head === "d") { 31 + if ($[0].tail.head.startsWith("b ")) { 32 + if ($[0].head === "a") { 33 + return 1; 34 + } else { 35 + return 1; 36 + } 37 + } else { 38 + return 1; 39 + } 40 + } else { 41 + return 1; 42 + } 43 + } else { 44 + return 1; 45 + } 46 + } 47 + } 28 48 } 29 49 }
+18 -5
compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case__nested_string_prefix_match_that_would_crash_on_js.snap
··· 17 17 18 18 function main() { 19 19 let $ = new Ok(toList(["b c", "d"])); 20 - if ($.isOk() && 21 - $[0].hasLength(2) && 22 - $[0].head.startsWith("b ") && 23 - $[0].tail.head === "d") { 20 + if ($[0].hasLength(0)) { 24 21 return 1; 25 22 } else { 26 - return 1; 23 + if ($[0].tail.hasLength(0)) { 24 + return 1; 25 + } else { 26 + if ($[0].tail.tail.hasLength(0)) { 27 + if ($[0].tail.head === "d") { 28 + if ($[0].head.startsWith("b ")) { 29 + return 1; 30 + } else { 31 + return 1; 32 + } 33 + } else { 34 + return 1; 35 + } 36 + } else { 37 + return 1; 38 + } 39 + } 27 40 } 28 41 }
+1 -3
compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case__pointless.snap
··· 13 13 14 14 ----- COMPILED JAVASCRIPT 15 15 function go(x) { 16 - { 17 - return x; 18 - } 16 + return x; 19 17 }
+4 -6
compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case__single_clause_variables.snap
··· 17 17 export function main() { 18 18 let text = "first defined"; 19 19 let $ = "defined again"; 20 - { 21 - let text$1 = $; 22 - undefined 23 - } 24 - let text$1 = "a third time"; 25 - return text$1; 20 + let text$1 = $; 21 + undefined 22 + let text$2 = "a third time"; 23 + return text$2; 26 24 }
+4 -6
compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case__single_clause_variables_assigned.snap
··· 18 18 let text = "first defined"; 19 19 let _block; 20 20 let $ = "defined again"; 21 - { 22 - let text$1 = $; 23 - _block = undefined; 24 - } 21 + let text$1 = $; 22 + _block = undefined; 25 23 let other = _block; 26 - let text$1 = "a third time"; 27 - return text$1; 24 + let text$2 = "a third time"; 25 + return text$2; 28 26 }
+28
compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case__tuple_and_guard.snap
··· 1 + --- 2 + source: compiler-core/src/javascript/tests/case.rs 3 + expression: "\nfn go(x) {\n case #(1, 2) {\n #(1, a) if a == 2 -> 1\n #(_, _) -> 2\n }\n}\n" 4 + --- 5 + ----- SOURCE CODE 6 + 7 + fn go(x) { 8 + case #(1, 2) { 9 + #(1, a) if a == 2 -> 1 10 + #(_, _) -> 2 11 + } 12 + } 13 + 14 + 15 + ----- COMPILED JAVASCRIPT 16 + function go(x) { 17 + let $ = [1, 2]; 18 + if ($[0] === 1) { 19 + let a = $[1]; 20 + if (a === 2) { 21 + return 1; 22 + } else { 23 + return 2; 24 + } 25 + } else { 26 + return 2; 27 + } 28 + }
+13 -7
compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case_clause_guards__alternative_patterns_assignment.snap
··· 13 13 14 14 ----- COMPILED JAVASCRIPT 15 15 export function main(xs) { 16 - if (xs.hasLength(1)) { 17 - let x = xs.head; 18 - return x; 19 - } else if (xs.hasLength(2)) { 20 - let x = xs.tail.head; 21 - return x; 16 + if (xs.hasLength(0)) { 17 + return 1; 22 18 } else { 23 - return 1; 19 + if (xs.tail.hasLength(0)) { 20 + let x = xs.head; 21 + return x; 22 + } else { 23 + if (xs.tail.tail.hasLength(0)) { 24 + let x = xs.tail.head; 25 + return x; 26 + } else { 27 + return 1; 28 + } 29 + } 24 30 } 25 31 }
+21 -7
compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case_clause_guards__alternative_patterns_guard.snap
··· 13 13 14 14 ----- COMPILED JAVASCRIPT 15 15 export function main(xs) { 16 - if (xs.hasLength(1) && (xs.head === 1)) { 17 - let x = xs.head; 18 - return x; 19 - } else if (xs.hasLength(2) && (xs.tail.head === 1)) { 20 - let x = xs.tail.head; 21 - return x; 22 - } else { 16 + if (xs.hasLength(0)) { 23 17 return 0; 18 + } else { 19 + if (xs.tail.hasLength(0)) { 20 + let x = xs.head; 21 + if (x === 1) { 22 + return x; 23 + } else { 24 + return 0; 25 + } 26 + } else { 27 + if (xs.tail.tail.hasLength(0)) { 28 + let x = xs.tail.head; 29 + if (x === 1) { 30 + return x; 31 + } else { 32 + return 0; 33 + } 34 + } else { 35 + return 0; 36 + } 37 + } 24 38 } 25 39 }
+23 -5
compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case_clause_guards__alternative_patterns_list.snap
··· 13 13 14 14 ----- COMPILED JAVASCRIPT 15 15 export function main(xs) { 16 - if (xs.hasLength(1) && xs.head === 1) { 17 - return 0; 18 - } else if (xs.hasLength(2) && xs.head === 1 && xs.tail.head === 2) { 19 - return 0; 16 + if (xs.hasLength(0)) { 17 + return 1; 20 18 } else { 21 - return 1; 19 + if (xs.tail.hasLength(0)) { 20 + if (xs.head === 1) { 21 + return 0; 22 + } else { 23 + return 1; 24 + } 25 + } else { 26 + if (xs.tail.tail.hasLength(0)) { 27 + if (xs.tail.head === 2) { 28 + if (xs.head === 1) { 29 + return 0; 30 + } else { 31 + return 1; 32 + } 33 + } else { 34 + return 1; 35 + } 36 + } else { 37 + return 1; 38 + } 39 + } 22 40 } 23 41 }
+2 -2
compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case_clause_guards__bitarray_with_var.snap
··· 16 16 17 17 export function main() { 18 18 let $ = 5; 19 - if (isEqual(toBitArray([$]), toBitArray([$]))) { 20 - let z = $; 19 + let z = $; 20 + if (isEqual(toBitArray([z]), toBitArray([z]))) { 21 21 return undefined; 22 22 } else { 23 23 return undefined;
+2 -2
compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case_clause_guards__constant.snap
··· 13 13 14 14 ----- COMPILED JAVASCRIPT 15 15 export function main(xs) { 16 - if (xs[0] === 1) { 17 - let x = xs[0]; 16 + let x = xs[0]; 17 + if (x === 1) { 18 18 return x; 19 19 } else { 20 20 return 0;
+2 -2
compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case_clause_guards__custom_type_constructor_imported_and_aliased.snap
··· 19 19 20 20 function func() { 21 21 let $ = new B(); 22 - if (isEqual($, new B())) { 23 - let x = $; 22 + let x = $; 23 + if (isEqual(x, new B())) { 24 24 return true; 25 25 } else { 26 26 return false;
+2 -2
compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case_clause_guards__eq_scalar.snap
··· 13 13 14 14 ----- COMPILED JAVASCRIPT 15 15 export function main(xs, y) { 16 - if (xs[0] === y) { 17 - let x = xs[0]; 16 + let x = xs[0]; 17 + if (x === y) { 18 18 return 1; 19 19 } else { 20 20 return 0;
+2 -2
compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case_clause_guards__field_access.snap
··· 32 32 export function main() { 33 33 let given_name = "jack"; 34 34 let raiden = new Person("raiden", "jack", 31); 35 - if (given_name === raiden.name) { 36 - let name = given_name; 35 + let name = given_name; 36 + if (name === raiden.name) { 37 37 return "It's jack"; 38 38 } else { 39 39 return "It's not jack";
+2 -2
compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case_clause_guards__float_division.snap
··· 17 17 18 18 export function main() { 19 19 let $ = divideFloat(5.1, 0.0); 20 - if ($ === (divideFloat(5.1, 0.0))) { 21 - let x = $; 20 + let x = $; 21 + if (x === (divideFloat(5.1, 0.0))) { 22 22 return true; 23 23 } else { 24 24 return false;
+2 -2
compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case_clause_guards__imported_aliased_ok.snap
··· 23 23 24 24 function func() { 25 25 let $ = (var0) => { return new Y(var0); }; 26 - if (isEqual($, (var0) => { return new Y(var0); })) { 27 - let y = $; 26 + let y = $; 27 + if (isEqual(y, (var0) => { return new Y(var0); })) { 28 28 return true; 29 29 } else { 30 30 return false;
+2 -2
compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case_clause_guards__int_division.snap
··· 17 17 18 18 export function main() { 19 19 let $ = divideInt(5, 2); 20 - if ($ === (divideInt(5, 2))) { 21 - let x = $; 20 + let x = $; 21 + if (x === (divideInt(5, 2))) { 22 22 return true; 23 23 } else { 24 24 return false;
+2 -2
compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case_clause_guards__int_remainder.snap
··· 17 17 18 18 export function main() { 19 19 let $ = remainderInt(4, 0); 20 - if ($ === (remainderInt(4, 0))) { 21 - let x = $; 20 + let x = $; 21 + if (x === (remainderInt(4, 0))) { 22 22 return true; 23 23 } else { 24 24 return false;
+20 -12
compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case_clause_guards__keyword_var.snap
··· 34 34 let var$ = 7; 35 35 if (class$ === while$) { 36 36 return true; 37 - } else if (isEqual(toList([class$]), toList([5]))) { 38 - return true; 39 - } else if (isEqual([var$], [5])) { 40 - let function$1 = var$; 41 - return false; 42 - } else if (10 === 5) { 43 - return true; 44 - } else if (var$ > 5) { 45 - let while$1 = var$; 46 - return false; 47 37 } else { 48 - let class$1 = var$; 49 - return false; 38 + if (isEqual(toList([class$]), toList([5]))) { 39 + return true; 40 + } else { 41 + let function$1 = var$; 42 + if (isEqual([function$1], [5])) { 43 + return false; 44 + } else { 45 + if (10 === 5) { 46 + return true; 47 + } else { 48 + let while$1 = var$; 49 + if (while$1 > 5) { 50 + return false; 51 + } else { 52 + let class$1 = var$; 53 + return false; 54 + } 55 + } 56 + } 57 + } 50 58 } 51 59 }
+2 -2
compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case_clause_guards__module_access.snap
··· 19 19 20 20 export function main() { 21 21 let name = "Tony Stark"; 22 - if (name === $hero.ironman.name) { 23 - let n = name; 22 + let n = name; 23 + if (n === $hero.ironman.name) { 24 24 return true; 25 25 } else { 26 26 return false;
+2 -2
compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case_clause_guards__module_access_aliased.snap
··· 19 19 20 20 export function main() { 21 21 let name = "Tony Stark"; 22 - if (name === $myhero.ironman.name) { 23 - let n = name; 22 + let n = name; 23 + if (n === $myhero.ironman.name) { 24 24 return true; 25 25 } else { 26 26 return false;
+2 -2
compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case_clause_guards__module_access_submodule.snap
··· 19 19 20 20 export function main() { 21 21 let name = "Tony Stark"; 22 - if (name === $submodule.ironman.name) { 23 - let n = name; 22 + let n = name; 23 + if (n === $submodule.ironman.name) { 24 24 return true; 25 25 } else { 26 26 return false;
+2 -2
compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case_clause_guards__module_list_access.snap
··· 20 20 21 21 export function main() { 22 22 let names = toList(["Tony Stark", "Bruce Wayne"]); 23 - if (isEqual(names, $hero.heroes)) { 24 - let n = names; 23 + let n = names; 24 + if (isEqual(n, $hero.heroes)) { 25 25 return true; 26 26 } else { 27 27 return false;
+2 -2
compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case_clause_guards__module_nested_access.snap
··· 19 19 20 20 export function main() { 21 21 let name = "Bruce Wayne"; 22 - if (name === $hero.batman.secret_identity.name) { 23 - let n = name; 22 + let n = name; 23 + if (n === $hero.batman.secret_identity.name) { 24 24 return true; 25 25 } else { 26 26 return false;
+2 -2
compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case_clause_guards__module_string_access.snap
··· 19 19 20 20 export function main() { 21 21 let name = "Tony Stark"; 22 - if (name === ($hero.ironman)) { 23 - let n = name; 22 + let n = name; 23 + if (n === ($hero.ironman)) { 24 24 return true; 25 25 } else { 26 26 return false;
+2 -2
compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case_clause_guards__module_tuple_access.snap
··· 19 19 20 20 export function main() { 21 21 let name = "Tony Stark"; 22 - if (name === $hero.hero[1]) { 23 - let n = name; 22 + let n = name; 23 + if (n === $hero.hero[1]) { 24 24 return true; 25 25 } else { 26 26 return false;
+2 -2
compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case_clause_guards__not_eq_scalar.snap
··· 13 13 14 14 ----- COMPILED JAVASCRIPT 15 15 export function main(xs, y) { 16 - if (xs[0] !== y) { 17 - let x = xs[0]; 16 + let x = xs[0]; 17 + if (x !== y) { 18 18 return 1; 19 19 } else { 20 20 return 0;
+2 -2
compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case_clause_guards__operator_wrapping_left.snap
··· 13 13 14 14 ----- COMPILED JAVASCRIPT 15 15 export function main(xs, y, z) { 16 - if ((xs[0] === y) === z) { 17 - let x = xs[0]; 16 + let x = xs[0]; 17 + if ((x === y) === z) { 18 18 return 1; 19 19 } else { 20 20 return 0;
+2 -2
compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case_clause_guards__operator_wrapping_right.snap
··· 13 13 14 14 ----- COMPILED JAVASCRIPT 15 15 export function main(xs, y, z) { 16 - if (xs[0] === (y === z)) { 17 - let x = xs[0]; 16 + let x = xs[0]; 17 + if (x === (y === z)) { 18 18 return 1; 19 19 } else { 20 20 return 0;
+2 -2
compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case_clause_guards__referencing_pattern_var.snap
··· 13 13 14 14 ----- COMPILED JAVASCRIPT 15 15 export function main(xs) { 16 - if (xs[0]) { 17 - let x = xs[0]; 16 + let x = xs[0]; 17 + if (x) { 18 18 return 1; 19 19 } else { 20 20 return 0;
+7 -5
compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__imported_pattern.snap
··· 23 23 import { Two } from "../other.mjs"; 24 24 25 25 export function main(x) { 26 - if (x instanceof Two && x.a === 1) { 26 + if (x.a === 1) { 27 27 return 1; 28 - } else if (x instanceof $other.Two && x.b === 2) { 29 - let c = x.c; 30 - return c; 31 28 } else { 32 - return 3; 29 + if (x.b === 2) { 30 + let c = x.c; 31 + return c; 32 + } else { 33 + return 3; 34 + } 33 35 } 34 36 }
+3 -7
compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__nested_pattern_with_labels.snap
··· 24 24 } 25 25 26 26 function go(x) { 27 - if (x instanceof Box && x.b instanceof Box) { 28 - let a = x.b.a; 29 - let b = x.b.b; 30 - return a + b; 31 - } else { 32 - return 1; 33 - } 27 + let a = x.b.a; 28 + let b = x.b.b; 29 + return a + b; 34 30 }
+1 -1
compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__record_access_in_guard_with_reserved_field_name.snap
··· 30 30 export function main() { 31 31 let a = new Thing(undefined); 32 32 let $ = undefined; 33 - if (!$ && (a.constructor$ === undefined)) { 33 + if (a.constructor$ === undefined) { 34 34 return a.constructor$; 35 35 } else { 36 36 return undefined;
+8 -6
compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__record_access_in_pattern_with_reserved_field_name.snap
··· 32 32 export function main() { 33 33 let a = new Thing(undefined); 34 34 let ctor = a.constructor$; 35 - if (a.constructor$ === ctor) { 36 - let a$1 = a; 37 - return undefined; 38 - } else if (a instanceof Thing && (ctor === a.constructor$)) { 39 - let constructor = a.constructor$; 35 + let a$1 = a; 36 + if (a$1.constructor$ === ctor) { 40 37 return undefined; 41 38 } else { 42 - return undefined; 39 + let constructor = a.constructor; 40 + if (ctor === constructor) { 41 + return undefined; 42 + } else { 43 + return undefined; 44 + } 43 45 } 44 46 }
+1 -3
compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__echo__echo_with_a_case_expression.snap
··· 25 25 export function main() { 26 26 let _block; 27 27 let $ = 1; 28 - { 29 - _block = 2; 30 - } 28 + _block = 2; 31 29 return echo(_block, "src/module.gleam", 3); 32 30 } 33 31
+1 -3
compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__functions__variable_rewriting_in_anon_fn_with_matching_parameter_in_case.snap
··· 18 18 return (state) => { 19 19 let _block; 20 20 let $ = undefined; 21 - { 22 - _block = state; 23 - } 21 + _block = state; 24 22 let state$1 = _block; 25 23 return state$1; 26 24 };
+9 -5
compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__lists__case.snap
··· 18 18 function go(xs) { 19 19 if (xs.hasLength(0)) { 20 20 return 0; 21 - } else if (xs.hasLength(1)) { 22 - return 1; 23 - } else if (xs.hasLength(2)) { 24 - return 2; 25 21 } else { 26 - return 9999; 22 + if (xs.tail.hasLength(0)) { 23 + return 1; 24 + } else { 25 + if (xs.tail.tail.hasLength(0)) { 26 + return 2; 27 + } else { 28 + return 9999; 29 + } 30 + } 27 31 } 28 32 }
+8 -10
compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__panic__case.snap
··· 15 15 import { makeError } from "../gleam.mjs"; 16 16 17 17 function go(x) { 18 - { 19 - throw makeError( 20 - "panic", 21 - "my/mod", 22 - 4, 23 - "go", 24 - "`panic` expression evaluated.", 25 - {} 26 - ) 27 - } 18 + throw makeError( 19 + "panic", 20 + "my/mod", 21 + 4, 22 + "go", 23 + "`panic` expression evaluated.", 24 + {} 25 + ) 28 26 }
+1 -1
compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__strings__string_prefix_assignment.snap
··· 15 15 ----- COMPILED JAVASCRIPT 16 16 export function go(x) { 17 17 if (x.startsWith("Hello, ")) { 18 - let name = x.slice(7); 19 18 let greeting = "Hello, "; 19 + let name = x.slice(7); 20 20 return greeting; 21 21 } else { 22 22 return "Unknown";
-3
compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__strings__string_prefix_assignment_with_multiple_subjects.snap
··· 17 17 if (x.startsWith("1")) { 18 18 let prefix = "1"; 19 19 return prefix; 20 - } else if (x.startsWith("11")) { 21 - let prefix = "11"; 22 - return prefix; 23 20 } else { 24 21 return "Unknown"; 25 22 }
+4 -4
compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__strings__string_prefix_assignment_with_utf_escape_sequence.snap
··· 18 18 ----- COMPILED JAVASCRIPT 19 19 export function go(x) { 20 20 if (x.startsWith("\u{0032} ")) { 21 - let name = x.slice(2); 22 21 let greeting = "\u{0032} "; 22 + let name = x.slice(2); 23 23 return greeting; 24 24 } else if (x.startsWith("\u{0007ff} ")) { 25 - let name = x.slice(2); 26 25 let greeting = "\u{0007ff} "; 26 + let name = x.slice(2); 27 27 return greeting; 28 28 } else if (x.startsWith("\u{00ffff} ")) { 29 - let name = x.slice(2); 30 29 let greeting = "\u{00ffff} "; 30 + let name = x.slice(2); 31 31 return greeting; 32 32 } else if (x.startsWith("\u{10ffff} ")) { 33 - let name = x.slice(3); 34 33 let greeting = "\u{10ffff} "; 34 + let name = x.slice(3); 35 35 return greeting; 36 36 } else { 37 37 return "Unknown";
+1 -1
compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__strings__string_prefix_shadowing.snap
··· 15 15 ----- COMPILED JAVASCRIPT 16 16 export function go(x) { 17 17 if (x.startsWith("Hello, ")) { 18 - let name = x.slice(7); 19 18 let x$1 = "Hello, "; 19 + let name = x.slice(7); 20 20 return x$1; 21 21 } else { 22 22 return "Unknown";
+34
compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__tuples__case.snap
··· 1 + --- 2 + source: compiler-core/src/javascript/tests/tuples.rs 3 + expression: "\nfn go(a) {\n case a {\n #(2, a) -> a\n #(1, 1) -> 1\n #(a, b) -> a + b\n }\n}\n" 4 + --- 5 + ----- SOURCE CODE 6 + 7 + fn go(a) { 8 + case a { 9 + #(2, a) -> a 10 + #(1, 1) -> 1 11 + #(a, b) -> a + b 12 + } 13 + } 14 + 15 + 16 + ----- COMPILED JAVASCRIPT 17 + function go(a) { 18 + if (a[0] === 2) { 19 + let a$1 = a[1]; 20 + return a$1; 21 + } else if (a[0] === 1) { 22 + if (a[1] === 1) { 23 + return 1; 24 + } else { 25 + let a$1 = a[0]; 26 + let b = a[1]; 27 + return a$1 + b; 28 + } 29 + } else { 30 + let a$1 = a[0]; 31 + let b = a[1]; 32 + return a$1 + b; 33 + } 34 + }
+2 -2
compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__tuples__tuple_with_block_element.snap
··· 1 1 --- 2 2 source: compiler-core/src/javascript/tests/tuples.rs 3 - expression: "\nfn go() {\n #(\n \"1\", \n {\n \"2\"\n \"3\"\n },\n )\n}\n" 3 + expression: "\nfn go() {\n #(\n \"1\",\n {\n \"2\"\n \"3\"\n },\n )\n}\n" 4 4 --- 5 5 ----- SOURCE CODE 6 6 7 7 fn go() { 8 8 #( 9 - "1", 9 + "1", 10 10 { 11 11 "2" 12 12 "3"
+1 -14
compiler-core/src/javascript/tests/tuples.rs
··· 54 54 r#" 55 55 fn go() { 56 56 #( 57 - "1", 57 + "1", 58 58 { 59 59 "2" 60 60 "3" ··· 126 126 #(2, a) -> a 127 127 #(1, 1) -> 1 128 128 #(a, b) -> a + b 129 - } 130 - } 131 - "#, 132 - r#"function go(a) { 133 - if (a[0] === 2) { 134 - let a$1 = a[1]; 135 - return a$1; 136 - } else if (a[0] === 1 && a[1] === 1) { 137 - return 1; 138 - } else { 139 - let a$1 = a[0]; 140 - let b = a[1]; 141 - return a$1 + b; 142 129 } 143 130 } 144 131 "#