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

Configure Feed

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

Correctly add semicolons to pipeline

author
Gears
committer
Louis Pilfold
date (Mar 21, 2026, 5:54 PM UTC) commit 9c5d13ec parent 2a2ab5af change-id lznnrpoo
+149 -36
+4
CHANGELOG.md
··· 76 76 instead of raising an error. 77 77 ([Lucy McPhail](https://github.com/lucymcphail)) 78 78 79 + - Fixed a bug where semicolons would not be properly added to pipelines in 80 + generated JavaScript code, leading to runtime errors in certain circumstances. 81 + ([Surya Rose](https://github.com/GearsDatapacks)) 82 + 79 83 ## v1.15.1 - 2026-03-17 80 84 81 85 ### Bug fixes
+51 -32
compiler-core/src/javascript/expression.rs
··· 738 738 .as_ref() 739 739 .expect("echo with no previous step in a pipe"); 740 740 this.echo(var.to_doc(), message.as_deref(), location) 741 - })) 741 + })); 742 + documents.push(";".to_doc()); 742 743 } else { 743 744 // Otherwise we assign the intermediate pipe value to a variable. 744 745 let assignment_document = self ··· 761 762 { 762 763 let var = latest_local_var.expect("echo with no previous step in a pipe"); 763 764 documents.push(self.echo(var.to_doc(), message.as_deref(), location)); 765 + match &self.scope_position { 766 + Position::Statement => documents.push(";".to_doc()), 767 + Position::Expression(_) | Position::Tail | Position::Assign(_) => {} 768 + } 764 769 } else { 765 - let finally = self.expression(finally); 766 - documents.push(self.add_statement_level(finally)) 770 + let finally_doc = self.expression(finally); 771 + documents.push(self.add_statement_level(finally_doc)); 772 + 773 + // Add a semicolon if needed, to ensure the pipeline is properly 774 + // delimited 775 + match &self.scope_position { 776 + Position::Statement if expression_requires_semicolon(finally) => { 777 + documents.push(";".to_doc()) 778 + } 779 + Position::Statement 780 + | Position::Expression(_) 781 + | Position::Tail 782 + | Position::Assign(_) => {} 783 + } 767 784 } 768 785 769 786 documents.to_doc().force_break() ··· 2741 2758 2742 2759 fn requires_semicolon(statement: &TypedStatement) -> bool { 2743 2760 match statement { 2744 - Statement::Expression( 2745 - TypedExpr::Int { .. } 2746 - | TypedExpr::Fn { .. } 2747 - | TypedExpr::Var { .. } 2748 - | TypedExpr::List { .. } 2749 - | TypedExpr::Call { .. } 2750 - | TypedExpr::Echo { .. } 2751 - | TypedExpr::Float { .. } 2752 - | TypedExpr::String { .. } 2753 - | TypedExpr::BinOp { .. } 2754 - | TypedExpr::Tuple { .. } 2755 - | TypedExpr::NegateInt { .. } 2756 - | TypedExpr::BitArray { .. } 2757 - | TypedExpr::TupleIndex { .. } 2758 - | TypedExpr::NegateBool { .. } 2759 - | TypedExpr::RecordAccess { .. } 2760 - | TypedExpr::PositionalAccess { .. } 2761 - | TypedExpr::ModuleSelect { .. } 2762 - | TypedExpr::Block { .. }, 2763 - ) => true, 2764 - 2765 - Statement::Expression( 2766 - TypedExpr::Todo { .. } 2767 - | TypedExpr::Case { .. } 2768 - | TypedExpr::Panic { .. } 2769 - | TypedExpr::Pipeline { .. } 2770 - | TypedExpr::RecordUpdate { .. } 2771 - | TypedExpr::Invalid { .. }, 2772 - ) => false, 2761 + Statement::Expression(expression) => expression_requires_semicolon(expression), 2773 2762 2774 2763 Statement::Assignment(_) => false, 2775 2764 Statement::Use(_) => false, 2776 2765 Statement::Assert(_) => false, 2766 + } 2767 + } 2768 + 2769 + fn expression_requires_semicolon(expression: &TypedExpr) -> bool { 2770 + match expression { 2771 + TypedExpr::Int { .. } 2772 + | TypedExpr::Fn { .. } 2773 + | TypedExpr::Var { .. } 2774 + | TypedExpr::List { .. } 2775 + | TypedExpr::Call { .. } 2776 + | TypedExpr::Echo { .. } 2777 + | TypedExpr::Float { .. } 2778 + | TypedExpr::String { .. } 2779 + | TypedExpr::BinOp { .. } 2780 + | TypedExpr::Tuple { .. } 2781 + | TypedExpr::NegateInt { .. } 2782 + | TypedExpr::BitArray { .. } 2783 + | TypedExpr::TupleIndex { .. } 2784 + | TypedExpr::NegateBool { .. } 2785 + | TypedExpr::RecordAccess { .. } 2786 + | TypedExpr::PositionalAccess { .. } 2787 + | TypedExpr::ModuleSelect { .. } 2788 + | TypedExpr::Block { .. } => true, 2789 + 2790 + TypedExpr::Todo { .. } 2791 + | TypedExpr::Case { .. } 2792 + | TypedExpr::Panic { .. } 2793 + | TypedExpr::Pipeline { .. } 2794 + | TypedExpr::RecordUpdate { .. } 2795 + | TypedExpr::Invalid { .. } => false, 2777 2796 } 2778 2797 } 2779 2798
+25
compiler-core/src/javascript/tests/functions.rs
··· 596 596 " 597 597 ); 598 598 } 599 + 600 + // https://github.com/gleam-lang/gleam/issues/5488 601 + #[test] 602 + fn pipeline_has_correct_semicolons() { 603 + assert_js!( 604 + " 605 + pub fn main() { 606 + 1 |> echo |> fn(x) { { x + 1 } * 2 } 607 + { 1 + 2 } * 3 608 + } 609 + " 610 + ); 611 + } 612 + 613 + #[test] 614 + fn pipeline_with_final_has_correct_semicolon() { 615 + assert_js!( 616 + " 617 + pub fn main() { 618 + 1 |> echo 619 + { 1 + 2 } * 3 620 + } 621 + " 622 + ); 623 + }
+1 -1
compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__echo__echo_in_a_pipeline.snap
··· 33 33 34 34 export function main() { 35 35 let _pipe = toList([1, 2, 3]); 36 - echo(_pipe, undefined, "src/module.gleam", 4) 36 + echo(_pipe, undefined, "src/module.gleam", 4); 37 37 return wibble(_pipe); 38 38 } 39 39
+1 -1
compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__echo__echo_in_a_pipeline_with_message.snap
··· 33 33 34 34 export function main() { 35 35 let _pipe = toList([1, 2, 3]); 36 - echo(_pipe, "message!!", "src/module.gleam", 4) 36 + echo(_pipe, "message!!", "src/module.gleam", 4); 37 37 return wibble(_pipe); 38 38 } 39 39
+2 -2
compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__echo__multiple_echos_in_a_pipeline.snap
··· 36 36 37 37 export function main() { 38 38 let _pipe = toList([1, 2, 3]); 39 - echo(_pipe, undefined, "src/module.gleam", 4) 39 + echo(_pipe, undefined, "src/module.gleam", 4); 40 40 let _pipe$1 = wibble(_pipe); 41 - echo(_pipe$1, undefined, "src/module.gleam", 6) 41 + echo(_pipe$1, undefined, "src/module.gleam", 6); 42 42 let _pipe$2 = wibble(_pipe$1); 43 43 return echo(_pipe$2, undefined, "src/module.gleam", 8); 44 44 }
+33
compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__functions__pipeline_has_correct_semicolons.snap
··· 1 + --- 2 + source: compiler-core/src/javascript/tests/functions.rs 3 + expression: "\npub fn main() {\n 1 |> echo |> fn(x) { { x + 1 } * 2 }\n { 1 + 2 } * 3\n}\n" 4 + --- 5 + ----- SOURCE CODE 6 + 7 + pub fn main() { 8 + 1 |> echo |> fn(x) { { x + 1 } * 2 } 9 + { 1 + 2 } * 3 10 + } 11 + 12 + 13 + ----- COMPILED JAVASCRIPT 14 + import * as $stdlib$dict from "../../gleam_stdlib/gleam/dict.mjs"; 15 + import { 16 + Empty as $Empty, 17 + NonEmpty as $NonEmpty, 18 + CustomType as $CustomType, 19 + bitArraySlice, 20 + bitArraySliceToInt, 21 + BitArray as $BitArray, 22 + List as $List, 23 + UtfCodepoint as $UtfCodepoint, 24 + } from "../gleam.mjs"; 25 + 26 + export function main() { 27 + let _pipe = 1; 28 + echo(_pipe, undefined, "src/module.gleam", 3); 29 + (_pipe + 1) * 2; 30 + return (1 + 2) * 3; 31 + } 32 + 33 + // ...omitted code from `templates/echo.mjs`...
+32
compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__functions__pipeline_with_final_has_correct_semicolon.snap
··· 1 + --- 2 + source: compiler-core/src/javascript/tests/functions.rs 3 + expression: "\npub fn main() {\n 1 |> echo\n { 1 + 2 } * 3\n}\n" 4 + --- 5 + ----- SOURCE CODE 6 + 7 + pub fn main() { 8 + 1 |> echo 9 + { 1 + 2 } * 3 10 + } 11 + 12 + 13 + ----- COMPILED JAVASCRIPT 14 + import * as $stdlib$dict from "../../gleam_stdlib/gleam/dict.mjs"; 15 + import { 16 + Empty as $Empty, 17 + NonEmpty as $NonEmpty, 18 + CustomType as $CustomType, 19 + bitArraySlice, 20 + bitArraySliceToInt, 21 + BitArray as $BitArray, 22 + List as $List, 23 + UtfCodepoint as $UtfCodepoint, 24 + } from "../gleam.mjs"; 25 + 26 + export function main() { 27 + let _pipe = 1; 28 + echo(_pipe, undefined, "src/module.gleam", 3); 29 + return (1 + 2) * 3; 30 + } 31 + 32 + // ...omitted code from `templates/echo.mjs`...