Skip to content

Commit ec8ba63

Browse files
committed
Fix all remaining negative zero bugs
1 parent d7a5f1c commit ec8ba63

File tree

57 files changed

+223
-212
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

57 files changed

+223
-212
lines changed

src/compiler/jsexecute.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,17 @@ runtimeFunctions.retire = `const retire = () => {
223223
thread.target.runtime.sequencer.retireThread(thread);
224224
}`;
225225

226+
/**
227+
* Converts NaN to zero. Used to match Scratch's string-to-number.
228+
* Unlike (x || 0), -0 stays as -0 and is not converted to 0.
229+
* This function is written in this specific way to make it easy for browsers to inline.
230+
* We've found that calling isNaN() causes slowdowns in Firefox, so instead we utilize the
231+
* fact that NaN is the only JavaScript value that does not equal itself.
232+
* @param {number} value A number. Might be NaN.
233+
* @returns {number} A number. Never NaN.
234+
*/
235+
runtimeFunctions.toNotNaN = `const toNotNaN = value => value === value ? value : 0`;
236+
226237
/**
227238
* Scratch cast to boolean.
228239
* Similar to Cast.toBoolean()

src/compiler/jsgen.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -185,9 +185,9 @@ class JSGenerator {
185185
return `(+${this.descendInput(node.target.toType(InputType.BOOLEAN))})`;
186186
}
187187
if (node.target.isAlwaysType(InputType.NUMBER_OR_NAN)) {
188-
return `(${this.descendInput(node.target)} || 0)`;
188+
return `toNotNaN(${this.descendInput(node.target)})`;
189189
}
190-
return `(+${this.descendInput(node.target)} || 0)`;
190+
return `toNotNaN(+${this.descendInput(node.target)})`;
191191
case InputOpcode.CAST_NUMBER_OR_NAN:
192192
return `(+${this.descendInput(node.target)})`;
193193
case InputOpcode.CAST_NUMBER_INDEX:

test/snapshot/__snapshots__/order-library-reverse.sb3.tw-snapshot

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,14 @@ const b2 = runtime.getOpcodeFunction("looks_say");
99
return function* genXYZ () {
1010
b0.value = 0;
1111
thread.timer = timer();
12-
var a0 = Math.max(0, 1000 * (+b1.value || 0));
12+
var a0 = Math.max(0, 1000 * toNotNaN(+b1.value));
1313
runtime.requestRedraw();
1414
yield;
1515
while (thread.timer.timeElapsed() < a0) {
1616
yield;
1717
}
1818
thread.timer = null;
19-
b0.value = ((+b0.value || 0) + 1);
19+
b0.value = (toNotNaN(+b0.value) + 1);
2020
if ((b0.value === 1)) {
2121
yield* executeInCompatibilityLayer({"MESSAGE":"pass order is correct (1)",}, b2, false, false, "]4hbk*5ix]V00h|!x1oy", null);
2222
} else {
@@ -32,23 +32,23 @@ const b1 = stage.variables["p]KODv+)+:l=%NT~j3/d-wait"];
3232
return function* genXYZ () {
3333
yield* executeInCompatibilityLayer({"MESSAGE":"plan 2",}, b0, false, false, "1Ba%a0GIK#hwJ46y=WVt", null);
3434
thread.timer = timer();
35-
var a0 = Math.max(0, 1000 * (+b1.value || 0));
35+
var a0 = Math.max(0, 1000 * toNotNaN(+b1.value));
3636
runtime.requestRedraw();
3737
yield;
3838
while (thread.timer.timeElapsed() < a0) {
3939
yield;
4040
}
4141
thread.timer = null;
4242
thread.timer = timer();
43-
var a1 = Math.max(0, 1000 * (+b1.value || 0));
43+
var a1 = Math.max(0, 1000 * toNotNaN(+b1.value));
4444
runtime.requestRedraw();
4545
yield;
4646
while (thread.timer.timeElapsed() < a1) {
4747
yield;
4848
}
4949
thread.timer = null;
5050
thread.timer = timer();
51-
var a2 = Math.max(0, 1000 * (+b1.value || 0));
51+
var a2 = Math.max(0, 1000 * toNotNaN(+b1.value));
5252
runtime.requestRedraw();
5353
yield;
5454
while (thread.timer.timeElapsed() < a2) {
@@ -67,14 +67,14 @@ const b2 = runtime.getOpcodeFunction("looks_say");
6767
return function* genXYZ () {
6868
b0.value = 0;
6969
thread.timer = timer();
70-
var a0 = Math.max(0, 1000 * (+b1.value || 0));
70+
var a0 = Math.max(0, 1000 * toNotNaN(+b1.value));
7171
runtime.requestRedraw();
7272
yield;
7373
while (thread.timer.timeElapsed() < a0) {
7474
yield;
7575
}
7676
thread.timer = null;
77-
b0.value = ((+b0.value || 0) + 1);
77+
b0.value = (toNotNaN(+b0.value) + 1);
7878
if ((b0.value === 2)) {
7979
yield* executeInCompatibilityLayer({"MESSAGE":"pass order is correct (2)",}, b2, false, false, "0i[-T:vYTt=bi47@byUE", null);
8080
} else {

test/snapshot/__snapshots__/order-library.sb3.tw-snapshot

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,23 +8,23 @@ const b1 = stage.variables["):/PVGTvoVRvq(ikGwRE-wait"];
88
return function* genXYZ () {
99
yield* executeInCompatibilityLayer({"MESSAGE":"plan 2",}, b0, false, false, "s+@:|^WPr8]N1Y9Hk2f5", null);
1010
thread.timer = timer();
11-
var a0 = Math.max(0, 1000 * (+b1.value || 0));
11+
var a0 = Math.max(0, 1000 * toNotNaN(+b1.value));
1212
runtime.requestRedraw();
1313
yield;
1414
while (thread.timer.timeElapsed() < a0) {
1515
yield;
1616
}
1717
thread.timer = null;
1818
thread.timer = timer();
19-
var a1 = Math.max(0, 1000 * (+b1.value || 0));
19+
var a1 = Math.max(0, 1000 * toNotNaN(+b1.value));
2020
runtime.requestRedraw();
2121
yield;
2222
while (thread.timer.timeElapsed() < a1) {
2323
yield;
2424
}
2525
thread.timer = null;
2626
thread.timer = timer();
27-
var a2 = Math.max(0, 1000 * (+b1.value || 0));
27+
var a2 = Math.max(0, 1000 * toNotNaN(+b1.value));
2828
runtime.requestRedraw();
2929
yield;
3030
while (thread.timer.timeElapsed() < a2) {
@@ -43,14 +43,14 @@ const b2 = runtime.getOpcodeFunction("looks_say");
4343
return function* genXYZ () {
4444
b0.value = 0;
4545
thread.timer = timer();
46-
var a0 = Math.max(0, 1000 * (+b1.value || 0));
46+
var a0 = Math.max(0, 1000 * toNotNaN(+b1.value));
4747
runtime.requestRedraw();
4848
yield;
4949
while (thread.timer.timeElapsed() < a0) {
5050
yield;
5151
}
5252
thread.timer = null;
53-
b0.value = ((+b0.value || 0) + 1);
53+
b0.value = (toNotNaN(+b0.value) + 1);
5454
if ((b0.value === 1)) {
5555
yield* executeInCompatibilityLayer({"MESSAGE":"pass order is correct (1)",}, b2, false, false, "RSQ{nVCc)6E)(`KlnFCF", null);
5656
} else {
@@ -67,14 +67,14 @@ const b2 = runtime.getOpcodeFunction("looks_say");
6767
return function* genXYZ () {
6868
b0.value = 0;
6969
thread.timer = timer();
70-
var a0 = Math.max(0, 1000 * (+b1.value || 0));
70+
var a0 = Math.max(0, 1000 * toNotNaN(+b1.value));
7171
runtime.requestRedraw();
7272
yield;
7373
while (thread.timer.timeElapsed() < a0) {
7474
yield;
7575
}
7676
thread.timer = null;
77-
b0.value = ((+b0.value || 0) + 1);
77+
b0.value = (toNotNaN(+b0.value) + 1);
7878
if ((b0.value === 2)) {
7979
yield* executeInCompatibilityLayer({"MESSAGE":"pass order is correct (2)",}, b2, false, false, "KP?op(=Vg2#;@]!,C#.~", null);
8080
} else {

test/snapshot/__snapshots__/tw-NaN.sb3.tw-snapshot

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,61 +12,61 @@ yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "aA",
1212
if ((("" + (0 * Infinity)).toLowerCase() === "NaN".toLowerCase())) {
1313
yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "/", null);
1414
}
15-
if (((((0 * Infinity) || 0) * 1) === 0)) {
15+
if (((toNotNaN((0 * Infinity)) * 1) === 0)) {
1616
yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "?", null);
1717
}
1818
if ((("" + ((Math.acos(1.01) * 180) / Math.PI)).toLowerCase() === "NaN".toLowerCase())) {
1919
yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "=", null);
2020
}
21-
if ((((((Math.acos(1.01) * 180) / Math.PI) || 0) * 1) === 0)) {
21+
if (((toNotNaN(((Math.acos(1.01) * 180) / Math.PI)) * 1) === 0)) {
2222
yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "]", null);
2323
}
2424
if ((("" + ((Math.asin(1.01) * 180) / Math.PI)).toLowerCase() === "NaN".toLowerCase())) {
2525
yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "_", null);
2626
}
27-
if ((((((Math.asin(1.01) * 180) / Math.PI) || 0) * 1) === 0)) {
27+
if (((toNotNaN(((Math.asin(1.01) * 180) / Math.PI)) * 1) === 0)) {
2828
yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "{", null);
2929
}
3030
if ((("" + (0 / 0)).toLowerCase() === "NaN".toLowerCase())) {
3131
yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "}", null);
3232
}
33-
if (((((0 / 0) || 0) * 1) === 0)) {
33+
if (((toNotNaN((0 / 0)) * 1) === 0)) {
3434
yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "aa", null);
3535
}
3636
if ((("" + Math.sqrt(-1)).toLowerCase() === "NaN".toLowerCase())) {
3737
yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "ac", null);
3838
}
39-
if ((((Math.sqrt(-1) || 0) * 1) === 0)) {
39+
if (((toNotNaN(Math.sqrt(-1)) * 1) === 0)) {
4040
yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "ae", null);
4141
}
4242
if ((("" + mod(0, 0)).toLowerCase() === "NaN".toLowerCase())) {
4343
yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "ag", null);
4444
}
45-
if ((((mod(0, 0) || 0) * 1) === 0)) {
45+
if (((toNotNaN(mod(0, 0)) * 1) === 0)) {
4646
yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "ai", null);
4747
}
4848
if ((("" + Math.log(-1)).toLowerCase() === "NaN".toLowerCase())) {
4949
yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "ak", null);
5050
}
51-
if ((((Math.log(-1) || 0) * 1) === 0)) {
51+
if (((toNotNaN(Math.log(-1)) * 1) === 0)) {
5252
yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "am", null);
5353
}
5454
if ((("" + (Math.log(-1) / Math.LN10)).toLowerCase() === "NaN".toLowerCase())) {
5555
yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "ao", null);
5656
}
57-
if (((((Math.log(-1) / Math.LN10) || 0) * 1) === 0)) {
57+
if (((toNotNaN((Math.log(-1) / Math.LN10)) * 1) === 0)) {
5858
yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "aq", null);
5959
}
60-
if (((((Math.round(Math.sin((Math.PI * (1 / 0)) / 180) * 1e10) / 1e10) || 0) * 1) === 0)) {
60+
if (((toNotNaN((Math.round(Math.sin((Math.PI * (1 / 0)) / 180) * 1e10) / 1e10)) * 1) === 0)) {
6161
yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "as", null);
6262
}
63-
if (((((Math.round(Math.cos((Math.PI * (1 / 0)) / 180) * 1e10) / 1e10) || 0) * 1) === 0)) {
63+
if (((toNotNaN((Math.round(Math.cos((Math.PI * (1 / 0)) / 180) * 1e10) / 1e10)) * 1) === 0)) {
6464
yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "au", null);
6565
}
66-
if ((((tan((1 / 0)) || 0) * 1) === 0)) {
66+
if (((toNotNaN(tan((1 / 0))) * 1) === 0)) {
6767
yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "aw", null);
6868
}
69-
if ((((runtime.ext_scratch3_operators._random((-1 / 0), (1 / 0)) || 0) * 1) === 0)) {
69+
if (((toNotNaN(runtime.ext_scratch3_operators._random((-1 / 0), (1 / 0))) * 1) === 0)) {
7070
yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "ax", null);
7171
}
7272
yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, ":", null);

test/snapshot/__snapshots__/tw-block-with-null-for-variable-id.sb3.tw-snapshot

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ retire(); return;
2121
const b0 = stage.variables["`jEk@4|i[#Fk?(8x)AV.-my variable"];
2222
const b1 = runtime.getOpcodeFunction("looks_say");
2323
return function* genXYZ () {
24-
if (((+b0.value || 0) === 1)) {
24+
if ((toNotNaN(+b0.value) === 1)) {
2525
yield* executeInCompatibilityLayer({"MESSAGE":"pass variable 1",}, b1, false, false, "m", null);
2626
}
2727
retire(); return;
@@ -32,7 +32,7 @@ retire(); return;
3232
const b0 = stage.variables[")|GMR5fz;%F_H,c0wGVM"];
3333
const b1 = runtime.getOpcodeFunction("looks_say");
3434
return function* genXYZ () {
35-
if (((+b0.value || 0) === 2)) {
35+
if ((toNotNaN(+b0.value) === 2)) {
3636
yield* executeInCompatibilityLayer({"MESSAGE":"pass variable 2",}, b1, false, false, "q", null);
3737
}
3838
retire(); return;

test/snapshot/__snapshots__/tw-change-size-does-not-use-rounded-size.sb3.tw-snapshot

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,11 @@ yield* executeInCompatibilityLayer({"MESSAGE":"plan 1",}, b0, false, false, "()*
1010
target.setSize(96);
1111
b1.value = 0;
1212
while (!(100 === Math.round(target.size))) {
13-
b1.value = ((+b1.value || 0) + 1);
13+
b1.value = (toNotNaN(+b1.value) + 1);
1414
target.setSize(target.size + ((100 - Math.round(target.size)) / 10));
1515
yield;
1616
}
17-
if (((+b1.value || 0) === 20)) {
17+
if ((toNotNaN(+b1.value) === 20)) {
1818
yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "FPDFR?Wwq)kLj0A$0D{@", null);
1919
}
2020
yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, "1,vLoJ4OQBv+Q#$VoYf=", null);

test/snapshot/__snapshots__/tw-comparison-matrix-runtime.sb3.tw-snapshot

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,19 +24,19 @@ thread.procedures["Wsetup values"]();
2424
b0.value = 0;
2525
b1.value = 0;
2626
for (var a0 = b2.value.length; a0 >= 0.5; a0--) {
27-
b1.value = ((+b1.value || 0) + 1);
27+
b1.value = (toNotNaN(+b1.value) + 1);
2828
b3.value = 0;
2929
for (var a1 = b2.value.length; a1 >= 0.5; a1--) {
30-
b3.value = ((+b3.value || 0) + 1);
31-
b0.value = ((+b0.value || 0) + 1);
30+
b3.value = (toNotNaN(+b3.value) + 1);
31+
b0.value = (toNotNaN(+b0.value) + 1);
3232
if (!compareEqual(compareGreaterThan(listGet(b2.value, b1.value), (b2.value[(b3.value | 0) - 1] ?? "")), (b4.value[(b0.value | 0) - 1] ?? ""))) {
3333
yield* executeInCompatibilityLayer({"MESSAGE":("fail " + (("" + listGet(b2.value, b1.value)) + (" should be > " + ("" + (b2.value[(b3.value | 0) - 1] ?? ""))))),}, b5, true, false, "]", null);
3434
}
35-
b0.value = ((+b0.value || 0) + 1);
35+
b0.value = (toNotNaN(+b0.value) + 1);
3636
if (!compareEqual(compareEqual(listGet(b2.value, b1.value), listGet(b2.value, b3.value)), (b4.value[(b0.value | 0) - 1] ?? ""))) {
3737
yield* executeInCompatibilityLayer({"MESSAGE":("fail " + (("" + listGet(b2.value, b1.value)) + (" should be = " + ("" + listGet(b2.value, b3.value))))),}, b5, true, false, "|", null);
3838
}
39-
b0.value = ((+b0.value || 0) + 1);
39+
b0.value = (toNotNaN(+b0.value) + 1);
4040
if (!compareEqual(compareLessThan(listGet(b2.value, b1.value), listGet(b2.value, b3.value)), (b4.value[(b0.value | 0) - 1] ?? ""))) {
4141
yield* executeInCompatibilityLayer({"MESSAGE":("fail " + (("" + listGet(b2.value, b1.value)) + (" should be < " + ("" + listGet(b2.value, b3.value))))),}, b5, true, true, "ab", null);
4242
if (hasResumedFromPromise) {hasResumedFromPromise = false;continue;}

test/snapshot/__snapshots__/tw-compatibility-layer-type-barrier.sb3.tw-snapshot

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ return function* genXYZ () {
1010
yield* executeInCompatibilityLayer({"MESSAGE":"plan 1",}, b0, false, false, "c", null);
1111
b1.value = (0 + 0);
1212
yield* executeInCompatibilityLayer({"MESSAGE":"Hello!","SECS":0.1,}, b2, false, false, "d", null);
13-
if ((((+b1.value || 0) + 2) === 2)) {
13+
if (((toNotNaN(+b1.value) + 2) === 2)) {
1414
yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "i", null);
1515
}
1616
yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, "m", null);

test/snapshot/__snapshots__/tw-custom-report-repeat.sb3.tw-snapshot

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,11 @@ const b1 = stage.variables["`jEk@4|i[#Fk?(8x)AV.-my variable"];
88
return function* genXYZ () {
99
yield* executeInCompatibilityLayer({"MESSAGE":"plan 1",}, b0, false, false, "e", null);
1010
b1.value = 0;
11-
for (var a0 = (+thread.procedures["Zblock name"]() || 0); a0 >= 0.5; a0--) {
12-
b1.value = ((+b1.value || 0) + 1);
11+
for (var a0 = toNotNaN(+thread.procedures["Zblock name"]()); a0 >= 0.5; a0--) {
12+
b1.value = (toNotNaN(+b1.value) + 1);
1313
yield;
1414
}
15-
if (((+b1.value || 0) === 40)) {
15+
if ((toNotNaN(+b1.value) === 40)) {
1616
yield* executeInCompatibilityLayer({"MESSAGE":"pass",}, b0, false, false, "n", null);
1717
}
1818
yield* executeInCompatibilityLayer({"MESSAGE":"end",}, b0, false, false, "l", null);

0 commit comments

Comments
 (0)