Skip to content

Commit 5176fcd

Browse files
authored
fix >>> (unsigned right shift) producing wrong results on Lua 5.3+ (#1703)
* fix >>> (unsigned right shift) producing wrong results on Lua 5.3+ TS >>> is a logical right shift (zero-fills from the left), but TSTL was mapping it to Lua's >> which is arithmetic (sign-extends). This gave wrong results for negative numbers: e.g. -1 >>> 0 should be 4294967295 but produced -1. Fix by masking to unsigned 32-bit first: (left & 0xFFFFFFFF) >> right. The Lua 5.2 (bit32.rshift) and LuaJIT (bit.rshift) paths were already correct. * lint
1 parent c043cdc commit 5176fcd

File tree

3 files changed

+51
-6
lines changed

3 files changed

+51
-6
lines changed

src/transformation/visitors/binary-expression/bit.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,22 @@ export function transformBinaryBitOperation(
7575
case LuaTarget.Lua52:
7676
return transformBinaryBitLibOperation(node, left, right, operator, "bit32");
7777
default:
78+
// Lua 5.3+ `>>` is arithmetic (sign-extending), but TS `>>>` is logical (zero-fill).
79+
// Emit `(left & 0xFFFFFFFF) >> right` to convert to unsigned 32-bit first.
80+
if (operator === ts.SyntaxKind.GreaterThanGreaterThanGreaterThanToken) {
81+
const mask = lua.createBinaryExpression(
82+
left,
83+
lua.createNumericLiteral(0xffffffff, node),
84+
lua.SyntaxKind.BitwiseAndOperator,
85+
node
86+
);
87+
return lua.createBinaryExpression(
88+
lua.createParenthesizedExpression(mask, node),
89+
right,
90+
lua.SyntaxKind.BitwiseRightShiftOperator,
91+
node
92+
);
93+
}
7894
const luaOperator = transformBitOperatorToLuaOperator(context, node, operator);
7995
return lua.createBinaryExpression(left, right, luaOperator, node);
8096
}

test/unit/__snapshots__/expressions.spec.ts.snap

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -374,14 +374,14 @@ return ____exports"
374374
375375
exports[`Bitop [5.3] ("a>>>=b") 1`] = `
376376
"local ____exports = {}
377-
a = a >> b
377+
a = (a & 4294967295) >> b
378378
____exports.__result = a
379379
return ____exports"
380380
`;
381381
382382
exports[`Bitop [5.3] ("a>>>b") 1`] = `
383383
"local ____exports = {}
384-
____exports.__result = a >> b
384+
____exports.__result = (a & 4294967295) >> b
385385
return ____exports"
386386
`;
387387
@@ -445,14 +445,14 @@ return ____exports"
445445
446446
exports[`Bitop [5.4] ("a>>>=b") 1`] = `
447447
"local ____exports = {}
448-
a = a >> b
448+
a = (a & 4294967295) >> b
449449
____exports.__result = a
450450
return ____exports"
451451
`;
452452
453453
exports[`Bitop [5.4] ("a>>>b") 1`] = `
454454
"local ____exports = {}
455-
____exports.__result = a >> b
455+
____exports.__result = (a & 4294967295) >> b
456456
return ____exports"
457457
`;
458458
@@ -516,14 +516,14 @@ return ____exports"
516516
517517
exports[`Bitop [5.5] ("a>>>=b") 1`] = `
518518
"local ____exports = {}
519-
a = a >> b
519+
a = (a & 4294967295) >> b
520520
____exports.__result = a
521521
return ____exports"
522522
`;
523523
524524
exports[`Bitop [5.5] ("a>>>b") 1`] = `
525525
"local ____exports = {}
526-
____exports.__result = a >> b
526+
____exports.__result = (a & 4294967295) >> b
527527
return ____exports"
528528
`;
529529

test/unit/expressions.spec.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,35 @@ test.each(unsupportedIn53And54)("Unsupported bitop 5.4 (%p)", input => {
117117
.expectDiagnosticsToMatchSnapshot([unsupportedRightShiftOperator.code]);
118118
});
119119

120+
// Execution tests: verify >>> produces correct results matching JS semantics
121+
for (const expression of ["-5 >>> 0", "-1 >>> 0", "1 >>> 0", "-1 >>> 16", "255 >>> 4"]) {
122+
util.testEachVersion(`Unsigned right shift execution (${expression})`, () => util.testExpression(expression), {
123+
[tstl.LuaTarget.Universal]: false,
124+
[tstl.LuaTarget.Lua50]: false, // No bit library in WASM runtime
125+
[tstl.LuaTarget.Lua51]: false, // No bit library in WASM runtime
126+
[tstl.LuaTarget.Lua52]: builder => builder.expectToMatchJsResult(),
127+
[tstl.LuaTarget.Lua53]: builder => builder.expectToMatchJsResult(),
128+
[tstl.LuaTarget.Lua54]: builder => builder.expectToMatchJsResult(),
129+
[tstl.LuaTarget.Lua55]: builder => builder.expectToMatchJsResult(),
130+
[tstl.LuaTarget.LuaJIT]: false, // Can't execute LuaJIT in tests
131+
[tstl.LuaTarget.Luau]: false,
132+
});
133+
}
134+
135+
for (const code of ["let a = -5; a >>>= 0; return a;", "let a = -1; a >>>= 16; return a;"]) {
136+
util.testEachVersion(`Unsigned right shift assignment execution (${code})`, () => util.testFunction(code), {
137+
[tstl.LuaTarget.Universal]: false,
138+
[tstl.LuaTarget.Lua50]: false, // No bit library in WASM runtime
139+
[tstl.LuaTarget.Lua51]: false, // No bit library in WASM runtime
140+
[tstl.LuaTarget.Lua52]: builder => builder.expectToMatchJsResult(),
141+
[tstl.LuaTarget.Lua53]: builder => builder.expectToMatchJsResult(),
142+
[tstl.LuaTarget.Lua54]: builder => builder.expectToMatchJsResult(),
143+
[tstl.LuaTarget.Lua55]: builder => builder.expectToMatchJsResult(),
144+
[tstl.LuaTarget.LuaJIT]: false, // Can't execute LuaJIT in tests
145+
[tstl.LuaTarget.Luau]: false,
146+
});
147+
}
148+
120149
test.each(["1+1", "-1+1", "1*30+4", "1*(3+4)", "1*(3+4*2)", "10-(4+5)"])(
121150
"Binary expressions ordering parentheses (%p)",
122151
input => {

0 commit comments

Comments
 (0)