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

Configure Feed

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

Fix duplicate let declarations in JS codegen for directly matching case subjects

+130 -1
+5
CHANGELOG.md
··· 283 283 - Fixed the formatting of some errors' hints to properly wrap. 284 284 ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) 285 285 286 + - Fixed a bug where the JavaScript code generator could produce duplicate `let` 287 + declarations for internal variables after a `case` expression whose subject 288 + directly matches one of the branches. 289 + ([Eyup Can Akman](https://github.com/eyupcanakman)) 290 + 286 291 ## v1.15.1 - 2026-03-17 287 292 288 293 ### Bug fixes
+43 -1
compiler-core/src/javascript/decision.rs
··· 391 391 if let FallbackCheck::RuntimeCheck { check } = fallback_check { 392 392 self.variables.record_check_assignments(var, check); 393 393 } 394 - return self.inside_new_scope(|this| this.decision(fallback)); 394 + 395 + // We can't use `inside_new_scope` here without care: the 396 + // code we generate goes directly into the enclosing scope 397 + // (there's no wrapping if-else block), so the `$` variable 398 + // counter must not be reset or later code could redeclare 399 + // one of the `$N` variables introduced here. 400 + let old_scope = match &self.kind { 401 + DecisionKind::Case { .. } => { 402 + self.variables.expression_generator.current_scope_vars.clone() 403 + } 404 + DecisionKind::LetAssert { .. } => Default::default(), 405 + }; 406 + let old_names = self.variables.scoped_variable_names.clone(); 407 + let old_segments = self.variables.segment_values.clone(); 408 + let old_segment_names = self.variables.scoped_segment_names.clone(); 409 + 410 + let result = self.decision(fallback); 411 + 412 + match &self.kind { 413 + DecisionKind::Case { .. } => { 414 + let assignment_var: EcoString = ASSIGNMENT_VAR.into(); 415 + let counter = self 416 + .variables 417 + .expression_generator 418 + .current_scope_vars 419 + .get(&assignment_var) 420 + .copied(); 421 + self.variables.expression_generator.current_scope_vars = old_scope; 422 + if let Some(n) = counter { 423 + let _ = self 424 + .variables 425 + .expression_generator 426 + .current_scope_vars 427 + .insert(assignment_var, n); 428 + } 429 + } 430 + DecisionKind::LetAssert { .. } => {} 431 + } 432 + 433 + self.variables.scoped_variable_names = old_names; 434 + self.variables.segment_values = old_segments; 435 + self.variables.scoped_segment_names = old_segment_names; 436 + return result; 395 437 } 396 438 397 439 // Otherwise we'll have to generate a series of if-else to check which
+19
compiler-core/src/javascript/tests/case.rs
··· 968 968 "# 969 969 ); 970 970 } 971 + 972 + // https://github.com/gleam-lang/gleam/issues/5467 973 + #[test] 974 + fn no_duplicate_let_after_directly_matching_case() { 975 + assert_js!( 976 + r#" 977 + pub fn go() { 978 + let x = 979 + case #(1, 2) { 980 + #(1, b) -> b 981 + #(_, b) -> b 982 + } 983 + 984 + let #(a, _b) = #(x, 3) 985 + 986 + a 987 + }"# 988 + ) 989 + }
+2
compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__assignments__variable_renaming.snap
··· 1 1 --- 2 2 source: compiler-core/src/javascript/tests/assignments.rs 3 + assertion_line: 37 3 4 expression: "\n\npub fn go(x, wibble) {\n let a = 1\n wibble(a)\n let a = 2\n wibble(a)\n let assert #(a, 3) = x\n let b = a\n wibble(b)\n let c = {\n let a = a\n #(a, b)\n }\n wibble(a)\n // make sure arguments are counted in initial state\n let x = c\n x\n}\n" 5 + snapshot_kind: text 4 6 --- 5 7 ----- SOURCE CODE 6 8
+2
compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__assignments__variable_used_in_pattern_and_assignment.snap
··· 1 1 --- 2 2 source: compiler-core/src/javascript/tests/assignments.rs 3 + assertion_line: 129 3 4 expression: "pub fn main(x) {\n let #(x) = #(x)\n x\n}\n" 5 + snapshot_kind: text 4 6 --- 5 7 ----- SOURCE CODE 6 8 pub fn main(x) {
+2
compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case__case_building_list_matched_by_pattern.snap
··· 1 1 --- 2 2 source: compiler-core/src/javascript/tests/case.rs 3 + assertion_line: 461 3 4 expression: "pub fn go(x) {\n case x {\n [] -> []\n [a, b] -> [a, b]\n [1, ..rest] -> [1, ..rest]\n _ -> x\n }\n}" 5 + snapshot_kind: text 4 6 --- 5 7 ----- SOURCE CODE 6 8 pub fn go(x) {
+2
compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case__case_with_multiple_subjects_building_list_matched_by_pattern.snap
··· 1 1 --- 2 2 source: compiler-core/src/javascript/tests/case.rs 3 + assertion_line: 804 3 4 expression: "pub fn go(n, x) {\n case n, x {\n 1, [] -> []\n _, [a, b] -> [a, b]\n 3, [1, ..rest] -> [1, ..rest]\n _, _ -> x\n }\n}" 5 + snapshot_kind: text 4 6 --- 5 7 ----- SOURCE CODE 6 8 pub fn go(n, x) {
+38
compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case__no_duplicate_let_after_directly_matching_case.snap
··· 1 + --- 2 + source: compiler-core/src/javascript/tests/case.rs 3 + assertion_line: 950 4 + expression: "\npub fn go() {\n let x =\n case #(1, 2) {\n #(1, b) -> b\n #(_, b) -> b\n }\n\n let #(a, _b) = #(x, 3)\n\n a\n}" 5 + snapshot_kind: text 6 + --- 7 + ----- SOURCE CODE 8 + 9 + pub fn go() { 10 + let x = 11 + case #(1, 2) { 12 + #(1, b) -> b 13 + #(_, b) -> b 14 + } 15 + 16 + let #(a, _b) = #(x, 3) 17 + 18 + a 19 + } 20 + 21 + ----- COMPILED JAVASCRIPT 22 + export function go() { 23 + let _block; 24 + let $ = [1, 2]; 25 + let $1 = $[0]; 26 + if ($1 === 1) { 27 + let b = $[1]; 28 + _block = b; 29 + } else { 30 + let b = $[1]; 31 + _block = b; 32 + } 33 + let x = _block; 34 + let $2 = [x, 3]; 35 + let a; 36 + a = $2[0]; 37 + return a; 38 + }
+2
compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case_clause_guards__alternative_patterns_list.snap
··· 1 1 --- 2 2 source: compiler-core/src/javascript/tests/case_clause_guards.rs 3 + assertion_line: 187 3 4 expression: "pub fn main(xs) -> Int {\n case xs {\n [1] | [1, 2] -> 0\n _ -> 1\n }\n}\n" 5 + snapshot_kind: text 4 6 --- 5 7 ----- SOURCE CODE 6 8 pub fn main(xs) -> Int {
+2
compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__destructure_custom_type_with_mixed_fields_first_unlabelled.snap
··· 1 1 --- 2 2 source: compiler-core/src/javascript/tests/custom_types.rs 3 + assertion_line: 281 3 4 expression: "\npub type Cat {\n Cat(String, cuteness: Int)\n}\n\npub fn go(cat) {\n let Cat(x, y) = cat\n let Cat(cuteness: y, ..) = cat\n let Cat(x, cuteness: y) = cat\n x\n}\n\n" 5 + snapshot_kind: text 4 6 --- 5 7 ----- SOURCE CODE 6 8
+2
compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__destructure_custom_type_with_named_fields.snap
··· 1 1 --- 2 2 source: compiler-core/src/javascript/tests/custom_types.rs 3 + assertion_line: 262 3 4 expression: "\npub type Cat {\n Cat(name: String, cuteness: Int)\n}\n\npub fn go(cat) {\n let Cat(x, y) = cat\n let Cat(name: x, ..) = cat\n let assert Cat(cuteness: 4, name: x) = cat\n x\n}\n\n" 5 + snapshot_kind: text 4 6 --- 5 7 ----- SOURCE CODE 6 8
+11
test/language/test/language/directly_matching_case_subject_test.gleam
··· 17 17 18 18 assert result == "ABC" 19 19 } 20 + 21 + // github.com/gleam-lang/gleam/issues/5467 22 + pub fn no_duplicate_let_after_directly_matching_case_test() { 23 + let x = 24 + case #(1, 2) { 25 + #(1, b) -> b 26 + #(_, b) -> b 27 + } 28 + let #(a, _b) = #(x, 3) 29 + assert a == 2 30 + }