Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Comparisons of heterogenous primitive types are not considered for dead code elimination #2394

Closed
hamish-milne opened this issue Jul 18, 2022 · 2 comments

Comments

@hamish-milne
Copy link

echo "if ('foo'=='bar') foo(); else bar();" | yarn esbuild --minify outputs bar(); as expected.

echo "if ('foo'==null) foo(); else bar();" | yarn esbuild --minify outputs "foo"==null?foo():bar(); when we expect it to output bar();, because 'foo'==null is always false.

This occurs when the LHS and RHS of an equality operator are different types, or arrays, or objects. Additionally, only ==, ===, the logical and ternary operators, and property accessors of new object expressions seem to be considered; arithmetic and comparison operators are excluded. While some of these are probably out of scope, it would be helpful to understand which constructs are eliminated and which are not.

@evanw
Copy link
Owner

evanw commented Jul 18, 2022

The code for this is here:

// Returns "equal, ok". If "ok" is false, then nothing is known about the two
// values. If "ok" is true, the equality or inequality of the two values is
// stored in "equal".
func CheckEqualityIfNoSideEffects(left E, right E) (bool, bool) {
if r, ok := right.(*EInlinedEnum); ok {
return CheckEqualityIfNoSideEffects(left, r.Value.Data)
}
switch l := left.(type) {
case *EInlinedEnum:
return CheckEqualityIfNoSideEffects(l.Value.Data, right)
case *ENull:
_, ok := right.(*ENull)
return ok, ok
case *EUndefined:
_, ok := right.(*EUndefined)
return ok, ok
case *EBoolean:
r, ok := right.(*EBoolean)
return ok && l.Value == r.Value, ok
case *ENumber:
r, ok := right.(*ENumber)
return ok && l.Value == r.Value, ok
case *EBigInt:
r, ok := right.(*EBigInt)
return ok && l.Value == r.Value, ok
case *EString:
r, ok := right.(*EString)
return ok && helpers.UTF16EqualsUTF16(l.Value, r.Value), ok
}
return false, false
}

Code folding for loose comparisons hasn't been implemented yet. Unlike JavaScript minifiers written in JavaScript, doing this in Go is non-trivial because you have to carefully and manually implement JavaScript type coercion rules. Right now constant folding for loose comparisons is only done in cases where both sides are primitives and there is no type coercion.

I'm open to implementing some additional rules. The specific algorithm to use as a guide would be this: https://tc39.es/ecma262/#sec-islooselyequal. Which rules are you looking for? Just equality with null?

@Conaclos
Copy link

Loose equality is often discouraged except for null/undefined checks. It seems fine to implement loose equality for these cases. It is pretty straightforward since null/undefined are only loosely equal to null/undefined.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants