aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> 2023-05-06 18:23:37 -0700
committerGravatar Jarred Sumner <709451+Jarred-Sumner@users.noreply.github.com> 2023-05-06 18:23:37 -0700
commit4708835ae649c9b1021e0b6686f7c5772f41ef0c (patch)
tree75b41e30afa5a1f84301dd0ff15d1a78886c92ad
parent8975717bc38ecfa9f1bca119cef75515b3e94a7d (diff)
downloadbun-4708835ae649c9b1021e0b6686f7c5772f41ef0c.tar.gz
bun-4708835ae649c9b1021e0b6686f7c5772f41ef0c.tar.zst
bun-4708835ae649c9b1021e0b6686f7c5772f41ef0c.zip
[minifier] Rewrite equality check logic
-rw-r--r--src/js_ast.zig146
-rw-r--r--src/js_parser.zig8
-rw-r--r--test/transpiler/transpiler.test.js2
3 files changed, 129 insertions, 27 deletions
diff --git a/src/js_ast.zig b/src/js_ast.zig
index 8b27cd0a8..a026d793d 100644
--- a/src/js_ast.zig
+++ b/src/js_ast.zig
@@ -1708,11 +1708,11 @@ pub const E = struct {
}
pub fn toStringFromF64Safe(value: f64, allocator: std.mem.Allocator) ?string {
- if (value == @trunc(value)) {
- const int_value = @floatToInt(i64, value);
+ const int_value = @floatToInt(i64, value);
+ if (value == @intToFloat(f64, int_value)) {
const abs = @intCast(u64, std.math.absInt(int_value) catch return null);
if (abs < double_digit.len) {
- return if (value < 0)
+ return if (int_value < 0)
neg_double_digit[abs]
else
double_digit[abs];
@@ -5040,7 +5040,10 @@ pub const Expr = struct {
};
}
- pub const Equality = struct { equal: bool = false, ok: bool = false };
+ pub const Equality = struct {
+ equal: bool = false,
+ ok: bool = false,
+ };
// 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
@@ -5049,36 +5052,133 @@ pub const Expr = struct {
left: Expr.Data,
right: Expr.Data,
allocator: std.mem.Allocator,
+ comptime kind: enum { loose, strict },
) Equality {
+ // https://dorey.github.io/JavaScript-Equality-Table/
var equality = Equality{};
switch (left) {
- .e_null => {
- equality.equal = @as(Expr.Tag, right) == Expr.Tag.e_null;
- equality.ok = equality.equal;
- },
- .e_undefined => {
- equality.ok = @as(Expr.Tag, right) == Expr.Tag.e_undefined;
- equality.equal = equality.ok;
+ .e_null, .e_undefined => {
+ const ok = switch (@as(Expr.Tag, right)) {
+ .e_null, .e_undefined => true,
+ else => @as(Expr.Tag, right).isPrimitiveLiteral(),
+ };
+
+ if (comptime kind == .loose) {
+ return .{
+ .equal = switch (@as(Expr.Tag, right)) {
+ .e_null, .e_undefined => true,
+ else => false,
+ },
+ .ok = ok,
+ };
+ }
+
+ return .{
+ .equal = @as(Tag, right) == @as(Tag, left),
+ .ok = ok,
+ };
},
.e_boolean => |l| {
- equality.ok = @as(Expr.Tag, right) == Expr.Tag.e_boolean;
- equality.equal = equality.ok and l.value == right.e_boolean.value;
+ switch (right) {
+ .e_boolean => {
+ equality.ok = true;
+ equality.equal = l.value == right.e_boolean.value;
+ },
+ .e_number => |num| {
+ if (comptime kind == .strict) {
+ // "true === 1" is false
+ // "false === 0" is false
+ return .{ .ok = true, .equal = false };
+ }
+
+ return .{
+ .ok = true,
+ .equal = if (l.value)
+ num.value == 1
+ else
+ num.value == 0,
+ };
+ },
+ .e_null, .e_undefined => {
+ return .{ .ok = true, .equal = false };
+ },
+ else => {},
+ }
},
.e_number => |l| {
- equality.ok = @as(Expr.Tag, right) == Expr.Tag.e_number;
- equality.equal = equality.ok and l.value == right.e_number.value;
+ switch (right) {
+ .e_number => |r| {
+ return .{
+ .ok = true,
+ .equal = l.value == r.value,
+ };
+ },
+ .e_boolean => |r| {
+ if (comptime kind == .loose) {
+ return .{
+ .ok = true,
+ // "1 == true" is true
+ // "0 == false" is true
+ .equal = if (r.value)
+ l.value == 1
+ else
+ l.value == 0,
+ };
+ }
+
+ // "1 === true" is false
+ // "0 === false" is false
+ return .{ .ok = true, .equal = false };
+ },
+ .e_null, .e_undefined => {
+ // "(not null or undefined) == undefined" is false
+ return .{ .ok = true, .equal = false };
+ },
+ else => {},
+ }
},
.e_big_int => |l| {
- equality.ok = @as(Expr.Tag, right) == Expr.Tag.e_big_int;
- equality.equal = equality.ok and strings.eql(l.value, right.e_big_int.value);
+ if (right == .e_big_int) {
+ equality.ok = true;
+ equality.equal = strings.eql(l.value, l.value);
+ } else {
+ equality.ok = switch (right) {
+ .e_null, .e_undefined => true,
+ else => false,
+ };
+ equality.equal = false;
+ }
},
.e_string => |l| {
- equality.ok = @as(Expr.Tag, right) == Expr.Tag.e_string;
- if (equality.ok) {
- var r = right.e_string;
- r.resovleRopeIfNeeded(allocator);
- l.resovleRopeIfNeeded(allocator);
- equality.equal = r.eql(E.String, l);
+ switch (right) {
+ .e_string => |r| {
+ equality.ok = true;
+ r.resovleRopeIfNeeded(allocator);
+ l.resovleRopeIfNeeded(allocator);
+ equality.equal = r.eql(E.String, l);
+ },
+ .e_null, .e_undefined => {
+ equality.ok = true;
+ equality.equal = false;
+ },
+ .e_number => |r| {
+ if (comptime kind == .loose) {
+ if (r.value == 0 or r.value == 1) {
+ equality.ok = true;
+ equality.equal = if (r.value == 0)
+ l.eqlComptime("0")
+ else if (r.value == 1)
+ l.eqlComptime("1")
+ else
+ unreachable;
+ }
+ } else {
+ equality.ok = true;
+ equality.equal = false;
+ }
+ },
+
+ else => {},
}
},
else => {},
diff --git a/src/js_parser.zig b/src/js_parser.zig
index 9f216e862..0e191f52d 100644
--- a/src/js_parser.zig
+++ b/src/js_parser.zig
@@ -15530,7 +15530,7 @@ fn NewParser_(
// notimpl();
},
.bin_loose_eq => {
- const equality = e_.left.data.eql(e_.right.data, p.allocator);
+ const equality = e_.left.data.eql(e_.right.data, p.allocator, .loose);
if (equality.ok) {
return p.newExpr(
E.Boolean{ .value = equality.equal },
@@ -15544,7 +15544,7 @@ fn NewParser_(
},
.bin_strict_eq => {
- const equality = e_.left.data.eql(e_.right.data, p.allocator);
+ const equality = e_.left.data.eql(e_.right.data, p.allocator, .strict);
if (equality.ok) {
return p.newExpr(E.Boolean{ .value = equality.equal }, expr.loc);
}
@@ -15554,7 +15554,7 @@ fn NewParser_(
// TODO: warn about typeof string
},
.bin_loose_ne => {
- const equality = e_.left.data.eql(e_.right.data, p.allocator);
+ const equality = e_.left.data.eql(e_.right.data, p.allocator, .loose);
if (equality.ok) {
return p.newExpr(E.Boolean{ .value = !equality.equal }, expr.loc);
}
@@ -15568,7 +15568,7 @@ fn NewParser_(
}
},
.bin_strict_ne => {
- const equality = e_.left.data.eql(e_.right.data, p.allocator);
+ const equality = e_.left.data.eql(e_.right.data, p.allocator, .strict);
if (equality.ok) {
return p.newExpr(E.Boolean{ .value = !equality.equal }, expr.loc);
}
diff --git a/test/transpiler/transpiler.test.js b/test/transpiler/transpiler.test.js
index fe40ae66a..2cb46c698 100644
--- a/test/transpiler/transpiler.test.js
+++ b/test/transpiler/transpiler.test.js
@@ -1737,8 +1737,10 @@ export const { dead } = { dead: "hello world!" };
it("rewrite string to length", () => {
expectBunPrinted_(`export const foo = "a".length + "b".length;`, `export const foo = 2`);
+ // check rope string
expectBunPrinted_(`export const foo = ("a" + "b").length;`, `export const foo = 2`);
expectBunPrinted_(
+ // check UTF-16
`export const foo = "😋 Get Emoji — All Emojis to ✂️ Copy and 📋 Paste 👌".length;`,
`export const foo = 52`,
);