Skip to content

Commit 16aaf56

Browse files
re-masashilpil
authored andcommitted
Optimise comparison with singleton custom types on JavaScript #4903
1 parent f0ae752 commit 16aaf56

File tree

20 files changed

+851
-13
lines changed

20 files changed

+851
-13
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@
2929

3030
([Surya Rose](https://github.com/GearsDatapacks))
3131

32+
- The performance of `==` and `!=` has been improved for single-variant custom types
33+
compared using `instanceof` instead of `isEqual(new Variant())`).
34+
([Nafi](https://github.com/re-masashi))
35+
3236
- The lowercase bool pattern error is no longer a syntax error, but instead a
3337
part of the analysis step. This allows the entire module to be analyzed, rather
3438
than stopping at the syntax error.

compiler-core/src/javascript/expression.rs

Lines changed: 99 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1536,14 +1536,75 @@ impl<'module, 'a> Generator<'module, 'a> {
15361536
return docvec![left_doc, operator, right_doc];
15371537
}
15381538

1539+
// For comparison with singleton custom types, ie, one with no fields.
1540+
// If you have some code like this
1541+
// ```gleam
1542+
// pub type Wibble {
1543+
// Wibble
1544+
// Wobble
1545+
// }
1546+
1547+
// pub fn is_wibble(w: Wibble) -> Bool {
1548+
// w == Wibble
1549+
// }
1550+
// ```
1551+
// Instead of `isEqual(w, new Wibble())`, generate `w instanceof Wibble`
1552+
// because the first approach needs to construct a new Wibble, and then call the isEqual function,
1553+
// which supports any shape of data, and so does a lot of extra logic which isn't necessary.
1554+
1555+
if let Some(doc) = self.singleton_variant_equality(left, right, should_be_equal) {
1556+
return doc;
1557+
}
1558+
1559+
if let Some(doc) = self.singleton_variant_equality(right, left, should_be_equal) {
1560+
return doc;
1561+
}
1562+
15391563
// Other types must be compared using structural equality
15401564
let left =
15411565
self.not_in_tail_position(Some(Ordering::Strict), |this| this.wrap_expression(left));
15421566
let right =
15431567
self.not_in_tail_position(Some(Ordering::Strict), |this| this.wrap_expression(right));
1568+
15441569
self.prelude_equal_call(should_be_equal, left, right)
15451570
}
15461571

1572+
fn singleton_variant_equality(
1573+
&mut self,
1574+
left: &'a TypedExpr,
1575+
right: &'a TypedExpr,
1576+
should_be_equal: bool,
1577+
) -> Option<Document<'a>> {
1578+
if let TypedExpr::Var {
1579+
constructor:
1580+
ValueConstructor {
1581+
variant: ValueConstructorVariant::Record { arity: 0, name, .. },
1582+
..
1583+
},
1584+
..
1585+
} = right
1586+
{
1587+
let left_doc = self
1588+
.not_in_tail_position(Some(Ordering::Strict), |this| this.wrap_expression(left));
1589+
Some(self.singleton_equal(left_doc, name, should_be_equal))
1590+
} else {
1591+
None
1592+
}
1593+
}
1594+
1595+
fn singleton_equal(
1596+
&self,
1597+
value: Document<'a>,
1598+
tag: &EcoString,
1599+
should_be_equal: bool,
1600+
) -> Document<'a> {
1601+
if should_be_equal {
1602+
docvec![value, " instanceof ", tag.to_doc()]
1603+
} else {
1604+
docvec!["!(", value, " instanceof ", tag.to_doc(), ")"]
1605+
}
1606+
}
1607+
15471608
fn equal_with_doc_operands(
15481609
&mut self,
15491610
left: Document<'a>,
@@ -1966,16 +2027,26 @@ impl<'module, 'a> Generator<'module, 'a> {
19662027
docvec![left, " !== ", right]
19672028
}
19682029

1969-
ClauseGuard::Equals { left, right, .. } => {
1970-
let left = self.guard(left);
1971-
let right = self.guard(right);
1972-
self.prelude_equal_call(true, left, right)
1973-
}
2030+
ClauseGuard::Equals { left, right, .. }
2031+
| ClauseGuard::NotEquals { left, right, .. } => {
2032+
let should_be_equal = matches!(guard, ClauseGuard::Equals { .. });
2033+
2034+
// Handle singleton equality optimization for guards
2035+
if let Some(doc) =
2036+
self.singleton_variant_guard_equality(left, right, should_be_equal)
2037+
{
2038+
return doc;
2039+
}
19742040

1975-
ClauseGuard::NotEquals { left, right, .. } => {
1976-
let left = self.guard(left);
1977-
let right = self.guard(right);
1978-
self.prelude_equal_call(false, left, right)
2041+
if let Some(doc) =
2042+
self.singleton_variant_guard_equality(right, left, should_be_equal)
2043+
{
2044+
return doc;
2045+
}
2046+
2047+
let left_doc = self.guard(left);
2048+
let right_doc = self.guard(right);
2049+
self.prelude_equal_call(should_be_equal, left_doc, right_doc)
19792050
}
19802051

19812052
ClauseGuard::GtFloat { left, right, .. } | ClauseGuard::GtInt { left, right, .. } => {
@@ -2078,6 +2149,25 @@ impl<'module, 'a> Generator<'module, 'a> {
20782149
}
20792150
}
20802151

2152+
fn singleton_variant_guard_equality(
2153+
&mut self,
2154+
left: &'a TypedClauseGuard,
2155+
right: &'a TypedClauseGuard,
2156+
should_be_equal: bool,
2157+
) -> Option<Document<'a>> {
2158+
if let ClauseGuard::Constant(Constant::Record {
2159+
record_constructor: Some(constructor),
2160+
name,
2161+
..
2162+
}) = right
2163+
&& let ValueConstructorVariant::Record { arity: 0, .. } = constructor.variant
2164+
{
2165+
let left_doc = self.guard(left);
2166+
return Some(self.singleton_equal(left_doc, name, should_be_equal));
2167+
}
2168+
None
2169+
}
2170+
20812171
fn wrapped_guard(&mut self, guard: &'a TypedClauseGuard) -> Document<'a> {
20822172
match guard {
20832173
ClauseGuard::Var { .. }

0 commit comments

Comments
 (0)