Skip to content

Commit 80d3169

Browse files
authored
fix: emit bxor, not bnot, for binary '~' at gen-target 5.1 (#1168)
The '~' token is both unary bitwise NOT and binary bitwise XOR, and the 5.1 compat pass matched on the operator token alone. Binary '~' was rewritten to bit32.bnot(a), dropping the right operand and changing the operation; a __bxor metamethod call was dispatched to __bnot. Check the arity so binary '~' falls through to the existing bit_operators branch. spec/util.lua built its line arrays with gmatch("([^\n]*)\n"), which drops the final line, so a util.gen mismatch confined to the last line of generated output was never reported. That hid the metamethod half of this bug from the new test and from the existing __bnot metamethod test. Co-authored-by: Dylan Pulver <dylanpulver@users.noreply.github.com>
1 parent 35c55ea commit 80d3169

6 files changed

Lines changed: 60 additions & 6 deletions

File tree

spec/lang/compat/lua_versions_spec.lua

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,20 @@ describe("Lua version compatibility", function()
3030
local x = bit32.band(2, (bit32.bor(bit32.rshift(c, bit32.bnot(4)), 0xff)))
3131
]], "5.1"))
3232

33+
it("distinguishes unary and binary '~' operators", util.gen([[
34+
35+
36+
local c = 0xcafebabe
37+
local x = c ~ 0xff
38+
local y = ~c ~ c
39+
]], [[
40+
local bit32 = bit32; if not bit32 then local p, m = pcall(require, 'bit32'); if p then bit32 = m end end
41+
42+
local c = 0xcafebabe
43+
local x = bit32.bxor(c, 0xff)
44+
local y = bit32.bxor(bit32.bnot(c), c)
45+
]], "5.1"))
46+
3347
it("generates compat code for bitwise unary operator metamethods", util.gen([[
3448
3549
local type Rec = record
@@ -100,6 +114,42 @@ describe("Lua version compatibility", function()
100114
print(_tl_mt("__shl", 1, r, s))
101115
]], "5.1"))
102116

117+
it("generates compat code for the binary '~' operator metamethod", util.gen([[
118+
119+
local type Rec = record
120+
x: number
121+
metamethod __bxor: function(Rec, Rec): number
122+
end
123+
124+
local rec_mt: metatable<Rec> = {
125+
__bxor = function(a: Rec, b: Rec): number
126+
return a.x + b.x
127+
end
128+
}
129+
130+
local r = setmetatable({} as Rec, rec_mt)
131+
local s = setmetatable({} as Rec, rec_mt)
132+
133+
print(r ~ s)
134+
]], [[
135+
local _tl_mt = function(m, s, a, b) return (getmetatable(s == 1 and a or b)[m](a, b)) end
136+
137+
138+
139+
140+
141+
local rec_mt = {
142+
__bxor = function(a, b)
143+
return a.x + b.x
144+
end,
145+
}
146+
147+
local r = setmetatable({}, rec_mt)
148+
local s = setmetatable({}, rec_mt)
149+
150+
print(_tl_mt("__bxor", 1, r, s))
151+
]], "5.1"))
152+
103153
-- varargs
104154
it("generates compatibility code for named varargs with Lua 5.1", util.gen([[
105155

spec/util.lua

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -743,12 +743,12 @@ local function gen(lax, code, expected, gen_target, type_errors)
743743
local expected_code = util.dedent(expected):match("^(.-)%s*$")
744744

745745
local expected_lines = {}
746-
for line in expected_code:gmatch("([^\n]*)\n") do
746+
for line in expected_code:gmatch("([^\n]*)\n?") do
747747
table.insert(expected_lines, line)
748748
end
749749

750750
local output_lines = {}
751-
for line in output_code:gmatch("([^\n]*)\n") do
751+
for line in output_code:gmatch("([^\n]*)\n?") do
752752
table.insert(output_lines, line)
753753
end
754754

teal.lua

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11277,7 +11277,8 @@ local function adjust_code(ast, needs_compat, gen_compat, gen_target)
1127711277
needs_compat[key] = true
1127811278
end
1127911279
end
11280-
elseif node.op.op == "~" and gen_target == "5.1" then
11280+
11281+
elseif node.op.op == "~" and node.op.arity == 1 and gen_target == "5.1" then
1128111282
if node.op.meta_on_operand then
1128211283
needs_compat["mt"] = true
1128311284
convert_node_to_compat_mt_call(node, unop_to_metamethod[node.op.op], 1, node.e1)

teal/gen/lua_compat.lua

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,8 @@ local function adjust_code(ast, needs_compat, gen_compat, gen_target)
231231
needs_compat[key] = true
232232
end
233233
end
234-
elseif node.op.op == "~" and gen_target == "5.1" then
234+
235+
elseif node.op.op == "~" and node.op.arity == 1 and gen_target == "5.1" then
235236
if node.op.meta_on_operand then
236237
needs_compat["mt"] = true
237238
convert_node_to_compat_mt_call(node, unop_to_metamethod[node.op.op], 1, node.e1)

teal/gen/lua_compat.tl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,8 @@ local function adjust_code(ast: Node, needs_compat: {string:boolean}, gen_compat
231231
needs_compat[key] = true
232232
end
233233
end
234-
elseif node.op.op == "~" and gen_target == "5.1" then
234+
-- unary '~' only; binary '~' is bxor, handled below
235+
elseif node.op.op == "~" and node.op.arity == 1 and gen_target == "5.1" then
235236
if node.op.meta_on_operand then
236237
needs_compat["mt"] = true
237238
convert_node_to_compat_mt_call(node, unop_to_metamethod[node.op.op], 1, node.e1)

tl.lua

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11531,7 +11531,8 @@ local function adjust_code(ast, needs_compat, gen_compat, gen_target)
1153111531
needs_compat[key] = true
1153211532
end
1153311533
end
11534-
elseif node.op.op == "~" and gen_target == "5.1" then
11534+
11535+
elseif node.op.op == "~" and node.op.arity == 1 and gen_target == "5.1" then
1153511536
if node.op.meta_on_operand then
1153611537
needs_compat["mt"] = true
1153711538
convert_node_to_compat_mt_call(node, unop_to_metamethod[node.op.op], 1, node.e1)

0 commit comments

Comments
 (0)