From 1a5ce6265a0d652440ec17ea46e3e993cb62c967 Mon Sep 17 00:00:00 2001 From: Nafi Date: Wed, 10 Sep 2025 17:16:10 +0530 Subject: [PATCH 01/12] Optimise comparison with singleton custom types on JavaScript #4903. also fixed some clippy errors --- compiler-core/src/analyse.rs | 3 +- compiler-core/src/ast.rs | 6 - compiler-core/src/erlang.rs | 2 +- compiler-core/src/javascript/expression.rs | 51 ++++++++ .../singleton_comparison_optimisation.rs | 120 ++++++++++++++++++ compiler-core/src/type_.rs | 13 +- compiler-core/src/type_/expression.rs | 4 +- 7 files changed, 181 insertions(+), 18 deletions(-) create mode 100644 compiler-core/src/javascript/tests/singleton_comparison_optimisation.rs diff --git a/compiler-core/src/analyse.rs b/compiler-core/src/analyse.rs index 02d8aff4c33..af2f6e5ec06 100644 --- a/compiler-core/src/analyse.rs +++ b/compiler-core/src/analyse.rs @@ -51,9 +51,10 @@ use vec1::Vec1; use self::imports::Importer; -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Default)] pub enum Inferred { Known(T), + #[default] Unknown, } diff --git a/compiler-core/src/ast.rs b/compiler-core/src/ast.rs index a02afc3ff48..d2e50ff7023 100644 --- a/compiler-core/src/ast.rs +++ b/compiler-core/src/ast.rs @@ -2473,12 +2473,6 @@ impl BitArraySize { } } -impl Default for Inferred<()> { - fn default() -> Self { - Self::Unknown - } -} - #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum AssignName { Variable(EcoString), diff --git a/compiler-core/src/erlang.rs b/compiler-core/src/erlang.rs index 49524e4ac4a..d23044cea30 100644 --- a/compiler-core/src/erlang.rs +++ b/compiler-core/src/erlang.rs @@ -708,7 +708,7 @@ fn string_inner(value: &str) -> Document<'_> { .replace_all(value, |caps: &Captures<'_>| { let slashes = caps.get(1).map_or("", |m| m.as_str()); - if slashes.len() % 2 == 0 { + if slashes.len().is_multiple_of(2) { format!("{slashes}u") } else { format!("{slashes}x") diff --git a/compiler-core/src/javascript/expression.rs b/compiler-core/src/javascript/expression.rs index 25e345fefb2..64cb89c2e82 100644 --- a/compiler-core/src/javascript/expression.rs +++ b/compiler-core/src/javascript/expression.rs @@ -1536,11 +1536,62 @@ impl<'module, 'a> Generator<'module, 'a> { return docvec![left_doc, operator, right_doc]; } + // Optimise comparison with singleton custom types on JavaScript (https://gleam-lang/gleam/issues/4903) + if let ( + _, + // RHS: singleton record constructor (arity 0) + TypedExpr::Var { + constructor: + ValueConstructor { + variant: ValueConstructorVariant::Record { arity: 0, name, .. }, + .. + }, + .. + }, + ) = (left, right) + { + let left_doc = self + .not_in_tail_position(Some(Ordering::Strict), |this| this.wrap_expression(left)); + + let constructor_name = name.to_doc(); + + if should_be_equal { + return docvec![left_doc, " instanceof ", constructor_name]; + } else { + return docvec!["!(", left_doc, " instanceof ", constructor_name, ")"]; + } + } + + if let ( + // LHS: singleton record constructor (arity 0) + TypedExpr::Var { + constructor: + ValueConstructor { + variant: ValueConstructorVariant::Record { arity: 0, name, .. }, + .. + }, + .. + }, + _, + ) = (left, right) + { + let right_doc = self + .not_in_tail_position(Some(Ordering::Strict), |this| this.wrap_expression(right)); + let constructor_name = name.to_doc(); + + if should_be_equal { + return docvec![right_doc, " instanceof ", constructor_name]; + } else { + return docvec!["!(", right_doc, " instanceof ", constructor_name, ")"]; + } + } + // Other types must be compared using structural equality let left = self.not_in_tail_position(Some(Ordering::Strict), |this| this.wrap_expression(left)); let right = self.not_in_tail_position(Some(Ordering::Strict), |this| this.wrap_expression(right)); + self.prelude_equal_call(should_be_equal, left, right) } diff --git a/compiler-core/src/javascript/tests/singleton_comparison_optimisation.rs b/compiler-core/src/javascript/tests/singleton_comparison_optimisation.rs new file mode 100644 index 00000000000..4e29c6d1fcb --- /dev/null +++ b/compiler-core/src/javascript/tests/singleton_comparison_optimisation.rs @@ -0,0 +1,120 @@ +use crate::assert_js; + +#[test] +fn singleton_record_equality() { + assert_js!( + r#" +pub type Wibble { + Wibble + Wobble +} + +pub fn is_wibble(w: Wibble) -> Bool { + w == Wibble +} +"#, + ); +} + +#[test] +fn singleton_record_inequality() { + assert_js!( + r#" +pub type Wibble { + Wibble + Wobble +} + +pub fn is_not_wibble(w: Wibble) -> Bool { + w != Wibble +} +"#, + ); +} + +#[test] +fn singleton_record_reverse_order() { + assert_js!( + r#" +pub type Wibble { + Wibble + Wobble +} + +pub fn is_wibble_reverse(w: Wibble) -> Bool { + Wibble == w +} +"#, + ); +} + +#[test] +fn non_singleton_record_equality() { + assert_js!( + r#" +pub type Person { + Person(name: String, age: Int) +} + +pub fn same_person(p1: Person, p2: Person) -> Bool { + p1 == p2 +} +"#, + ); +} + +#[test] +fn multiple_singleton_constructors() { + assert_js!( + r#" +pub type Status { + Loading + Success + Error +} + +pub fn is_loading(s: Status) -> Bool { + s == Loading +} + +pub fn is_success(s: Status) -> Bool { + s == Success +} +"#, + ); +} + +#[test] +fn mixed_singleton_and_non_singleton() { + assert_js!( + r#" +pub type Result { + Ok(value: Int) + Error +} + +pub fn is_error(r: Result) -> Bool { + r == Error +} +"#, + ); +} + +#[test] +fn singleton_in_case_guard() { + assert_js!( + r#" +pub type State { + Active + Inactive +} + +pub fn process(s: State) -> String { + case s { + state if state == Active -> "active" + _ -> "inactive" + } +} +"#, + ); +} diff --git a/compiler-core/src/type_.rs b/compiler-core/src/type_.rs index 080522f15db..ec32881c151 100644 --- a/compiler-core/src/type_.rs +++ b/compiler-core/src/type_.rs @@ -1375,10 +1375,13 @@ pub struct ValueConstructor { pub type_: Arc, } -#[derive(Debug, Clone, PartialEq, Eq, Serialize)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Default)] pub enum Deprecation { + #[default] NotDeprecated, - Deprecated { message: EcoString }, + Deprecated { + message: EcoString, + }, } impl Deprecation { @@ -1391,12 +1394,6 @@ impl Deprecation { } } -impl Default for Deprecation { - fn default() -> Self { - Self::NotDeprecated - } -} - impl ValueConstructor { pub fn local_variable(location: SrcSpan, origin: VariableOrigin, type_: Arc) -> Self { Self { diff --git a/compiler-core/src/type_/expression.rs b/compiler-core/src/type_/expression.rs index 4239fdb3aed..783865d8375 100644 --- a/compiler-core/src/type_/expression.rs +++ b/compiler-core/src/type_/expression.rs @@ -924,7 +924,7 @@ impl<'a, 'b> ExprTyper<'a, 'b> { // !!!True // we can remove all but one negation. // ``` if negations > 1 { - let location = if negations % 2 == 0 { + let location = if negations.is_multiple_of(2) { SrcSpan { start: starting_location.start, end: location.start + 1, @@ -1009,7 +1009,7 @@ impl<'a, 'b> ExprTyper<'a, 'b> { // ---1 // we can remove all but one negation. // ``` if negations > 1 { - let location = if negations % 2 == 0 { + let location = if negations.is_multiple_of(2) { SrcSpan { start: starting_location.start, end: end + 1, From c0bc2f31b18b596a59c688ad126928be8203894d Mon Sep 17 00:00:00 2001 From: Nafi Date: Wed, 10 Sep 2025 20:12:02 +0530 Subject: [PATCH 02/12] implemented optimizations for guard clauses as well. added proper tests for the new code --- compiler-core/src/javascript/expression.rs | 105 ++++++++++++--- .../src/javascript/tests/custom_types.rs | 119 +++++++++++++++++ .../singleton_comparison_optimisation.rs | 120 ------------------ ...type_constructor_imported_and_aliased.snap | 5 +- ...se_clause_guards__imported_aliased_ok.snap | 6 +- ...es__mixed_singleton_and_non_singleton.snap | 31 +++++ ...ypes__multiple_singleton_constructors.snap | 37 ++++++ ..._types__non_singleton_record_equality.snap | 29 +++++ ...custom_types__singleton_in_case_guard.snap | 34 +++++ ...stom_types__singleton_record_equality.snap | 26 ++++ ...om_types__singleton_record_inequality.snap | 26 ++++ ...types__singleton_record_reverse_order.snap | 26 ++++ 12 files changed, 416 insertions(+), 148 deletions(-) delete mode 100644 compiler-core/src/javascript/tests/singleton_comparison_optimisation.rs create mode 100644 compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__mixed_singleton_and_non_singleton.snap create mode 100644 compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__multiple_singleton_constructors.snap create mode 100644 compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__non_singleton_record_equality.snap create mode 100644 compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__singleton_in_case_guard.snap create mode 100644 compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__singleton_record_equality.snap create mode 100644 compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__singleton_record_inequality.snap create mode 100644 compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__singleton_record_reverse_order.snap diff --git a/compiler-core/src/javascript/expression.rs b/compiler-core/src/javascript/expression.rs index 64cb89c2e82..87263da8910 100644 --- a/compiler-core/src/javascript/expression.rs +++ b/compiler-core/src/javascript/expression.rs @@ -1536,7 +1536,22 @@ impl<'module, 'a> Generator<'module, 'a> { return docvec![left_doc, operator, right_doc]; } - // Optimise comparison with singleton custom types on JavaScript (https://gleam-lang/gleam/issues/4903) + // For comparison with singleton custom types, ie, one with no fields. + // If you have some code like this + // ```gleam + // pub type Wibble { + // Wibble + // Wobble + // } + + // pub fn is_wibble(w: Wibble) -> Bool { + // w == Wibble + // } + // ``` + // Instead of `isEqual(w, new Wibble())`, generate `w instanceof Wibble` + // because the first approach needs to construct a new Wibble, and then call the isEqual function, + // which supports any shape of data, and so does a lot of extra logic which isn't necessary. + if let ( _, // RHS: singleton record constructor (arity 0) @@ -1550,16 +1565,7 @@ impl<'module, 'a> Generator<'module, 'a> { }, ) = (left, right) { - let left_doc = self - .not_in_tail_position(Some(Ordering::Strict), |this| this.wrap_expression(left)); - - let constructor_name = name.to_doc(); - - if should_be_equal { - return docvec![left_doc, " instanceof ", constructor_name]; - } else { - return docvec!["!(", left_doc, " instanceof ", constructor_name, ")"]; - } + return self.singleton_equal_helper(left, name, should_be_equal); } if let ( @@ -1575,15 +1581,7 @@ impl<'module, 'a> Generator<'module, 'a> { _, ) = (left, right) { - let right_doc = self - .not_in_tail_position(Some(Ordering::Strict), |this| this.wrap_expression(right)); - let constructor_name = name.to_doc(); - - if should_be_equal { - return docvec![right_doc, " instanceof ", constructor_name]; - } else { - return docvec!["!(", right_doc, " instanceof ", constructor_name, ")"]; - } + return self.singleton_equal_helper(right, name, should_be_equal); } // Other types must be compared using structural equality @@ -1595,6 +1593,23 @@ impl<'module, 'a> Generator<'module, 'a> { self.prelude_equal_call(should_be_equal, left, right) } + fn singleton_equal_helper( + &mut self, + expr: &'a TypedExpr, + name: &EcoString, + should_be_equal: bool, + ) -> Document<'a> { + let expr_doc = + self.not_in_tail_position(Some(Ordering::Strict), |this| this.wrap_expression(expr)); + let constructor_name = name.to_doc(); + + if should_be_equal { + docvec![expr_doc, " instanceof ", constructor_name] + } else { + docvec!["!(", expr_doc, " instanceof ", constructor_name, ")"] + } + } + fn equal_with_doc_operands( &mut self, left: Document<'a>, @@ -2018,12 +2033,62 @@ impl<'module, 'a> Generator<'module, 'a> { } ClauseGuard::Equals { left, right, .. } => { + if let ( + _, + ClauseGuard::Constant(Constant::Record { + arguments, name, .. + }), + // Check if it's a singleton (no arguments) + ) = (&**left, &**right) + && arguments.is_empty() + { + let left_doc = self.guard(left); + return docvec![left_doc, " instanceof ", name.to_doc()]; + } + + if let ( + ClauseGuard::Constant(Constant::Record { + arguments, name, .. + }), + _, + // Check if it's a singleton (no arguments) + ) = (&**left, &**right) + && arguments.is_empty() + { + let right_doc = self.guard(right); + return docvec![right_doc, " instanceof ", name.to_doc()]; + } + let left = self.guard(left); let right = self.guard(right); self.prelude_equal_call(true, left, right) } ClauseGuard::NotEquals { left, right, .. } => { + if let ( + _, + ClauseGuard::Constant(Constant::Record { + arguments, name, .. + }), + ) = (&**left, &**right) + && arguments.is_empty() + { + let left_doc = self.guard(left); + return docvec!["!(", left_doc, " instanceof ", name.to_doc(), ")"]; + } + + if let ( + ClauseGuard::Constant(Constant::Record { + arguments, name, .. + }), + _, + ) = (&**left, &**right) + && arguments.is_empty() + { + let right_doc = self.guard(right); + return docvec!["!(", right_doc, " instanceof ", name.to_doc(), ")"]; + } + let left = self.guard(left); let right = self.guard(right); self.prelude_equal_call(false, left, right) diff --git a/compiler-core/src/javascript/tests/custom_types.rs b/compiler-core/src/javascript/tests/custom_types.rs index 2fcecfef056..459b8d80057 100644 --- a/compiler-core/src/javascript/tests/custom_types.rs +++ b/compiler-core/src/javascript/tests/custom_types.rs @@ -687,3 +687,122 @@ pub type Wibble { "# ); } + +#[test] +fn singleton_record_equality() { + assert_js!( + r#" +pub type Wibble { + Wibble + Wobble +} + +pub fn is_wibble(w: Wibble) -> Bool { + w == Wibble +} +"#, + ); +} + +#[test] +fn singleton_record_inequality() { + assert_js!( + r#" +pub type Wibble { + Wibble + Wobble +} + +pub fn is_not_wibble(w: Wibble) -> Bool { + w != Wibble +} +"#, + ); +} + +#[test] +fn singleton_record_reverse_order() { + assert_js!( + r#" +pub type Wibble { + Wibble + Wobble +} + +pub fn is_wibble_reverse(w: Wibble) -> Bool { + Wibble == w +} +"#, + ); +} + +#[test] +fn non_singleton_record_equality() { + assert_js!( + r#" +pub type Person { + Person(name: String, age: Int) +} + +pub fn same_person(p1: Person, p2: Person) -> Bool { + p1 == p2 +} +"#, + ); +} + +#[test] +fn multiple_singleton_constructors() { + assert_js!( + r#" +pub type Status { + Loading + Success + Error +} + +pub fn is_loading(s: Status) -> Bool { + s == Loading +} + +pub fn is_success(s: Status) -> Bool { + s == Success +} +"#, + ); +} + +#[test] +fn mixed_singleton_and_non_singleton() { + assert_js!( + r#" +pub type Result { + Ok(value: Int) + Error +} + +pub fn is_error(r: Result) -> Bool { + r == Error +} +"#, + ); +} + +#[test] +fn singleton_in_case_guard() { + assert_js!( + r#" +pub type State { + Active + Inactive +} + +pub fn process(s: State) -> String { + case s { + state if state == Active -> "active" + _ -> "inactive" + } +} +"#, + ); +} diff --git a/compiler-core/src/javascript/tests/singleton_comparison_optimisation.rs b/compiler-core/src/javascript/tests/singleton_comparison_optimisation.rs deleted file mode 100644 index 4e29c6d1fcb..00000000000 --- a/compiler-core/src/javascript/tests/singleton_comparison_optimisation.rs +++ /dev/null @@ -1,120 +0,0 @@ -use crate::assert_js; - -#[test] -fn singleton_record_equality() { - assert_js!( - r#" -pub type Wibble { - Wibble - Wobble -} - -pub fn is_wibble(w: Wibble) -> Bool { - w == Wibble -} -"#, - ); -} - -#[test] -fn singleton_record_inequality() { - assert_js!( - r#" -pub type Wibble { - Wibble - Wobble -} - -pub fn is_not_wibble(w: Wibble) -> Bool { - w != Wibble -} -"#, - ); -} - -#[test] -fn singleton_record_reverse_order() { - assert_js!( - r#" -pub type Wibble { - Wibble - Wobble -} - -pub fn is_wibble_reverse(w: Wibble) -> Bool { - Wibble == w -} -"#, - ); -} - -#[test] -fn non_singleton_record_equality() { - assert_js!( - r#" -pub type Person { - Person(name: String, age: Int) -} - -pub fn same_person(p1: Person, p2: Person) -> Bool { - p1 == p2 -} -"#, - ); -} - -#[test] -fn multiple_singleton_constructors() { - assert_js!( - r#" -pub type Status { - Loading - Success - Error -} - -pub fn is_loading(s: Status) -> Bool { - s == Loading -} - -pub fn is_success(s: Status) -> Bool { - s == Success -} -"#, - ); -} - -#[test] -fn mixed_singleton_and_non_singleton() { - assert_js!( - r#" -pub type Result { - Ok(value: Int) - Error -} - -pub fn is_error(r: Result) -> Bool { - r == Error -} -"#, - ); -} - -#[test] -fn singleton_in_case_guard() { - assert_js!( - r#" -pub type State { - Active - Inactive -} - -pub fn process(s: State) -> String { - case s { - state if state == Active -> "active" - _ -> "inactive" - } -} -"#, - ); -} diff --git a/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case_clause_guards__custom_type_constructor_imported_and_aliased.snap b/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case_clause_guards__custom_type_constructor_imported_and_aliased.snap index 002d9a563b8..33963a20fb3 100644 --- a/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case_clause_guards__custom_type_constructor_imported_and_aliased.snap +++ b/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case_clause_guards__custom_type_constructor_imported_and_aliased.snap @@ -1,8 +1,6 @@ --- source: compiler-core/src/javascript/tests/case_clause_guards.rs -assertion_line: 474 expression: "import other_module.{A as B}\npub fn func() {\n case B {\n x if x == B -> True\n _ -> False\n }\n}\n" -snapshot_kind: text --- ----- SOURCE CODE import other_module.{A as B} @@ -17,12 +15,11 @@ pub fn func() { ----- COMPILED JAVASCRIPT import * as $other_module from "../../package/other_module.mjs"; import { A as B } from "../../package/other_module.mjs"; -import { isEqual } from "../gleam.mjs"; export function func() { let $ = new B(); let x = $; - if (isEqual(x, new B())) { + if (x instanceof B) { return true; } else { return false; diff --git a/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case_clause_guards__imported_aliased_ok.snap b/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case_clause_guards__imported_aliased_ok.snap index e34d96e85e8..285cc93783a 100644 --- a/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case_clause_guards__imported_aliased_ok.snap +++ b/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case_clause_guards__imported_aliased_ok.snap @@ -1,8 +1,6 @@ --- source: compiler-core/src/javascript/tests/case_clause_guards.rs -assertion_line: 489 expression: "import gleam.{Ok as Y}\npub type X {\n Ok\n}\npub fn func() {\n case Y {\n y if y == Y -> True\n _ -> False\n }\n}\n" -snapshot_kind: text --- ----- SOURCE CODE import gleam.{Ok as Y} @@ -19,14 +17,14 @@ pub fn func() { ----- COMPILED JAVASCRIPT import * as $gleam from "../gleam.mjs"; -import { Ok as Y, CustomType as $CustomType, isEqual } from "../gleam.mjs"; +import { Ok as Y, CustomType as $CustomType } from "../gleam.mjs"; export class Ok extends $CustomType {} export function func() { let $ = (var0) => { return new Y(var0); }; let y = $; - if (isEqual(y, (var0) => { return new Y(var0); })) { + if (y instanceof Y) { return true; } else { return false; diff --git a/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__mixed_singleton_and_non_singleton.snap b/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__mixed_singleton_and_non_singleton.snap new file mode 100644 index 00000000000..d63c98b7ea0 --- /dev/null +++ b/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__mixed_singleton_and_non_singleton.snap @@ -0,0 +1,31 @@ +--- +source: compiler-core/src/javascript/tests/custom_types.rs +expression: "\npub type Result {\n Ok(value: Int)\n Error\n}\n\npub fn is_error(r: Result) -> Bool {\n r == Error\n}\n" +--- +----- SOURCE CODE + +pub type Result { + Ok(value: Int) + Error +} + +pub fn is_error(r: Result) -> Bool { + r == Error +} + + +----- COMPILED JAVASCRIPT +import { CustomType as $CustomType } from "../gleam.mjs"; + +export class Ok extends $CustomType { + constructor(value) { + super(); + this.value = value; + } +} + +export class Error extends $CustomType {} + +export function is_error(r) { + return r instanceof Error; +} diff --git a/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__multiple_singleton_constructors.snap b/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__multiple_singleton_constructors.snap new file mode 100644 index 00000000000..95fae46f222 --- /dev/null +++ b/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__multiple_singleton_constructors.snap @@ -0,0 +1,37 @@ +--- +source: compiler-core/src/javascript/tests/custom_types.rs +expression: "\npub type Status {\n Loading\n Success\n Error\n}\n\npub fn is_loading(s: Status) -> Bool {\n s == Loading\n}\n\npub fn is_success(s: Status) -> Bool {\n s == Success\n}\n" +--- +----- SOURCE CODE + +pub type Status { + Loading + Success + Error +} + +pub fn is_loading(s: Status) -> Bool { + s == Loading +} + +pub fn is_success(s: Status) -> Bool { + s == Success +} + + +----- COMPILED JAVASCRIPT +import { CustomType as $CustomType } from "../gleam.mjs"; + +export class Loading extends $CustomType {} + +export class Success extends $CustomType {} + +export class Error extends $CustomType {} + +export function is_loading(s) { + return s instanceof Loading; +} + +export function is_success(s) { + return s instanceof Success; +} diff --git a/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__non_singleton_record_equality.snap b/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__non_singleton_record_equality.snap new file mode 100644 index 00000000000..78757f96e62 --- /dev/null +++ b/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__non_singleton_record_equality.snap @@ -0,0 +1,29 @@ +--- +source: compiler-core/src/javascript/tests/custom_types.rs +expression: "\npub type Person {\n Person(name: String, age: Int)\n}\n\npub fn same_person(p1: Person, p2: Person) -> Bool {\n p1 == p2\n}\n" +--- +----- SOURCE CODE + +pub type Person { + Person(name: String, age: Int) +} + +pub fn same_person(p1: Person, p2: Person) -> Bool { + p1 == p2 +} + + +----- COMPILED JAVASCRIPT +import { CustomType as $CustomType, isEqual } from "../gleam.mjs"; + +export class Person extends $CustomType { + constructor(name, age) { + super(); + this.name = name; + this.age = age; + } +} + +export function same_person(p1, p2) { + return isEqual(p1, p2); +} diff --git a/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__singleton_in_case_guard.snap b/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__singleton_in_case_guard.snap new file mode 100644 index 00000000000..65ff0ca2a1a --- /dev/null +++ b/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__singleton_in_case_guard.snap @@ -0,0 +1,34 @@ +--- +source: compiler-core/src/javascript/tests/custom_types.rs +expression: "\npub type State {\n Active\n Inactive\n}\n\npub fn process(s: State) -> String {\n case s {\n state if state == Active -> \"active\"\n _ -> \"inactive\"\n }\n}\n" +--- +----- SOURCE CODE + +pub type State { + Active + Inactive +} + +pub fn process(s: State) -> String { + case s { + state if state == Active -> "active" + _ -> "inactive" + } +} + + +----- COMPILED JAVASCRIPT +import { CustomType as $CustomType } from "../gleam.mjs"; + +export class Active extends $CustomType {} + +export class Inactive extends $CustomType {} + +export function process(s) { + let state = s; + if (state instanceof Active) { + return "active"; + } else { + return "inactive"; + } +} diff --git a/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__singleton_record_equality.snap b/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__singleton_record_equality.snap new file mode 100644 index 00000000000..7f6c37a21af --- /dev/null +++ b/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__singleton_record_equality.snap @@ -0,0 +1,26 @@ +--- +source: compiler-core/src/javascript/tests/custom_types.rs +expression: "\npub type Wibble {\n Wibble\n Wobble\n}\n\npub fn is_wibble(w: Wibble) -> Bool {\n w == Wibble\n}\n" +--- +----- SOURCE CODE + +pub type Wibble { + Wibble + Wobble +} + +pub fn is_wibble(w: Wibble) -> Bool { + w == Wibble +} + + +----- COMPILED JAVASCRIPT +import { CustomType as $CustomType } from "../gleam.mjs"; + +export class Wibble extends $CustomType {} + +export class Wobble extends $CustomType {} + +export function is_wibble(w) { + return w instanceof Wibble; +} diff --git a/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__singleton_record_inequality.snap b/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__singleton_record_inequality.snap new file mode 100644 index 00000000000..02823f89d3f --- /dev/null +++ b/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__singleton_record_inequality.snap @@ -0,0 +1,26 @@ +--- +source: compiler-core/src/javascript/tests/custom_types.rs +expression: "\npub type Wibble {\n Wibble\n Wobble\n}\n\npub fn is_not_wibble(w: Wibble) -> Bool {\n w != Wibble\n}\n" +--- +----- SOURCE CODE + +pub type Wibble { + Wibble + Wobble +} + +pub fn is_not_wibble(w: Wibble) -> Bool { + w != Wibble +} + + +----- COMPILED JAVASCRIPT +import { CustomType as $CustomType } from "../gleam.mjs"; + +export class Wibble extends $CustomType {} + +export class Wobble extends $CustomType {} + +export function is_not_wibble(w) { + return !(w instanceof Wibble); +} diff --git a/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__singleton_record_reverse_order.snap b/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__singleton_record_reverse_order.snap new file mode 100644 index 00000000000..8558926e5a2 --- /dev/null +++ b/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__singleton_record_reverse_order.snap @@ -0,0 +1,26 @@ +--- +source: compiler-core/src/javascript/tests/custom_types.rs +expression: "\npub type Wibble {\n Wibble\n Wobble\n}\n\npub fn is_wibble_reverse(w: Wibble) -> Bool {\n Wibble == w\n}\n" +--- +----- SOURCE CODE + +pub type Wibble { + Wibble + Wobble +} + +pub fn is_wibble_reverse(w: Wibble) -> Bool { + Wibble == w +} + + +----- COMPILED JAVASCRIPT +import { CustomType as $CustomType } from "../gleam.mjs"; + +export class Wibble extends $CustomType {} + +export class Wobble extends $CustomType {} + +export function is_wibble_reverse(w) { + return w instanceof Wibble; +} From 1a7329b80573a02d6c3e8152f0d52b3b584b65fe Mon Sep 17 00:00:00 2001 From: Nafi Date: Thu, 11 Sep 2025 02:50:51 +0530 Subject: [PATCH 03/12] PLUH --- compiler-core/src/javascript/expression.rs | 144 ++++++++---------- ..._constructor_imported_and_aliased.snap.new | 30 ++++ ...lause_guards__imported_aliased_ok.snap.new | 34 +++++ ...om_types__singleton_in_case_guard.snap.new | 36 +++++ 4 files changed, 160 insertions(+), 84 deletions(-) create mode 100644 compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case_clause_guards__custom_type_constructor_imported_and_aliased.snap.new create mode 100644 compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case_clause_guards__imported_aliased_ok.snap.new create mode 100644 compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__singleton_in_case_guard.snap.new diff --git a/compiler-core/src/javascript/expression.rs b/compiler-core/src/javascript/expression.rs index 87263da8910..e7a52d4c924 100644 --- a/compiler-core/src/javascript/expression.rs +++ b/compiler-core/src/javascript/expression.rs @@ -178,6 +178,18 @@ pub(crate) struct Generator<'module, 'ast> { pub let_assert_always_panics: bool, } +fn is_ctor_value(g: &TypedClauseGuard) -> Option { + match &*g { + ClauseGuard::Constant(Constant::Record { arguments, name, .. }) + if arguments.is_empty() => Some(name.clone()), + _ => None, + } +} + +fn is_var(g: &TypedClauseGuard) -> bool { + matches!(&*g, ClauseGuard::Var { .. }) +} + impl<'module, 'a> Generator<'module, 'a> { #[allow(clippy::too_many_arguments)] // TODO: FIXME pub fn new( @@ -1552,34 +1564,28 @@ impl<'module, 'a> Generator<'module, 'a> { // because the first approach needs to construct a new Wibble, and then call the isEqual function, // which supports any shape of data, and so does a lot of extra logic which isn't necessary. - if let ( - _, - // RHS: singleton record constructor (arity 0) - TypedExpr::Var { - constructor: - ValueConstructor { - variant: ValueConstructorVariant::Record { arity: 0, name, .. }, - .. - }, - .. - }, - ) = (left, right) + // RHS: singleton record constructor (arity 0) + if let TypedExpr::Var { + constructor: + ValueConstructor { + variant: ValueConstructorVariant::Record { arity: 0, name, .. }, + .. + }, + .. + } = right { return self.singleton_equal_helper(left, name, should_be_equal); } - if let ( - // LHS: singleton record constructor (arity 0) - TypedExpr::Var { - constructor: - ValueConstructor { - variant: ValueConstructorVariant::Record { arity: 0, name, .. }, - .. - }, - .. - }, - _, - ) = (left, right) + // LHS: singleton record constructor (arity 0) + if let TypedExpr::Var { + constructor: + ValueConstructor { + variant: ValueConstructorVariant::Record { arity: 0, name, .. }, + .. + }, + .. + } = left { return self.singleton_equal_helper(right, name, should_be_equal); } @@ -2032,66 +2038,36 @@ impl<'module, 'a> Generator<'module, 'a> { docvec![left, " !== ", right] } - ClauseGuard::Equals { left, right, .. } => { - if let ( - _, - ClauseGuard::Constant(Constant::Record { - arguments, name, .. - }), - // Check if it's a singleton (no arguments) - ) = (&**left, &**right) - && arguments.is_empty() - { - let left_doc = self.guard(left); - return docvec![left_doc, " instanceof ", name.to_doc()]; - } - - if let ( - ClauseGuard::Constant(Constant::Record { - arguments, name, .. - }), - _, - // Check if it's a singleton (no arguments) - ) = (&**left, &**right) - && arguments.is_empty() - { - let right_doc = self.guard(right); - return docvec![right_doc, " instanceof ", name.to_doc()]; - } - - let left = self.guard(left); - let right = self.guard(right); - self.prelude_equal_call(true, left, right) - } - - ClauseGuard::NotEquals { left, right, .. } => { - if let ( - _, - ClauseGuard::Constant(Constant::Record { - arguments, name, .. - }), - ) = (&**left, &**right) - && arguments.is_empty() - { - let left_doc = self.guard(left); - return docvec!["!(", left_doc, " instanceof ", name.to_doc(), ")"]; - } - - if let ( - ClauseGuard::Constant(Constant::Record { - arguments, name, .. - }), - _, - ) = (&**left, &**right) - && arguments.is_empty() - { - let right_doc = self.guard(right); - return docvec!["!(", right_doc, " instanceof ", name.to_doc(), ")"]; - } - - let left = self.guard(left); - let right = self.guard(right); - self.prelude_equal_call(false, left, right) + ClauseGuard::Equals { left, right, .. } + | ClauseGuard::NotEquals { left, right, .. } => { + let should_be_equal = matches!(guard, ClauseGuard::Equals { .. }); + + // if is_var(right) { + // if let Some(tag) = is_ctor_value(left) { + // let val = self.guard(right); + // return if should_be_equal { + // docvec![val, " instanceof ", tag.to_doc()] + // } else { + // docvec!["!(", val, " instanceof ", tag.to_doc(), ")"] + // }; + // } + // } + + // if is_var(left) { + // if let Some(tag) = is_ctor_value(right) { + // let val = self.guard(left); + // return if should_be_equal { + // docvec![val, " instanceof ", tag.to_doc()] + // } else { + // docvec!["!(", val, " instanceof ", tag.to_doc(), ")"] + // }; + // } + // } + + // Otherwise fallback to normal equality check + let left_doc = self.guard(left); + let right_doc = self.guard(right); + self.prelude_equal_call(should_be_equal, left_doc, right_doc) } ClauseGuard::GtFloat { left, right, .. } | ClauseGuard::GtInt { left, right, .. } => { diff --git a/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case_clause_guards__custom_type_constructor_imported_and_aliased.snap.new b/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case_clause_guards__custom_type_constructor_imported_and_aliased.snap.new new file mode 100644 index 00000000000..002d9a563b8 --- /dev/null +++ b/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case_clause_guards__custom_type_constructor_imported_and_aliased.snap.new @@ -0,0 +1,30 @@ +--- +source: compiler-core/src/javascript/tests/case_clause_guards.rs +assertion_line: 474 +expression: "import other_module.{A as B}\npub fn func() {\n case B {\n x if x == B -> True\n _ -> False\n }\n}\n" +snapshot_kind: text +--- +----- SOURCE CODE +import other_module.{A as B} +pub fn func() { + case B { + x if x == B -> True + _ -> False + } +} + + +----- COMPILED JAVASCRIPT +import * as $other_module from "../../package/other_module.mjs"; +import { A as B } from "../../package/other_module.mjs"; +import { isEqual } from "../gleam.mjs"; + +export function func() { + let $ = new B(); + let x = $; + if (isEqual(x, new B())) { + return true; + } else { + return false; + } +} diff --git a/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case_clause_guards__imported_aliased_ok.snap.new b/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case_clause_guards__imported_aliased_ok.snap.new new file mode 100644 index 00000000000..e34d96e85e8 --- /dev/null +++ b/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case_clause_guards__imported_aliased_ok.snap.new @@ -0,0 +1,34 @@ +--- +source: compiler-core/src/javascript/tests/case_clause_guards.rs +assertion_line: 489 +expression: "import gleam.{Ok as Y}\npub type X {\n Ok\n}\npub fn func() {\n case Y {\n y if y == Y -> True\n _ -> False\n }\n}\n" +snapshot_kind: text +--- +----- SOURCE CODE +import gleam.{Ok as Y} +pub type X { + Ok +} +pub fn func() { + case Y { + y if y == Y -> True + _ -> False + } +} + + +----- COMPILED JAVASCRIPT +import * as $gleam from "../gleam.mjs"; +import { Ok as Y, CustomType as $CustomType, isEqual } from "../gleam.mjs"; + +export class Ok extends $CustomType {} + +export function func() { + let $ = (var0) => { return new Y(var0); }; + let y = $; + if (isEqual(y, (var0) => { return new Y(var0); })) { + return true; + } else { + return false; + } +} diff --git a/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__singleton_in_case_guard.snap.new b/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__singleton_in_case_guard.snap.new new file mode 100644 index 00000000000..a0f6e160878 --- /dev/null +++ b/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__singleton_in_case_guard.snap.new @@ -0,0 +1,36 @@ +--- +source: compiler-core/src/javascript/tests/custom_types.rs +assertion_line: 793 +expression: "\npub type State {\n Active\n Inactive\n}\n\npub fn process(s: State) -> String {\n case s {\n state if state == Active -> \"active\"\n _ -> \"inactive\"\n }\n}\n" +snapshot_kind: text +--- +----- SOURCE CODE + +pub type State { + Active + Inactive +} + +pub fn process(s: State) -> String { + case s { + state if state == Active -> "active" + _ -> "inactive" + } +} + + +----- COMPILED JAVASCRIPT +import { CustomType as $CustomType, isEqual } from "../gleam.mjs"; + +export class Active extends $CustomType {} + +export class Inactive extends $CustomType {} + +export function process(s) { + let state = s; + if (isEqual(state, new Active())) { + return "active"; + } else { + return "inactive"; + } +} From bcd37c29011742a83f1a274c49158dea10bf1dd9 Mon Sep 17 00:00:00 2001 From: Nafi Date: Sun, 14 Sep 2025 19:59:50 +0530 Subject: [PATCH 04/12] finalized optimization for all cases. added more tests. cleaned the code up --- compiler-core/src/javascript/expression.rs | 64 ++++++++----- .../src/javascript/tests/custom_types.rs | 96 +++++++++++++++++++ ..._constructor_imported_and_aliased.snap.new | 30 ------ ...se_clause_guards__imported_aliased_ok.snap | 4 +- ...lause_guards__imported_aliased_ok.snap.new | 34 ------- ...om_types__singleton_in_case_guard.snap.new | 36 ------- ...n_another_module_aliased_clause_guard.snap | 32 +++++++ ..._in_another_module_aliased_expression.snap | 24 +++++ ...another_module_qualified_clause_guard.snap | 31 ++++++ ...n_another_module_qualified_expression.snap | 24 +++++ ...other_module_unqualified_clause_guard.snap | 32 +++++++ ...another_module_unqualified_expression.snap | 24 +++++ 12 files changed, 303 insertions(+), 128 deletions(-) delete mode 100644 compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case_clause_guards__custom_type_constructor_imported_and_aliased.snap.new delete mode 100644 compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case_clause_guards__imported_aliased_ok.snap.new delete mode 100644 compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__singleton_in_case_guard.snap.new create mode 100644 compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__variant_defined_in_another_module_aliased_clause_guard.snap create mode 100644 compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__variant_defined_in_another_module_aliased_expression.snap create mode 100644 compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__variant_defined_in_another_module_qualified_clause_guard.snap create mode 100644 compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__variant_defined_in_another_module_qualified_expression.snap create mode 100644 compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__variant_defined_in_another_module_unqualified_clause_guard.snap create mode 100644 compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__variant_defined_in_another_module_unqualified_expression.snap diff --git a/compiler-core/src/javascript/expression.rs b/compiler-core/src/javascript/expression.rs index e7a52d4c924..721b2df560a 100644 --- a/compiler-core/src/javascript/expression.rs +++ b/compiler-core/src/javascript/expression.rs @@ -178,16 +178,24 @@ pub(crate) struct Generator<'module, 'ast> { pub let_assert_always_panics: bool, } -fn is_ctor_value(g: &TypedClauseGuard) -> Option { - match &*g { - ClauseGuard::Constant(Constant::Record { arguments, name, .. }) - if arguments.is_empty() => Some(name.clone()), +fn ctor_tag(g: &TypedClauseGuard) -> Option { + match g { + ClauseGuard::Constant(Constant::Record { + arguments, name, .. + }) if arguments.is_empty() => Some(name.clone()), _ => None, } } fn is_var(g: &TypedClauseGuard) -> bool { - matches!(&*g, ClauseGuard::Var { .. }) + matches!(g, ClauseGuard::Var { .. }) +} + +fn looks_like_constructor_alias(var: &TypedClauseGuard) -> bool { + match var { + ClauseGuard::Var { type_, .. } => matches!(&**type_, Type::Fn { .. }), // lambda + _ => false, + } } impl<'module, 'a> Generator<'module, 'a> { @@ -2041,28 +2049,32 @@ impl<'module, 'a> Generator<'module, 'a> { ClauseGuard::Equals { left, right, .. } | ClauseGuard::NotEquals { left, right, .. } => { let should_be_equal = matches!(guard, ClauseGuard::Equals { .. }); + let both_ctor_values = ctor_tag(left).is_some() && ctor_tag(right).is_some(); - // if is_var(right) { - // if let Some(tag) = is_ctor_value(left) { - // let val = self.guard(right); - // return if should_be_equal { - // docvec![val, " instanceof ", tag.to_doc()] - // } else { - // docvec!["!(", val, " instanceof ", tag.to_doc(), ")"] - // }; - // } - // } - - // if is_var(left) { - // if let Some(tag) = is_ctor_value(right) { - // let val = self.guard(left); - // return if should_be_equal { - // docvec![val, " instanceof ", tag.to_doc()] - // } else { - // docvec!["!(", val, " instanceof ", tag.to_doc(), ")"] - // }; - // } - // } + if !both_ctor_values { + if is_var(left) + && !looks_like_constructor_alias(left) + && let Some(tag) = ctor_tag(right) + { + let l = self.guard(left); + return if should_be_equal { + docvec![l, " instanceof ", tag.to_doc()] + } else { + docvec!["!(", l, " instanceof ", tag.to_doc(), ")"] + }; + } + if is_var(right) + && !looks_like_constructor_alias(right) + && let Some(tag) = ctor_tag(left) + { + let r = self.guard(right); + return if should_be_equal { + docvec![r, " instanceof ", tag.to_doc()] + } else { + docvec!["!(", r, " instanceof ", tag.to_doc(), ")"] + }; + } + } // Otherwise fallback to normal equality check let left_doc = self.guard(left); diff --git a/compiler-core/src/javascript/tests/custom_types.rs b/compiler-core/src/javascript/tests/custom_types.rs index 459b8d80057..b62607cdc98 100644 --- a/compiler-core/src/javascript/tests/custom_types.rs +++ b/compiler-core/src/javascript/tests/custom_types.rs @@ -806,3 +806,99 @@ pub fn process(s: State) -> String { "#, ); } + +#[test] +fn variant_defined_in_another_module_qualified_expression() { + assert_js!( + ( + "other_module", + r#"pub type Thingy { Variant OtherVariant }"# + ), + r#" +import other_module + +pub fn check(x) -> Bool { + x == other_module.Variant +} +"#, + ); +} + +#[test] +fn variant_defined_in_another_module_unqualified_expression() { + assert_js!( + ("other_module", r#"pub type Thingy { Variant Other(Int) }"#), + r#" +import other_module.{Variant} + +pub fn check(x) -> Bool { + x == Variant +} +"#, + ); +} + +#[test] +fn variant_defined_in_another_module_aliased_expression() { + assert_js!( + ("other_module", r#"pub type Thingy { Variant Other(Int) }"#), + r#" +import other_module.{Variant as Aliased} + +pub fn check(x) -> Bool { + x == Aliased +} +"#, + ); +} + +#[test] +fn variant_defined_in_another_module_qualified_clause_guard() { + assert_js!( + ("other_module", r#"pub type Thingy { Variant Other(Int) }"#), + r#" +import other_module + +pub fn process(e) -> String { + case e { + value if value == other_module.Variant -> "match" + _ -> "no match" + } +} +"#, + ); +} + +#[test] +fn variant_defined_in_another_module_unqualified_clause_guard() { + assert_js!( + ("other_module", r#"pub type Thingy { Variant Other(Int) }"#), + r#" +import other_module.{Variant} + +pub fn process(e) -> String { + case e { + value if value == Variant -> "match" + _ -> "no match" + } +} +"#, + ); +} + +#[test] +fn variant_defined_in_another_module_aliased_clause_guard() { + assert_js!( + ("other_module", r#"pub type Thingy { Variant Other(Int) }"#), + r#" +import other_module.{Variant as Aliased} + +pub fn process(e) -> String { + case e { + value if value == Aliased -> "match" + _ -> "no match" + } +} +"#, + ); +} diff --git a/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case_clause_guards__custom_type_constructor_imported_and_aliased.snap.new b/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case_clause_guards__custom_type_constructor_imported_and_aliased.snap.new deleted file mode 100644 index 002d9a563b8..00000000000 --- a/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case_clause_guards__custom_type_constructor_imported_and_aliased.snap.new +++ /dev/null @@ -1,30 +0,0 @@ ---- -source: compiler-core/src/javascript/tests/case_clause_guards.rs -assertion_line: 474 -expression: "import other_module.{A as B}\npub fn func() {\n case B {\n x if x == B -> True\n _ -> False\n }\n}\n" -snapshot_kind: text ---- ------ SOURCE CODE -import other_module.{A as B} -pub fn func() { - case B { - x if x == B -> True - _ -> False - } -} - - ------ COMPILED JAVASCRIPT -import * as $other_module from "../../package/other_module.mjs"; -import { A as B } from "../../package/other_module.mjs"; -import { isEqual } from "../gleam.mjs"; - -export function func() { - let $ = new B(); - let x = $; - if (isEqual(x, new B())) { - return true; - } else { - return false; - } -} diff --git a/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case_clause_guards__imported_aliased_ok.snap b/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case_clause_guards__imported_aliased_ok.snap index 285cc93783a..7f03a182a43 100644 --- a/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case_clause_guards__imported_aliased_ok.snap +++ b/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case_clause_guards__imported_aliased_ok.snap @@ -17,14 +17,14 @@ pub fn func() { ----- COMPILED JAVASCRIPT import * as $gleam from "../gleam.mjs"; -import { Ok as Y, CustomType as $CustomType } from "../gleam.mjs"; +import { Ok as Y, CustomType as $CustomType, isEqual } from "../gleam.mjs"; export class Ok extends $CustomType {} export function func() { let $ = (var0) => { return new Y(var0); }; let y = $; - if (y instanceof Y) { + if (isEqual(y, (var0) => { return new Y(var0); })) { return true; } else { return false; diff --git a/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case_clause_guards__imported_aliased_ok.snap.new b/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case_clause_guards__imported_aliased_ok.snap.new deleted file mode 100644 index e34d96e85e8..00000000000 --- a/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__case_clause_guards__imported_aliased_ok.snap.new +++ /dev/null @@ -1,34 +0,0 @@ ---- -source: compiler-core/src/javascript/tests/case_clause_guards.rs -assertion_line: 489 -expression: "import gleam.{Ok as Y}\npub type X {\n Ok\n}\npub fn func() {\n case Y {\n y if y == Y -> True\n _ -> False\n }\n}\n" -snapshot_kind: text ---- ------ SOURCE CODE -import gleam.{Ok as Y} -pub type X { - Ok -} -pub fn func() { - case Y { - y if y == Y -> True - _ -> False - } -} - - ------ COMPILED JAVASCRIPT -import * as $gleam from "../gleam.mjs"; -import { Ok as Y, CustomType as $CustomType, isEqual } from "../gleam.mjs"; - -export class Ok extends $CustomType {} - -export function func() { - let $ = (var0) => { return new Y(var0); }; - let y = $; - if (isEqual(y, (var0) => { return new Y(var0); })) { - return true; - } else { - return false; - } -} diff --git a/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__singleton_in_case_guard.snap.new b/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__singleton_in_case_guard.snap.new deleted file mode 100644 index a0f6e160878..00000000000 --- a/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__singleton_in_case_guard.snap.new +++ /dev/null @@ -1,36 +0,0 @@ ---- -source: compiler-core/src/javascript/tests/custom_types.rs -assertion_line: 793 -expression: "\npub type State {\n Active\n Inactive\n}\n\npub fn process(s: State) -> String {\n case s {\n state if state == Active -> \"active\"\n _ -> \"inactive\"\n }\n}\n" -snapshot_kind: text ---- ------ SOURCE CODE - -pub type State { - Active - Inactive -} - -pub fn process(s: State) -> String { - case s { - state if state == Active -> "active" - _ -> "inactive" - } -} - - ------ COMPILED JAVASCRIPT -import { CustomType as $CustomType, isEqual } from "../gleam.mjs"; - -export class Active extends $CustomType {} - -export class Inactive extends $CustomType {} - -export function process(s) { - let state = s; - if (isEqual(state, new Active())) { - return "active"; - } else { - return "inactive"; - } -} diff --git a/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__variant_defined_in_another_module_aliased_clause_guard.snap b/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__variant_defined_in_another_module_aliased_clause_guard.snap new file mode 100644 index 00000000000..eaec979a3f5 --- /dev/null +++ b/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__variant_defined_in_another_module_aliased_clause_guard.snap @@ -0,0 +1,32 @@ +--- +source: compiler-core/src/javascript/tests/custom_types.rs +expression: "\nimport other_module.{Variant as Aliased}\n\npub fn process(e) -> String {\n case e {\n value if value == Aliased -> \"match\"\n _ -> \"no match\"\n }\n}\n" +--- +----- SOURCE CODE +-- other_module.gleam +pub type Thingy { Variant Other(Int) } + +-- main.gleam + +import other_module.{Variant as Aliased} + +pub fn process(e) -> String { + case e { + value if value == Aliased -> "match" + _ -> "no match" + } +} + + +----- COMPILED JAVASCRIPT +import * as $other_module from "../other_module.mjs"; +import { Variant as Aliased } from "../other_module.mjs"; + +export function process(e) { + let value = e; + if (value instanceof Aliased) { + return "match"; + } else { + return "no match"; + } +} diff --git a/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__variant_defined_in_another_module_aliased_expression.snap b/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__variant_defined_in_another_module_aliased_expression.snap new file mode 100644 index 00000000000..17e25679264 --- /dev/null +++ b/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__variant_defined_in_another_module_aliased_expression.snap @@ -0,0 +1,24 @@ +--- +source: compiler-core/src/javascript/tests/custom_types.rs +expression: "\nimport other_module.{Variant as Aliased}\n\npub fn check(x) -> Bool {\n x == Aliased\n}\n" +--- +----- SOURCE CODE +-- other_module.gleam +pub type Thingy { Variant Other(Int) } + +-- main.gleam + +import other_module.{Variant as Aliased} + +pub fn check(x) -> Bool { + x == Aliased +} + + +----- COMPILED JAVASCRIPT +import * as $other_module from "../other_module.mjs"; +import { Variant as Aliased } from "../other_module.mjs"; + +export function check(x) { + return x instanceof Variant; +} diff --git a/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__variant_defined_in_another_module_qualified_clause_guard.snap b/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__variant_defined_in_another_module_qualified_clause_guard.snap new file mode 100644 index 00000000000..6e6003ccc47 --- /dev/null +++ b/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__variant_defined_in_another_module_qualified_clause_guard.snap @@ -0,0 +1,31 @@ +--- +source: compiler-core/src/javascript/tests/custom_types.rs +expression: "\nimport other_module\n\npub fn process(e) -> String {\n case e {\n value if value == other_module.Variant -> \"match\"\n _ -> \"no match\"\n }\n}\n" +--- +----- SOURCE CODE +-- other_module.gleam +pub type Thingy { Variant Other(Int) } + +-- main.gleam + +import other_module + +pub fn process(e) -> String { + case e { + value if value == other_module.Variant -> "match" + _ -> "no match" + } +} + + +----- COMPILED JAVASCRIPT +import * as $other_module from "../other_module.mjs"; + +export function process(e) { + let value = e; + if (value instanceof Variant) { + return "match"; + } else { + return "no match"; + } +} diff --git a/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__variant_defined_in_another_module_qualified_expression.snap b/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__variant_defined_in_another_module_qualified_expression.snap new file mode 100644 index 00000000000..7ac37916696 --- /dev/null +++ b/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__variant_defined_in_another_module_qualified_expression.snap @@ -0,0 +1,24 @@ +--- +source: compiler-core/src/javascript/tests/custom_types.rs +expression: "\nimport other_module\n\npub fn check(x) -> Bool {\n x == other_module.Variant\n}\n" +--- +----- SOURCE CODE +-- other_module.gleam +pub type Thingy { Variant OtherVariant } + +-- main.gleam + +import other_module + +pub fn check(x) -> Bool { + x == other_module.Variant +} + + +----- COMPILED JAVASCRIPT +import { isEqual } from "../gleam.mjs"; +import * as $other_module from "../other_module.mjs"; + +export function check(x) { + return isEqual(x, new $other_module.Variant()); +} diff --git a/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__variant_defined_in_another_module_unqualified_clause_guard.snap b/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__variant_defined_in_another_module_unqualified_clause_guard.snap new file mode 100644 index 00000000000..6e898796329 --- /dev/null +++ b/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__variant_defined_in_another_module_unqualified_clause_guard.snap @@ -0,0 +1,32 @@ +--- +source: compiler-core/src/javascript/tests/custom_types.rs +expression: "\nimport other_module.{Variant}\n\npub fn process(e) -> String {\n case e {\n value if value == Variant -> \"match\"\n _ -> \"no match\"\n }\n}\n" +--- +----- SOURCE CODE +-- other_module.gleam +pub type Thingy { Variant Other(Int) } + +-- main.gleam + +import other_module.{Variant} + +pub fn process(e) -> String { + case e { + value if value == Variant -> "match" + _ -> "no match" + } +} + + +----- COMPILED JAVASCRIPT +import * as $other_module from "../other_module.mjs"; +import { Variant } from "../other_module.mjs"; + +export function process(e) { + let value = e; + if (value instanceof Variant) { + return "match"; + } else { + return "no match"; + } +} diff --git a/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__variant_defined_in_another_module_unqualified_expression.snap b/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__variant_defined_in_another_module_unqualified_expression.snap new file mode 100644 index 00000000000..a51477e0c6e --- /dev/null +++ b/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__variant_defined_in_another_module_unqualified_expression.snap @@ -0,0 +1,24 @@ +--- +source: compiler-core/src/javascript/tests/custom_types.rs +expression: "\nimport other_module.{Variant}\n\npub fn check(x) -> Bool {\n x == Variant\n}\n" +--- +----- SOURCE CODE +-- other_module.gleam +pub type Thingy { Variant Other(Int) } + +-- main.gleam + +import other_module.{Variant} + +pub fn check(x) -> Bool { + x == Variant +} + + +----- COMPILED JAVASCRIPT +import * as $other_module from "../other_module.mjs"; +import { Variant } from "../other_module.mjs"; + +export function check(x) { + return x instanceof Variant; +} From e93197eac3c6643659cc490b448738410834504e Mon Sep 17 00:00:00 2001 From: Nafi Date: Tue, 16 Sep 2025 22:50:46 +0530 Subject: [PATCH 05/12] refactor --- compiler-core/src/javascript/expression.rs | 47 ++++++++++++---------- 1 file changed, 25 insertions(+), 22 deletions(-) diff --git a/compiler-core/src/javascript/expression.rs b/compiler-core/src/javascript/expression.rs index 721b2df560a..6edac4cf3ed 100644 --- a/compiler-core/src/javascript/expression.rs +++ b/compiler-core/src/javascript/expression.rs @@ -178,7 +178,7 @@ pub(crate) struct Generator<'module, 'ast> { pub let_assert_always_panics: bool, } -fn ctor_tag(g: &TypedClauseGuard) -> Option { +fn zero_arity_constructor_tag(g: &TypedClauseGuard) -> Option { match g { ClauseGuard::Constant(Constant::Record { arguments, name, .. @@ -187,11 +187,11 @@ fn ctor_tag(g: &TypedClauseGuard) -> Option { } } -fn is_var(g: &TypedClauseGuard) -> bool { +fn is_variable_guard(g: &TypedClauseGuard) -> bool { matches!(g, ClauseGuard::Var { .. }) } -fn looks_like_constructor_alias(var: &TypedClauseGuard) -> bool { +fn is_constructor_pattern_binding(var: &TypedClauseGuard) -> bool { match var { ClauseGuard::Var { type_, .. } => matches!(&**type_, Type::Fn { .. }), // lambda _ => false, @@ -1582,7 +1582,10 @@ impl<'module, 'a> Generator<'module, 'a> { .. } = right { - return self.singleton_equal_helper(left, name, should_be_equal); + let left = self + .not_in_tail_position(Some(Ordering::Strict), |this| this.wrap_expression(left)); + + return self.singleton_equal(left, name, should_be_equal); } // LHS: singleton record constructor (arity 0) @@ -1595,7 +1598,10 @@ impl<'module, 'a> Generator<'module, 'a> { .. } = left { - return self.singleton_equal_helper(right, name, should_be_equal); + let right = self + .not_in_tail_position(Some(Ordering::Strict), |this| this.wrap_expression(right)); + + return self.singleton_equal(right, name, should_be_equal); } // Other types must be compared using structural equality @@ -1607,20 +1613,16 @@ impl<'module, 'a> Generator<'module, 'a> { self.prelude_equal_call(should_be_equal, left, right) } - fn singleton_equal_helper( - &mut self, - expr: &'a TypedExpr, - name: &EcoString, + fn singleton_equal( + &self, + value: Document<'a>, + tag: &EcoString, should_be_equal: bool, ) -> Document<'a> { - let expr_doc = - self.not_in_tail_position(Some(Ordering::Strict), |this| this.wrap_expression(expr)); - let constructor_name = name.to_doc(); - if should_be_equal { - docvec![expr_doc, " instanceof ", constructor_name] + docvec![value, " instanceof ", tag.to_doc()] } else { - docvec!["!(", expr_doc, " instanceof ", constructor_name, ")"] + docvec!["!(", value, " instanceof ", tag.to_doc(), ")"] } } @@ -2049,12 +2051,13 @@ impl<'module, 'a> Generator<'module, 'a> { ClauseGuard::Equals { left, right, .. } | ClauseGuard::NotEquals { left, right, .. } => { let should_be_equal = matches!(guard, ClauseGuard::Equals { .. }); - let both_ctor_values = ctor_tag(left).is_some() && ctor_tag(right).is_some(); + let both_ctor_values = zero_arity_constructor_tag(left).is_some() + && zero_arity_constructor_tag(right).is_some(); if !both_ctor_values { - if is_var(left) - && !looks_like_constructor_alias(left) - && let Some(tag) = ctor_tag(right) + if is_variable_guard(left) + && !is_constructor_pattern_binding(left) + && let Some(tag) = zero_arity_constructor_tag(right) { let l = self.guard(left); return if should_be_equal { @@ -2063,9 +2066,9 @@ impl<'module, 'a> Generator<'module, 'a> { docvec!["!(", l, " instanceof ", tag.to_doc(), ")"] }; } - if is_var(right) - && !looks_like_constructor_alias(right) - && let Some(tag) = ctor_tag(left) + if is_variable_guard(right) + && !is_constructor_pattern_binding(right) + && let Some(tag) = zero_arity_constructor_tag(left) { let r = self.guard(right); return if should_be_equal { From 0f67076eef1e116068d55558b2e45ac6c56798e6 Mon Sep 17 00:00:00 2001 From: Nafi Date: Fri, 19 Sep 2025 03:13:38 +0530 Subject: [PATCH 06/12] refactor --- compiler-core/src/javascript/expression.rs | 81 ++++++++++------------ compiler-core/src/type_.rs | 7 ++ 2 files changed, 45 insertions(+), 43 deletions(-) diff --git a/compiler-core/src/javascript/expression.rs b/compiler-core/src/javascript/expression.rs index 6edac4cf3ed..2cb61a31c19 100644 --- a/compiler-core/src/javascript/expression.rs +++ b/compiler-core/src/javascript/expression.rs @@ -178,26 +178,6 @@ pub(crate) struct Generator<'module, 'ast> { pub let_assert_always_panics: bool, } -fn zero_arity_constructor_tag(g: &TypedClauseGuard) -> Option { - match g { - ClauseGuard::Constant(Constant::Record { - arguments, name, .. - }) if arguments.is_empty() => Some(name.clone()), - _ => None, - } -} - -fn is_variable_guard(g: &TypedClauseGuard) -> bool { - matches!(g, ClauseGuard::Var { .. }) -} - -fn is_constructor_pattern_binding(var: &TypedClauseGuard) -> bool { - match var { - ClauseGuard::Var { type_, .. } => matches!(&**type_, Type::Fn { .. }), // lambda - _ => false, - } -} - impl<'module, 'a> Generator<'module, 'a> { #[allow(clippy::too_many_arguments)] // TODO: FIXME pub fn new( @@ -2050,39 +2030,54 @@ impl<'module, 'a> Generator<'module, 'a> { ClauseGuard::Equals { left, right, .. } | ClauseGuard::NotEquals { left, right, .. } => { - let should_be_equal = matches!(guard, ClauseGuard::Equals { .. }); - let both_ctor_values = zero_arity_constructor_tag(left).is_some() + fn is_variable(g: &TypedClauseGuard) -> bool { + matches!(g, ClauseGuard::Var { .. }) + } + + // returns Some(name) if g is a zero arity constructor *value* + fn zero_arity_constructor_tag(g: &TypedClauseGuard) -> Option { + match g { + ClauseGuard::Constant(Constant::Record { + arguments, name, .. + }) if arguments.is_empty() && g.type_().is_named() => Some(name.clone()), + _ => None, + } + } + + // true when the variable is the alias introduced by pattern + fn is_constructor_alias(g: &TypedClauseGuard) -> bool { + match g { + ClauseGuard::Var { type_, .. } => matches!(&**type_, Type::Fn { .. }), + _ => false, + } + } + + let want_equal = matches!(guard, ClauseGuard::Equals { .. }); + let both_sides_ctor = zero_arity_constructor_tag(left).is_some() && zero_arity_constructor_tag(right).is_some(); - if !both_ctor_values { - if is_variable_guard(left) - && !is_constructor_pattern_binding(left) + if !both_sides_ctor { + // variable == constructor_value + if is_variable(left) + && !is_constructor_alias(left) && let Some(tag) = zero_arity_constructor_tag(right) { - let l = self.guard(left); - return if should_be_equal { - docvec![l, " instanceof ", tag.to_doc()] - } else { - docvec!["!(", l, " instanceof ", tag.to_doc(), ")"] - }; + let val = self.guard(left); + return self.singleton_equal(val, &tag, want_equal); } - if is_variable_guard(right) - && !is_constructor_pattern_binding(right) + // constructor_value == variable + if is_variable(right) + && !is_constructor_alias(right) && let Some(tag) = zero_arity_constructor_tag(left) { - let r = self.guard(right); - return if should_be_equal { - docvec![r, " instanceof ", tag.to_doc()] - } else { - docvec!["!(", r, " instanceof ", tag.to_doc(), ")"] - }; + let val = self.guard(right); + return self.singleton_equal(val, &tag, want_equal); } } - // Otherwise fallback to normal equality check - let left_doc = self.guard(left); - let right_doc = self.guard(right); - self.prelude_equal_call(should_be_equal, left_doc, right_doc) + let l = self.guard(left); + let r = self.guard(right); + self.prelude_equal_call(want_equal, l, r) } ClauseGuard::GtFloat { left, right, .. } | ClauseGuard::GtInt { left, right, .. } => { diff --git a/compiler-core/src/type_.rs b/compiler-core/src/type_.rs index ec32881c151..d7136804377 100644 --- a/compiler-core/src/type_.rs +++ b/compiler-core/src/type_.rs @@ -131,6 +131,13 @@ impl Type { } } + pub fn is_named(&self) -> bool { + match self { + Self::Named { .. } => true, + _ => false, + } + } + pub fn result_ok_type(&self) -> Option> { match self { Self::Named { From 3f65f998ae4914c9cf7a8439d11300613e6b6846 Mon Sep 17 00:00:00 2001 From: Nafi Date: Tue, 23 Sep 2025 00:30:04 +0530 Subject: [PATCH 07/12] refactor --- compiler-core/src/javascript/expression.rs | 66 +++++++++------------- 1 file changed, 26 insertions(+), 40 deletions(-) diff --git a/compiler-core/src/javascript/expression.rs b/compiler-core/src/javascript/expression.rs index 2cb61a31c19..4b16530c224 100644 --- a/compiler-core/src/javascript/expression.rs +++ b/compiler-core/src/javascript/expression.rs @@ -2030,54 +2030,40 @@ impl<'module, 'a> Generator<'module, 'a> { ClauseGuard::Equals { left, right, .. } | ClauseGuard::NotEquals { left, right, .. } => { - fn is_variable(g: &TypedClauseGuard) -> bool { - matches!(g, ClauseGuard::Var { .. }) - } - - // returns Some(name) if g is a zero arity constructor *value* - fn zero_arity_constructor_tag(g: &TypedClauseGuard) -> Option { - match g { + fn eligible_singleton_cmp( + lhs: &TypedClauseGuard, + rhs: &TypedClauseGuard, + ) -> Option { + match rhs { ClauseGuard::Constant(Constant::Record { arguments, name, .. - }) if arguments.is_empty() && g.type_().is_named() => Some(name.clone()), - _ => None, - } - } - - // true when the variable is the alias introduced by pattern - fn is_constructor_alias(g: &TypedClauseGuard) -> bool { - match g { - ClauseGuard::Var { type_, .. } => matches!(&**type_, Type::Fn { .. }), - _ => false, + }) if arguments.is_empty() && rhs.type_().is_named() => { + // exclude the `Type::Fn` case + if !matches!(lhs, + ClauseGuard::Var { type_, .. } if matches!(&**type_, Type::Fn { .. })) + { + return Some(name.clone()); + } + } + _ => {} } + None } - let want_equal = matches!(guard, ClauseGuard::Equals { .. }); - let both_sides_ctor = zero_arity_constructor_tag(left).is_some() - && zero_arity_constructor_tag(right).is_some(); + let should_be_equal = matches!(guard, ClauseGuard::Equals { .. }); - if !both_sides_ctor { - // variable == constructor_value - if is_variable(left) - && !is_constructor_alias(left) - && let Some(tag) = zero_arity_constructor_tag(right) - { - let val = self.guard(left); - return self.singleton_equal(val, &tag, want_equal); - } - // constructor_value == variable - if is_variable(right) - && !is_constructor_alias(right) - && let Some(tag) = zero_arity_constructor_tag(left) - { - let val = self.guard(right); - return self.singleton_equal(val, &tag, want_equal); - } + if let Some(tag) = eligible_singleton_cmp(left, right) { + let doc = self.guard(left); + return self.singleton_equal(doc, &tag, should_be_equal); + } + if let Some(tag) = eligible_singleton_cmp(right, left) { + let doc = self.guard(right); + return self.singleton_equal(doc, &tag, should_be_equal); } - let l = self.guard(left); - let r = self.guard(right); - self.prelude_equal_call(want_equal, l, r) + let left = self.guard(left); + let right = self.guard(right); + self.prelude_equal_call(should_be_equal, left, right) } ClauseGuard::GtFloat { left, right, .. } | ClauseGuard::GtInt { left, right, .. } => { From 4f0046ed69d58ded1cba2df62904e6813f299058 Mon Sep 17 00:00:00 2001 From: Nafi Date: Tue, 23 Sep 2025 03:56:09 +0530 Subject: [PATCH 08/12] refactor --- compiler-core/src/javascript/expression.rs | 67 +++++++++------------- 1 file changed, 28 insertions(+), 39 deletions(-) diff --git a/compiler-core/src/javascript/expression.rs b/compiler-core/src/javascript/expression.rs index 4b16530c224..e1e2d06eb26 100644 --- a/compiler-core/src/javascript/expression.rs +++ b/compiler-core/src/javascript/expression.rs @@ -1552,16 +1552,21 @@ impl<'module, 'a> Generator<'module, 'a> { // because the first approach needs to construct a new Wibble, and then call the isEqual function, // which supports any shape of data, and so does a lot of extra logic which isn't necessary. - // RHS: singleton record constructor (arity 0) - if let TypedExpr::Var { - constructor: - ValueConstructor { - variant: ValueConstructorVariant::Record { arity: 0, name, .. }, + fn singleton_record_constructor_name(expr: &TypedExpr) -> Option<&EcoString> { + match expr { + TypedExpr::Var { + constructor: + ValueConstructor { + variant: ValueConstructorVariant::Record { arity: 0, name, .. }, + .. + }, .. - }, - .. - } = right - { + } => Some(name), + _ => None, + } + } + + if let Some(name) = singleton_record_constructor_name(right) { let left = self .not_in_tail_position(Some(Ordering::Strict), |this| this.wrap_expression(left)); @@ -1569,15 +1574,7 @@ impl<'module, 'a> Generator<'module, 'a> { } // LHS: singleton record constructor (arity 0) - if let TypedExpr::Var { - constructor: - ValueConstructor { - variant: ValueConstructorVariant::Record { arity: 0, name, .. }, - .. - }, - .. - } = left - { + if let Some(name) = singleton_record_constructor_name(left) { let right = self .not_in_tail_position(Some(Ordering::Strict), |this| this.wrap_expression(right)); @@ -2030,35 +2027,27 @@ impl<'module, 'a> Generator<'module, 'a> { ClauseGuard::Equals { left, right, .. } | ClauseGuard::NotEquals { left, right, .. } => { - fn eligible_singleton_cmp( - lhs: &TypedClauseGuard, - rhs: &TypedClauseGuard, - ) -> Option { - match rhs { + fn singleton_record_constructor_name( + guard: &TypedClauseGuard, + ) -> Option<&EcoString> { + match guard { ClauseGuard::Constant(Constant::Record { arguments, name, .. - }) if arguments.is_empty() && rhs.type_().is_named() => { - // exclude the `Type::Fn` case - if !matches!(lhs, - ClauseGuard::Var { type_, .. } if matches!(&**type_, Type::Fn { .. })) - { - return Some(name.clone()); - } - } - _ => {} + }) if arguments.is_empty() => Some(name), + _ => None, } - None } let should_be_equal = matches!(guard, ClauseGuard::Equals { .. }); - if let Some(tag) = eligible_singleton_cmp(left, right) { - let doc = self.guard(left); - return self.singleton_equal(doc, &tag, should_be_equal); + if let Some(name) = singleton_record_constructor_name(right) { + let left = self.guard(left); + return self.singleton_equal(left, name, should_be_equal); } - if let Some(tag) = eligible_singleton_cmp(right, left) { - let doc = self.guard(right); - return self.singleton_equal(doc, &tag, should_be_equal); + + if let Some(name) = singleton_record_constructor_name(left) { + let right = self.guard(right); + return self.singleton_equal(right, name, should_be_equal); } let left = self.guard(left); From ba10e88a10695f447a72aaec845bee878a31c171 Mon Sep 17 00:00:00 2001 From: Nafi Date: Wed, 24 Sep 2025 16:50:43 +0530 Subject: [PATCH 09/12] refactor. addded helper methods for singleton equality codegen --- compiler-core/src/javascript/expression.rs | 96 ++++++++++++++-------- 1 file changed, 60 insertions(+), 36 deletions(-) diff --git a/compiler-core/src/javascript/expression.rs b/compiler-core/src/javascript/expression.rs index e1e2d06eb26..10050284759 100644 --- a/compiler-core/src/javascript/expression.rs +++ b/compiler-core/src/javascript/expression.rs @@ -1552,6 +1552,25 @@ impl<'module, 'a> Generator<'module, 'a> { // because the first approach needs to construct a new Wibble, and then call the isEqual function, // which supports any shape of data, and so does a lot of extra logic which isn't necessary. + if let Some(result) = self.handle_singleton_equality(left, right, should_be_equal) { + return result; + } + + // Other types must be compared using structural equality + let left = + self.not_in_tail_position(Some(Ordering::Strict), |this| this.wrap_expression(left)); + let right = + self.not_in_tail_position(Some(Ordering::Strict), |this| this.wrap_expression(right)); + + self.prelude_equal_call(should_be_equal, left, right) + } + + fn handle_singleton_equality( + &mut self, + left: &'a TypedExpr, + right: &'a TypedExpr, + should_be_equal: bool, + ) -> Option> { fn singleton_record_constructor_name(expr: &TypedExpr) -> Option<&EcoString> { match expr { TypedExpr::Var { @@ -1567,27 +1586,18 @@ impl<'module, 'a> Generator<'module, 'a> { } if let Some(name) = singleton_record_constructor_name(right) { - let left = self + let left_doc = self .not_in_tail_position(Some(Ordering::Strict), |this| this.wrap_expression(left)); - - return self.singleton_equal(left, name, should_be_equal); + return Some(self.singleton_equal(left_doc, name, should_be_equal)); } - // LHS: singleton record constructor (arity 0) if let Some(name) = singleton_record_constructor_name(left) { - let right = self + let right_doc = self .not_in_tail_position(Some(Ordering::Strict), |this| this.wrap_expression(right)); - - return self.singleton_equal(right, name, should_be_equal); + return Some(self.singleton_equal(right_doc, name, should_be_equal)); } - // Other types must be compared using structural equality - let left = - self.not_in_tail_position(Some(Ordering::Strict), |this| this.wrap_expression(left)); - let right = - self.not_in_tail_position(Some(Ordering::Strict), |this| this.wrap_expression(right)); - - self.prelude_equal_call(should_be_equal, left, right) + None } fn singleton_equal( @@ -2027,32 +2037,18 @@ impl<'module, 'a> Generator<'module, 'a> { ClauseGuard::Equals { left, right, .. } | ClauseGuard::NotEquals { left, right, .. } => { - fn singleton_record_constructor_name( - guard: &TypedClauseGuard, - ) -> Option<&EcoString> { - match guard { - ClauseGuard::Constant(Constant::Record { - arguments, name, .. - }) if arguments.is_empty() => Some(name), - _ => None, - } - } - let should_be_equal = matches!(guard, ClauseGuard::Equals { .. }); - if let Some(name) = singleton_record_constructor_name(right) { - let left = self.guard(left); - return self.singleton_equal(left, name, should_be_equal); - } - - if let Some(name) = singleton_record_constructor_name(left) { - let right = self.guard(right); - return self.singleton_equal(right, name, should_be_equal); + // Handle singleton equality optimization for guards + if let Some(result) = + self.handle_guard_singleton_equality(left, right, should_be_equal) + { + return result; } - let left = self.guard(left); - let right = self.guard(right); - self.prelude_equal_call(should_be_equal, left, right) + let left_doc = self.guard(left); + let right_doc = self.guard(right); + self.prelude_equal_call(should_be_equal, left_doc, right_doc) } ClauseGuard::GtFloat { left, right, .. } | ClauseGuard::GtInt { left, right, .. } => { @@ -2155,6 +2151,34 @@ impl<'module, 'a> Generator<'module, 'a> { } } + fn handle_guard_singleton_equality( + &mut self, + left: &'a TypedClauseGuard, + right: &'a TypedClauseGuard, + should_be_equal: bool, + ) -> Option> { + fn singleton_record_constructor_name(guard: &TypedClauseGuard) -> Option<&EcoString> { + match guard { + ClauseGuard::Constant(Constant::Record { + arguments, name, .. + }) if arguments.is_empty() => Some(name), + _ => None, + } + } + + if let Some(name) = singleton_record_constructor_name(right) { + let left_doc = self.guard(left); + return Some(self.singleton_equal(left_doc, name, should_be_equal)); + } + + if let Some(name) = singleton_record_constructor_name(left) { + let right_doc = self.guard(right); + return Some(self.singleton_equal(right_doc, name, should_be_equal)); + } + + None + } + fn wrapped_guard(&mut self, guard: &'a TypedClauseGuard) -> Document<'a> { match guard { ClauseGuard::Var { .. } From 95c73b4a23c03ba20989f97d55fa304eb6ee2c21 Mon Sep 17 00:00:00 2001 From: Nafi Date: Sat, 27 Sep 2025 00:47:20 +0530 Subject: [PATCH 10/12] refactor --- compiler-core/src/javascript/expression.rs | 88 ++++++++----------- ...es__mixed_singleton_and_non_singleton.snap | 6 ++ ...ypes__multiple_singleton_constructors.snap | 6 ++ ..._types__non_singleton_record_equality.snap | 6 ++ ...custom_types__singleton_in_case_guard.snap | 4 + ...stom_types__singleton_record_equality.snap | 4 + ...om_types__singleton_record_inequality.snap | 4 + ...types__singleton_record_reverse_order.snap | 4 + 8 files changed, 72 insertions(+), 50 deletions(-) diff --git a/compiler-core/src/javascript/expression.rs b/compiler-core/src/javascript/expression.rs index 10050284759..052d415a1a7 100644 --- a/compiler-core/src/javascript/expression.rs +++ b/compiler-core/src/javascript/expression.rs @@ -1552,8 +1552,12 @@ impl<'module, 'a> Generator<'module, 'a> { // because the first approach needs to construct a new Wibble, and then call the isEqual function, // which supports any shape of data, and so does a lot of extra logic which isn't necessary. - if let Some(result) = self.handle_singleton_equality(left, right, should_be_equal) { - return result; + if let Some(doc) = self.singleton_variant_equality(left, right, should_be_equal) { + return doc; + } + + if let Some(doc) = self.singleton_variant_equality(right, left, should_be_equal) { + return doc; } // Other types must be compared using structural equality @@ -1565,42 +1569,30 @@ impl<'module, 'a> Generator<'module, 'a> { self.prelude_equal_call(should_be_equal, left, right) } - fn handle_singleton_equality( + fn singleton_variant_equality( &mut self, left: &'a TypedExpr, right: &'a TypedExpr, should_be_equal: bool, ) -> Option> { - fn singleton_record_constructor_name(expr: &TypedExpr) -> Option<&EcoString> { - match expr { - TypedExpr::Var { - constructor: - ValueConstructor { - variant: ValueConstructorVariant::Record { arity: 0, name, .. }, - .. - }, + if let TypedExpr::Var { + constructor: + ValueConstructor { + variant: ValueConstructorVariant::Record { arity: 0, name, .. }, .. - } => Some(name), - _ => None, - } - } - - if let Some(name) = singleton_record_constructor_name(right) { + }, + .. + } = right + { let left_doc = self .not_in_tail_position(Some(Ordering::Strict), |this| this.wrap_expression(left)); - return Some(self.singleton_equal(left_doc, name, should_be_equal)); - } - - if let Some(name) = singleton_record_constructor_name(left) { - let right_doc = self - .not_in_tail_position(Some(Ordering::Strict), |this| this.wrap_expression(right)); - return Some(self.singleton_equal(right_doc, name, should_be_equal)); + Some(self.singleton_equal_helper(left_doc, name, should_be_equal)) + } else { + None } - - None } - fn singleton_equal( + fn singleton_equal_helper( &self, value: Document<'a>, tag: &EcoString, @@ -2040,10 +2032,16 @@ impl<'module, 'a> Generator<'module, 'a> { let should_be_equal = matches!(guard, ClauseGuard::Equals { .. }); // Handle singleton equality optimization for guards - if let Some(result) = - self.handle_guard_singleton_equality(left, right, should_be_equal) + if let Some(doc) = + self.singleton_variant_guard_equality(left, right, should_be_equal) { - return result; + return doc; + } + + if let Some(doc) = + self.singleton_variant_guard_equality(right, left, should_be_equal) + { + return doc; } let left_doc = self.guard(left); @@ -2151,31 +2149,21 @@ impl<'module, 'a> Generator<'module, 'a> { } } - fn handle_guard_singleton_equality( + fn singleton_variant_guard_equality( &mut self, left: &'a TypedClauseGuard, right: &'a TypedClauseGuard, should_be_equal: bool, ) -> Option> { - fn singleton_record_constructor_name(guard: &TypedClauseGuard) -> Option<&EcoString> { - match guard { - ClauseGuard::Constant(Constant::Record { - arguments, name, .. - }) if arguments.is_empty() => Some(name), - _ => None, - } - } - - if let Some(name) = singleton_record_constructor_name(right) { - let left_doc = self.guard(left); - return Some(self.singleton_equal(left_doc, name, should_be_equal)); - } - - if let Some(name) = singleton_record_constructor_name(left) { - let right_doc = self.guard(right); - return Some(self.singleton_equal(right_doc, name, should_be_equal)); - } - + if let ClauseGuard::Constant(Constant::Record { + arguments, name, .. + }) = right + && arguments.is_empty() && right.type_().is_named() + && let ClauseGuard::Var { type_, .. } = left + && !matches!(&**type_, Type::Fn { .. }) { + let left_doc = self.guard(left); + return Some(self.singleton_equal_helper(left_doc, name, should_be_equal)); + } None } diff --git a/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__mixed_singleton_and_non_singleton.snap b/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__mixed_singleton_and_non_singleton.snap index d63c98b7ea0..facec826ad1 100644 --- a/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__mixed_singleton_and_non_singleton.snap +++ b/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__mixed_singleton_and_non_singleton.snap @@ -23,8 +23,14 @@ export class Ok extends $CustomType { this.value = value; } } +export const Result$Ok = (value) => new Ok(value); +export const Result$isOk = (value) => value instanceof Ok; +export const Result$Ok$value = (value) => value.value; +export const Result$Ok$0 = (value) => value.value; export class Error extends $CustomType {} +export const Result$Error = () => new Error(); +export const Result$isError = (value) => value instanceof Error; export function is_error(r) { return r instanceof Error; diff --git a/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__multiple_singleton_constructors.snap b/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__multiple_singleton_constructors.snap index 95fae46f222..b0d7056d28c 100644 --- a/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__multiple_singleton_constructors.snap +++ b/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__multiple_singleton_constructors.snap @@ -23,10 +23,16 @@ pub fn is_success(s: Status) -> Bool { import { CustomType as $CustomType } from "../gleam.mjs"; export class Loading extends $CustomType {} +export const Status$Loading = () => new Loading(); +export const Status$isLoading = (value) => value instanceof Loading; export class Success extends $CustomType {} +export const Status$Success = () => new Success(); +export const Status$isSuccess = (value) => value instanceof Success; export class Error extends $CustomType {} +export const Status$Error = () => new Error(); +export const Status$isError = (value) => value instanceof Error; export function is_loading(s) { return s instanceof Loading; diff --git a/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__non_singleton_record_equality.snap b/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__non_singleton_record_equality.snap index 78757f96e62..fae6e7e4234 100644 --- a/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__non_singleton_record_equality.snap +++ b/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__non_singleton_record_equality.snap @@ -23,6 +23,12 @@ export class Person extends $CustomType { this.age = age; } } +export const Person$Person = (name, age) => new Person(name, age); +export const Person$isPerson = (value) => value instanceof Person; +export const Person$Person$name = (value) => value.name; +export const Person$Person$0 = (value) => value.name; +export const Person$Person$age = (value) => value.age; +export const Person$Person$1 = (value) => value.age; export function same_person(p1, p2) { return isEqual(p1, p2); diff --git a/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__singleton_in_case_guard.snap b/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__singleton_in_case_guard.snap index 65ff0ca2a1a..69e79adb08c 100644 --- a/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__singleton_in_case_guard.snap +++ b/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__singleton_in_case_guard.snap @@ -21,8 +21,12 @@ pub fn process(s: State) -> String { import { CustomType as $CustomType } from "../gleam.mjs"; export class Active extends $CustomType {} +export const State$Active = () => new Active(); +export const State$isActive = (value) => value instanceof Active; export class Inactive extends $CustomType {} +export const State$Inactive = () => new Inactive(); +export const State$isInactive = (value) => value instanceof Inactive; export function process(s) { let state = s; diff --git a/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__singleton_record_equality.snap b/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__singleton_record_equality.snap index 7f6c37a21af..2933bedefc6 100644 --- a/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__singleton_record_equality.snap +++ b/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__singleton_record_equality.snap @@ -18,8 +18,12 @@ pub fn is_wibble(w: Wibble) -> Bool { import { CustomType as $CustomType } from "../gleam.mjs"; export class Wibble extends $CustomType {} +export const Wibble$Wibble = () => new Wibble(); +export const Wibble$isWibble = (value) => value instanceof Wibble; export class Wobble extends $CustomType {} +export const Wibble$Wobble = () => new Wobble(); +export const Wibble$isWobble = (value) => value instanceof Wobble; export function is_wibble(w) { return w instanceof Wibble; diff --git a/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__singleton_record_inequality.snap b/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__singleton_record_inequality.snap index 02823f89d3f..e062790629d 100644 --- a/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__singleton_record_inequality.snap +++ b/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__singleton_record_inequality.snap @@ -18,8 +18,12 @@ pub fn is_not_wibble(w: Wibble) -> Bool { import { CustomType as $CustomType } from "../gleam.mjs"; export class Wibble extends $CustomType {} +export const Wibble$Wibble = () => new Wibble(); +export const Wibble$isWibble = (value) => value instanceof Wibble; export class Wobble extends $CustomType {} +export const Wibble$Wobble = () => new Wobble(); +export const Wibble$isWobble = (value) => value instanceof Wobble; export function is_not_wibble(w) { return !(w instanceof Wibble); diff --git a/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__singleton_record_reverse_order.snap b/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__singleton_record_reverse_order.snap index 8558926e5a2..41ac8f2d288 100644 --- a/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__singleton_record_reverse_order.snap +++ b/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__singleton_record_reverse_order.snap @@ -18,8 +18,12 @@ pub fn is_wibble_reverse(w: Wibble) -> Bool { import { CustomType as $CustomType } from "../gleam.mjs"; export class Wibble extends $CustomType {} +export const Wibble$Wibble = () => new Wibble(); +export const Wibble$isWibble = (value) => value instanceof Wibble; export class Wobble extends $CustomType {} +export const Wibble$Wobble = () => new Wobble(); +export const Wibble$isWobble = (value) => value instanceof Wobble; export function is_wibble_reverse(w) { return w instanceof Wibble; From 3d76e012b4289c5a4eb8c98ddc0bda10c62ed3f8 Mon Sep 17 00:00:00 2001 From: Nafi Date: Sun, 28 Sep 2025 04:41:28 +0900 Subject: [PATCH 11/12] added tests to ensure there are no false positives (optmizations dont trigger where they shouldnt) --- compiler-core/src/javascript/expression.rs | 14 +++--- .../src/javascript/tests/custom_types.rs | 35 +++++++++++++++ ...__equality_with_non_singleton_variant.snap | 36 +++++++++++++++ ...d_equality_with_non_singleton_variant.snap | 44 +++++++++++++++++++ 4 files changed, 123 insertions(+), 6 deletions(-) create mode 100644 compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__equality_with_non_singleton_variant.snap create mode 100644 compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__guard_equality_with_non_singleton_variant.snap diff --git a/compiler-core/src/javascript/expression.rs b/compiler-core/src/javascript/expression.rs index 052d415a1a7..4e8bf1f945a 100644 --- a/compiler-core/src/javascript/expression.rs +++ b/compiler-core/src/javascript/expression.rs @@ -2158,12 +2158,14 @@ impl<'module, 'a> Generator<'module, 'a> { if let ClauseGuard::Constant(Constant::Record { arguments, name, .. }) = right - && arguments.is_empty() && right.type_().is_named() - && let ClauseGuard::Var { type_, .. } = left - && !matches!(&**type_, Type::Fn { .. }) { - let left_doc = self.guard(left); - return Some(self.singleton_equal_helper(left_doc, name, should_be_equal)); - } + && arguments.is_empty() + && right.type_().is_named() + && let ClauseGuard::Var { type_, .. } = left + && !matches!(&**type_, Type::Fn { .. }) + { + let left_doc = self.guard(left); + return Some(self.singleton_equal_helper(left_doc, name, should_be_equal)); + } None } diff --git a/compiler-core/src/javascript/tests/custom_types.rs b/compiler-core/src/javascript/tests/custom_types.rs index b62607cdc98..9a5f9920b04 100644 --- a/compiler-core/src/javascript/tests/custom_types.rs +++ b/compiler-core/src/javascript/tests/custom_types.rs @@ -807,6 +807,41 @@ pub fn process(s: State) -> String { ); } +#[test] +fn equality_with_non_singleton_variant() { + assert_js!( + r#" +pub type Thing { + Variant + Other(String) +} + +pub fn check_other(x: Thing) -> Bool { + x == Other("hello") +} +"#, + ); +} + +#[test] +fn guard_equality_with_non_singleton_variant() { + assert_js!( + r#" +pub type Thing { + Variant + Other(String) +} + +pub fn process(e: Thing) -> String { + case e { + value if value == Other("hello") -> "match" + _ -> "no match" + } +} +"#, + ); +} + #[test] fn variant_defined_in_another_module_qualified_expression() { assert_js!( diff --git a/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__equality_with_non_singleton_variant.snap b/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__equality_with_non_singleton_variant.snap new file mode 100644 index 00000000000..80ab92302c6 --- /dev/null +++ b/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__equality_with_non_singleton_variant.snap @@ -0,0 +1,36 @@ +--- +source: compiler-core/src/javascript/tests/custom_types.rs +expression: "\npub type Thing {\n Variant\n Other(String)\n}\n\npub fn check_other(x: Thing) -> Bool {\n x == Other(\"hello\")\n}\n" +--- +----- SOURCE CODE + +pub type Thing { + Variant + Other(String) +} + +pub fn check_other(x: Thing) -> Bool { + x == Other("hello") +} + + +----- COMPILED JAVASCRIPT +import { CustomType as $CustomType, isEqual } from "../gleam.mjs"; + +export class Variant extends $CustomType {} +export const Thing$Variant = () => new Variant(); +export const Thing$isVariant = (value) => value instanceof Variant; + +export class Other extends $CustomType { + constructor($0) { + super(); + this[0] = $0; + } +} +export const Thing$Other = ($0) => new Other($0); +export const Thing$isOther = (value) => value instanceof Other; +export const Thing$Other$0 = (value) => value[0]; + +export function check_other(x) { + return isEqual(x, new Other("hello")); +} diff --git a/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__guard_equality_with_non_singleton_variant.snap b/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__guard_equality_with_non_singleton_variant.snap new file mode 100644 index 00000000000..93f3e8229d3 --- /dev/null +++ b/compiler-core/src/javascript/tests/snapshots/gleam_core__javascript__tests__custom_types__guard_equality_with_non_singleton_variant.snap @@ -0,0 +1,44 @@ +--- +source: compiler-core/src/javascript/tests/custom_types.rs +expression: "\npub type Thing {\n Variant\n Other(String)\n}\n\npub fn process(e: Thing) -> String {\n case e {\n value if value == Other(\"hello\") -> \"match\"\n _ -> \"no match\"\n }\n}\n" +--- +----- SOURCE CODE + +pub type Thing { + Variant + Other(String) +} + +pub fn process(e: Thing) -> String { + case e { + value if value == Other("hello") -> "match" + _ -> "no match" + } +} + + +----- COMPILED JAVASCRIPT +import { CustomType as $CustomType, isEqual } from "../gleam.mjs"; + +export class Variant extends $CustomType {} +export const Thing$Variant = () => new Variant(); +export const Thing$isVariant = (value) => value instanceof Variant; + +export class Other extends $CustomType { + constructor($0) { + super(); + this[0] = $0; + } +} +export const Thing$Other = ($0) => new Other($0); +export const Thing$isOther = (value) => value instanceof Other; +export const Thing$Other$0 = (value) => value[0]; + +export function process(e) { + let value = e; + if (isEqual(value, new Other("hello"))) { + return "match"; + } else { + return "no match"; + } +} From a05544ee2a4f60995ad21bbaddd5b45a60d6cd4b Mon Sep 17 00:00:00 2001 From: Nafi Date: Mon, 6 Oct 2025 18:55:26 +0530 Subject: [PATCH 12/12] refactor --- compiler-core/src/javascript/expression.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/compiler-core/src/javascript/expression.rs b/compiler-core/src/javascript/expression.rs index 4e8bf1f945a..0bd5cc158d3 100644 --- a/compiler-core/src/javascript/expression.rs +++ b/compiler-core/src/javascript/expression.rs @@ -1586,13 +1586,13 @@ impl<'module, 'a> Generator<'module, 'a> { { let left_doc = self .not_in_tail_position(Some(Ordering::Strict), |this| this.wrap_expression(left)); - Some(self.singleton_equal_helper(left_doc, name, should_be_equal)) + Some(self.singleton_equal(left_doc, name, should_be_equal)) } else { None } } - fn singleton_equal_helper( + fn singleton_equal( &self, value: Document<'a>, tag: &EcoString, @@ -2164,7 +2164,7 @@ impl<'module, 'a> Generator<'module, 'a> { && !matches!(&**type_, Type::Fn { .. }) { let left_doc = self.guard(left); - return Some(self.singleton_equal_helper(left_doc, name, should_be_equal)); + return Some(self.singleton_equal(left_doc, name, should_be_equal)); } None }