Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion compiler-core/src/analyse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<T> {
Known(T),
#[default]
Unknown,
}

Expand Down
6 changes: 0 additions & 6 deletions compiler-core/src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2478,12 +2478,6 @@ impl<T> BitArraySize<T> {
}
}

impl Default for Inferred<()> {
fn default() -> Self {
Self::Unknown
}
}

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum AssignName {
Variable(EcoString),
Expand Down
109 changes: 100 additions & 9 deletions compiler-core/src/javascript/expression.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1536,14 +1536,75 @@ impl<'module, 'a> Generator<'module, 'a> {
return docvec![left_doc, operator, right_doc];
}

// 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 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;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we extract the duplicated bits to helper methods please 🙏

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

surely

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This hasn't been done yet! The patterns are still duplicated. Could we write something like this?:

if let Some(document) = self.zero_arity_variant_equality(left, right, should_be_equal) {
    return document;
}
if let Some(document) = self.zero_arity_variant_equality(right, left, should_be_equal) {
    return document;
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is still outstanding 🙏

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This still hasn't been done

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Once again, this still needs to be done


// 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 singleton_variant_equality(
&mut self,
left: &'a TypedExpr,
right: &'a TypedExpr,
should_be_equal: bool,
) -> Option<Document<'a>> {
if let TypedExpr::Var {
constructor:
ValueConstructor {
variant: ValueConstructorVariant::Record { arity: 0, name, .. },
..
},
..
} = right
{
let left_doc = self
.not_in_tail_position(Some(Ordering::Strict), |this| this.wrap_expression(left));
Some(self.singleton_equal(left_doc, name, should_be_equal))
} else {
None
}
}

fn singleton_equal(
&self,
value: Document<'a>,
tag: &EcoString,
should_be_equal: bool,
) -> Document<'a> {
if should_be_equal {
docvec![value, " instanceof ", tag.to_doc()]
} else {
docvec!["!(", value, " instanceof ", tag.to_doc(), ")"]
}
}

fn equal_with_doc_operands(
&mut self,
left: Document<'a>,
Expand Down Expand Up @@ -1966,16 +2027,26 @@ impl<'module, 'a> Generator<'module, 'a> {
docvec![left, " !== ", right]
}

ClauseGuard::Equals { left, right, .. } => {
let left = self.guard(left);
let right = self.guard(right);
self.prelude_equal_call(true, left, right)
}
ClauseGuard::Equals { left, right, .. }
| ClauseGuard::NotEquals { left, right, .. } => {
let should_be_equal = matches!(guard, ClauseGuard::Equals { .. });

// Handle singleton equality optimization for guards
if let Some(doc) =
self.singleton_variant_guard_equality(left, right, should_be_equal)
{
return doc;
}

ClauseGuard::NotEquals { left, right, .. } => {
let left = self.guard(left);
let right = self.guard(right);
self.prelude_equal_call(false, left, right)
if let Some(doc) =
self.singleton_variant_guard_equality(right, left, should_be_equal)
{
return doc;
}

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, .. } => {
Expand Down Expand Up @@ -2078,6 +2149,26 @@ impl<'module, 'a> Generator<'module, 'a> {
}
}

fn singleton_variant_guard_equality(
&mut self,
left: &'a TypedClauseGuard,
right: &'a TypedClauseGuard,
should_be_equal: bool,
) -> Option<Document<'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 { .. })
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use the same logic as the expression equality here, where we check the ValueContructor to see if it has zero arity? I think it would make this easier to read and understand

{
let left_doc = self.guard(left);
return Some(self.singleton_equal(left_doc, name, should_be_equal));
}
None
}

fn wrapped_guard(&mut self, guard: &'a TypedClauseGuard) -> Document<'a> {
match guard {
ClauseGuard::Var { .. }
Expand Down
Loading