Repository: gleam-lang/gleam (rust)

Issue:
Fatal bug in javascript code generation of case clauses
I've found a bug that I introduced in #4337.
If the branch of a case expression needs to be wrapped in a block, for example:
```gleam
case bool {
  True -> a |> b
  False -> c |> d
}
```
This will be compiled to something like:
```js
let _block;
let _pipe = a;
_block = b(_pipe);

let _block;
let _pipe = c;
_block = d(_pipe);

if (bool) {
  return _block;
} else {
  return _block;
}
```
This is of course incorrect, those two statements should appear within the body of the `if` statement.

I'm working on a fix at the moment, but thought I'd open an issue first.

Pull request description:
Fix bug with code generation of case clauses
Fixes #4383 
I just forgot to take `statement_level` if the case clause guard wasn't a block.

Patch:
diff --git a/compiler-core/src/javascript/expression.rs b/compiler-core/src/javascript/expression.rs
index 17a72d839..0ab08dca5 100644
--- a/compiler-core/src/javascript/expression.rs
+++ b/compiler-core/src/javascript/expression.rs
@@ -211,14 +211,18 @@ impl<'module, 'a> Generator<'module, 'a> {
             Statement::Assignment(assignment) => self.assignment(assignment),
             Statement::Use(_use) => self.expression(&_use.call),
         }?;
+        Ok(self.add_statement_level(expression_doc))
+    }
+
+    fn add_statement_level(&mut self, expression: Document<'a>) -> Document<'a> {
         if self.statement_level.is_empty() {
-            Ok(expression_doc)
+            expression
         } else {
             let mut statements = std::mem::take(&mut self.statement_level);
-            statements.push(expression_doc);
-            Ok(Itertools::intersperse(statements.into_iter(), line())
+            statements.push(expression);
+            Itertools::intersperse(statements.into_iter(), line())
                 .collect_vec()
-                .to_doc())
+                .to_doc()
         }
     }
 
@@ -678,7 +682,10 @@ impl<'module, 'a> Generator<'module, 'a> {
     fn expression_flattening_blocks(&mut self, expression: &'a TypedExpr) -> Output<'a> {
         match expression {
             TypedExpr::Block { statements, .. } => self.statements(statements),
-            _ => self.expression(expression),
+            _ => {
+                let expression_document = self.expression(expression)?;
+                Ok(self.add_statement_level(expression_document))
+            }
         }
     }
 


Test patch:
diff --git a/compiler-core/src/javascript/tests/case.rs b/compiler-core/src/javascript/tests/case.rs
index a3fc91790..341674fd3 100644
--- a/compiler-core/src/javascript/tests/case.rs
+++ b/compiler-core/src/javascript/tests/case.rs
@@ -294,7 +294,7 @@ fn deeply_nested_string_prefix_assignment() {
         r#"
 type Wibble {
   Wibble(Wobble)
-}            
+}
 type Wobble {
   Wobble(wabble: Wabble)
 }
@@ -312,3 +312,27 @@ pub fn main() {
 "#
     )
 }
+
+// https://github.com/gleam-lang/gleam/issues/4383
+#[test]
+fn record_update_in_pipeline_in_case_clause() {
+    assert_js!(
+        "
+type Wibble {
+  Wibble(wibble: Int, wobble: Int)
+}
+
+fn identity(x) {
+  x
+}
+
+fn go(x) {
+  case x {
+    Wibble(1, _) -> Wibble(..x, wibble: 4) |> identity
+    Wibble(_, 3) -> Wibble(..x, wobble: 10) |> identity
+    _ -> panic
+  }
+}
+"
+    );
+}
diff --git a/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case__deeply_nested_string_prefix_assignment.snap b/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case__deeply_nested_string_prefix_assignment.snap
index bd745ab93..9b67dc686 100644
--- a/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case__deeply_nested_string_prefix_assignment.snap
+++ b/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case__deeply_nested_string_prefix_assignment.snap
@@ -1,12 +1,12 @@
 ---
 source: compiler-core/src/javascript/tests/case.rs
-expression: "\ntype Wibble {\n  Wibble(Wobble)\n}            \ntype Wobble {\n  Wobble(wabble: Wabble)\n}\ntype Wabble {\n  Wabble(tuple: #(Int, String))\n}\n\npub fn main() {\n  let tmp = Wibble(Wobble(Wabble(#(42, \"wibble\"))))\n  case tmp {\n    Wibble(Wobble(Wabble(#(_int, \"w\" as wibble <> rest)))) -> wibble <> rest\n    _ -> panic\n  }\n}\n"
+expression: "\ntype Wibble {\n  Wibble(Wobble)\n}\ntype Wobble {\n  Wobble(wabble: Wabble)\n}\ntype Wabble {\n  Wabble(tuple: #(Int, String))\n}\n\npub fn main() {\n  let tmp = Wibble(Wobble(Wabble(#(42, \"wibble\"))))\n  case tmp {\n    Wibble(Wobble(Wabble(#(_int, \"w\" as wibble <> rest)))) -> wibble <> rest\n    _ -> panic\n  }\n}\n"
 ---
 ----- SOURCE CODE
 
 type Wibble {
   Wibble(Wobble)
-}            
+}
 type Wobble {
   Wobble(wabble: Wabble)
 }
diff --git a/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case__record_update_in_pipeline_in_case_clause.snap b/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case__record_update_in_pipeline_in_case_clause.snap
new file mode 100644
index 000000000..fc4d0824e
--- /dev/null
+++ b/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case__record_update_in_pipeline_in_case_clause.snap
@@ -0,0 +1,62 @@
+---
+source: compiler-core/src/javascript/tests/case.rs
+expression: "\ntype Wibble {\n  Wibble(wibble: Int, wobble: Int)\n}\n\nfn identity(x) {\n  x\n}\n\nfn go(x) {\n  case x {\n    Wibble(1, _) -> Wibble(..x, wibble: 4) |> identity\n    Wibble(_, 3) -> Wibble(..x, wobble: 10) |> identity\n    _ -> panic\n  }\n}\n"
+---
+----- SOURCE CODE
+
+type Wibble {
+  Wibble(wibble: Int, wobble: Int)
+}
+
+fn identity(x) {
+  x
+}
+
+fn go(x) {
+  case x {
+    Wibble(1, _) -> Wibble(..x, wibble: 4) |> identity
+    Wibble(_, 3) -> Wibble(..x, wobble: 10) |> identity
+    _ -> panic
+  }
+}
+
+
+----- COMPILED JAVASCRIPT
+import { CustomType as $CustomType, makeError } from "../gleam.mjs";
+
+class Wibble extends $CustomType {
+  constructor(wibble, wobble) {
+    super();
+    this.wibble = wibble;
+    this.wobble = wobble;
+  }
+}
+
+function identity(x) {
+  return x;
+}
+
+function go(x) {
+  if (x instanceof Wibble && x.wibble === 1) {
+    let _block;
+    let _record = x;
+    _block = new Wibble(4, _record.wobble);
+    let _pipe = _block;
+    return identity(_pipe);
+  } else if (x instanceof Wibble && x.wobble === 3) {
+    let _block;
+    let _record = x;
+    _block = new Wibble(_record.wibble, 10);
+    let _pipe = _block;
+    return identity(_pipe);
+  } else {
+    throw makeError(
+      "panic",
+      "my/mod",
+      14,
+      "go",
+      "`panic` expression evaluated.",
+      {}
+    )
+  }
+}