aboutsummaryrefslogtreecommitdiff
path: root/src/js_parser/js_parser.zig
diff options
context:
space:
mode:
authorGravatar Jarred Sumner <jarred@jarredsumner.com> 2021-06-09 22:45:31 -0700
committerGravatar Jarred Sumner <jarred@jarredsumner.com> 2021-06-09 22:45:31 -0700
commit0e2fb5d13235c111bfe6f65bc9c32836e48f947a (patch)
tree04b68e3471cfaf12df4b42fd5a64fa9202eba68e /src/js_parser/js_parser.zig
parentdf01d4de6ff8dcc9461408ab23be620045786a97 (diff)
downloadbun-0e2fb5d13235c111bfe6f65bc9c32836e48f947a.tar.gz
bun-0e2fb5d13235c111bfe6f65bc9c32836e48f947a.tar.zst
bun-0e2fb5d13235c111bfe6f65bc9c32836e48f947a.zip
Fix expression simplification bug
Former-commit-id: f8b7e45f0c1a76c970d701edf46acfec28be5b06
Diffstat (limited to 'src/js_parser/js_parser.zig')
-rw-r--r--src/js_parser/js_parser.zig84
1 files changed, 78 insertions, 6 deletions
diff --git a/src/js_parser/js_parser.zig b/src/js_parser/js_parser.zig
index f43762db5..cb0b25f74 100644
--- a/src/js_parser/js_parser.zig
+++ b/src/js_parser/js_parser.zig
@@ -596,6 +596,46 @@ pub const SideEffects = enum(u2) {
if (__if__.yes.isEmpty() and __if__.no.isEmpty()) {
return simpifyUnusedExpr(p, __if__.test_);
}
+
+ // "foo() ? 1 : bar()" => "foo() || bar()"
+ if (__if__.yes.isEmpty()) {
+ return Expr.joinWithLeftAssociativeOp(
+ .bin_logical_or,
+ __if__.test_,
+ __if__.no,
+ p.allocator,
+ );
+ }
+
+ // "foo() ? bar() : 2" => "foo() && bar()"
+ if (__if__.no.isEmpty()) {
+ return Expr.joinWithLeftAssociativeOp(
+ .bin_logical_or,
+ __if__.test_,
+ __if__.yes,
+ p.allocator,
+ );
+ }
+ },
+ .e_unary => |un| {
+ // These operators must not have any type conversions that can execute code
+ // such as "toString" or "valueOf". They must also never throw any exceptions.
+ switch (un.op) {
+ .un_void, .un_not => {
+ return simpifyUnusedExpr(p, un.value);
+ },
+ .un_typeof => {
+ // "typeof x" must not be transformed into if "x" since doing so could
+ // cause an exception to be thrown. Instead we can just remove it since
+ // "typeof x" is special-cased in the standard to never throw.
+ if (std.meta.activeTag(un.value.data) == .e_identifier) {
+ return null;
+ }
+
+ return simpifyUnusedExpr(p, un.value);
+ },
+ else => {},
+ }
},
.e_call => |call| {
@@ -609,15 +649,38 @@ pub const SideEffects = enum(u2) {
.e_binary => |bin| {
switch (bin.op) {
+ // These operators must not have any type conversions that can execute code
+ // such as "toString" or "valueOf". They must also never throw any exceptions.
+ .bin_strict_eq, .bin_strict_ne, .bin_comma => {
+ return Expr.joinWithComma(
+ simpifyUnusedExpr(p, bin.left) orelse bin.left.toEmpty(),
+ simpifyUnusedExpr(p, bin.right) orelse bin.right.toEmpty(),
+ p.allocator,
+ );
+ },
+
// We can simplify "==" and "!=" even though they can call "toString" and/or
// "valueOf" if we can statically determine that the types of both sides are
// primitives. In that case there won't be any chance for user-defined
// "toString" and/or "valueOf" to be called.
- .bin_loose_eq, .bin_loose_ne => {
+ .bin_loose_eq,
+ .bin_loose_ne,
+ => {
if (isPrimitiveWithSideEffects(bin.left.data) and isPrimitiveWithSideEffects(bin.right.data)) {
return Expr.joinWithComma(simpifyUnusedExpr(p, bin.left) orelse bin.left.toEmpty(), simpifyUnusedExpr(p, bin.right) orelse bin.right.toEmpty(), p.allocator);
}
},
+
+ .bin_logical_and, .bin_logical_or, .bin_nullish_coalescing => {
+ // Preserve short-circuit behavior: the left expression is only unused if
+ // the right expression can be completely removed. Otherwise, the left
+ // expression is important for the branch.
+ bin.right = simpifyUnusedExpr(p, bin.right) orelse bin.right.toEmpty();
+ if (!bin.right.isEmpty()) {
+ return simpifyUnusedExpr(p, bin.left);
+ }
+ },
+
else => {},
}
},
@@ -902,14 +965,23 @@ pub const SideEffects = enum(u2) {
.e_unary => |e| {
switch (e.op) {
// Always number or bigint
- .un_pos, .un_neg, .un_cpl, .un_pre_dec, .un_pre_inc, .un_post_dec, .un_post_inc => {
+ .un_pos,
+ .un_neg,
+ .un_cpl,
+ .un_pre_dec,
+ .un_pre_inc,
+ .un_post_dec,
+ .un_post_inc,
+
+ // Always boolean
+ .un_not,
+ .un_typeof,
+ .un_delete,
+ => {
return Result{ .ok = true, .value = false, .side_effects = SideEffects.could_have_side_effects };
},
- // Always undefined
- .un_not, .un_typeof, .un_delete => {
- return Result{ .value = true, .side_effects = .could_have_side_effects, .ok = true };
- },
+ // Always undefined
.un_void => {
return Result{ .value = true, .side_effects = .could_have_side_effects, .ok = true };
},