diff --git a/CHANGELOG.md b/CHANGELOG.md index e0bdf12005c..83bcd03de8b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,3 +26,6 @@ - The compiler now correctly tracks the minimum required version for expressions in `BitArray`s' `size` option to be `>= 1.12.0`. ([Giacomo Cavalieri](https://github.com/giacomocavalieri)) + +- Clause guard with logical expression now compiles correctly in JS. + ([vyacheslavhere](https://github.com/vyacheslavhere)) diff --git a/compiler-core/src/javascript/decision.rs b/compiler-core/src/javascript/decision.rs index 1c6c8635831..2bfd229f61c 100644 --- a/compiler-core/src/javascript/decision.rs +++ b/compiler-core/src/javascript/decision.rs @@ -3,7 +3,9 @@ use super::{ expression::{self, Generator, Ordering, float, float_from_value}, }; use crate::{ - ast::{AssignmentKind, Endianness, SrcSpan, TypedClause, TypedExpr, TypedPattern}, + ast::{ + AssignmentKind, Endianness, SrcSpan, TypedClause, TypedClauseGuard, TypedExpr, TypedPattern, + }, docvec, exhaustiveness::{ BitArrayMatchedValue, BitArrayTest, Body, BoundValue, CompiledCase, Decision, @@ -298,7 +300,7 @@ impl<'a> CasePrinter<'_, '_, 'a, '_> { guard, if_true, if_false, - } => self.decision_guard(*guard, if_true, if_false), + } => self.decision_guard(*guard, if_true, if_false, false), } } @@ -334,6 +336,17 @@ impl<'a> CasePrinter<'_, '_, 'a, '_> { } } + fn wrap_guard(&mut self, decision: &'a Decision) -> CaseBody<'a> { + match decision { + Decision::Guard { + guard, + if_true, + if_false, + } => self.decision_guard(*guard, if_true, if_false, true), + decision => self.decision(decision), + } + } + fn switch( &mut self, var: &'a Variable, @@ -379,7 +392,7 @@ impl<'a> CasePrinter<'_, '_, 'a, '_> { let (check_doc, body, mut segment_assignments) = self.inside_new_scope(|this| { let segment_assignments = this.variables.bit_array_segment_assignments(check); let check_doc = this.variables.runtime_check(var, check); - let body = this.decision(decision); + let body = this.wrap_guard(decision); (check_doc, body, segment_assignments) }); assignments.append(&mut segment_assignments); @@ -563,6 +576,7 @@ impl<'a> CasePrinter<'_, '_, 'a, '_> { guard: usize, if_true: &'a Body, if_false: &'a Decision, + wrap: bool, ) -> CaseBody<'a> { let DecisionKind::Case { clauses } = &self.kind else { unreachable!("Guards cannot appear in let assert decision trees") @@ -588,7 +602,16 @@ impl<'a> CasePrinter<'_, '_, 'a, '_> { // check_bindings and if_true generation have to be in this scope so that pattern-bound // variables used in guards don't leak into other case branches (if_false). let check_bindings = this.variables.bindings_ref_doc(&check_bindings); - let check = this.variables.expression_generator.guard(guard); + let check = if wrap { + match guard { + guard @ (TypedClauseGuard::Or { .. } | TypedClauseGuard::And { .. }) => { + docvec!["(", this.variables.expression_generator.guard(guard), ")"] + } + guard => this.variables.expression_generator.guard(guard), + } + } else { + this.variables.expression_generator.guard(guard) + }; // All the other bindings that are not needed by the guard check will // end up directly in the body of the if clause. let if_true_bindings = this.variables.bindings_ref_doc(&if_true_bindings); diff --git a/compiler-core/src/javascript/tests/case_clause_guards.rs b/compiler-core/src/javascript/tests/case_clause_guards.rs index 21ca29ed0ae..4cd46aa70fa 100644 --- a/compiler-core/src/javascript/tests/case_clause_guards.rs +++ b/compiler-core/src/javascript/tests/case_clause_guards.rs @@ -622,3 +622,23 @@ pub fn main() { " ); } + +// https://github.com/gleam-lang/gleam/issues/5254 +#[test] +fn logical_or() { + assert_js!( + " + pub fn main() { + let a = False + let b = [] + let c = True + + let r = case c == True { + False if a || b == [] -> \"1\" + _else -> \"0\" + } + assert r == \"0\" + } + " + ) +} diff --git a/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case__case_branches_guards_are_wrapped_in_parentheses.snap b/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case__case_branches_guards_are_wrapped_in_parentheses.snap index 9f301d8ccdc..b34442e81e1 100644 --- a/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case__case_branches_guards_are_wrapped_in_parentheses.snap +++ b/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case__case_branches_guards_are_wrapped_in_parentheses.snap @@ -20,7 +20,7 @@ export function anything() { let $ = toList([]); if (!($ instanceof $Empty)) { let $1 = $.tail; - if ($1 instanceof $Empty && false || true) { + if ($1 instanceof $Empty && (false || true)) { let a = $.head; return a; } diff --git a/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case_clause_guards__logical_or.snap b/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case_clause_guards__logical_or.snap new file mode 100644 index 00000000000..afa078598ba --- /dev/null +++ b/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case_clause_guards__logical_or.snap @@ -0,0 +1,58 @@ +--- +source: compiler-core/src/javascript/tests/case_clause_guards.rs +expression: "\n pub fn main() {\n let a = False\n let b = []\n let c = True\n\n let r = case c == True {\n False if a || b == [] -> \"1\"\n _else -> \"0\"\n }\n assert r == \"0\"\n }\n " +--- +----- SOURCE CODE + + pub fn main() { + let a = False + let b = [] + let c = True + + let r = case c == True { + False if a || b == [] -> "1" + _else -> "0" + } + assert r == "0" + } + + +----- COMPILED JAVASCRIPT +import { toList, makeError, isEqual } from "../gleam.mjs"; + +const FILEPATH = "src/module.gleam"; + +export function main() { + let a = false; + let b = toList([]); + let c = true; + let _block; + let $ = c === true; + if (!$ && (a || (isEqual(b, toList([]))))) { + _block = "1"; + } else { + _block = "0"; + } + let r = _block; + let $1 = "0"; + if (!(r === $1)) { + throw makeError( + "assert", + FILEPATH, + "my/mod", + 11, + "main", + "Assertion failed.", + { + kind: "binary_operator", + operator: "==", + left: { kind: "expression", value: r, start: 224, end: 225 }, + right: { kind: "literal", value: $1, start: 229, end: 232 }, + start: 217, + end: 232, + expression_start: 224 + } + ) + } + return undefined; +}