diff options
author | 2021-06-09 22:45:31 -0700 | |
---|---|---|
committer | 2021-06-09 22:45:31 -0700 | |
commit | 0e2fb5d13235c111bfe6f65bc9c32836e48f947a (patch) | |
tree | 04b68e3471cfaf12df4b42fd5a64fa9202eba68e /src/js_parser/js_parser.zig | |
parent | df01d4de6ff8dcc9461408ab23be620045786a97 (diff) | |
download | bun-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.zig | 84 |
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 }; }, |