diff --git a/docs/compiler_options.md b/docs/compiler_options.md index 63e11eaf3..e0178319c 100644 --- a/docs/compiler_options.md +++ b/docs/compiler_options.md @@ -23,7 +23,7 @@ return { | `-l --require` | | `{string}` | `run` | Require a module prior to executing the script. This is similar in behavior to the `-l` flag in the Lua interpreter. | | `-I --include-dir` | `include_dir` | `{string}` | `build` `check` `gen` `run` | Prepend this directory to the module search path. | `--gen-compat` | `gen_compat` | `string` | `build` `gen` `run` | Generate compatibility code for targeting different Lua VM versions. See [below](#generated-code) for details. -| `--gen-target` | `gen_target` | `string` | `build` `gen` `run` | Minimum targeted Lua version for generated code. Options are `5.1`, `5.3` and `5.4`. See [below](#generated-code) for details. +| `--gen-target` | `gen_target` | `string` | `build` `gen` `run` | Minimum targeted Lua version for generated code. Options are `5.1`, `5.2`, `5.3` and `5.4`. See [below](#generated-code) for details. || `include` | `{string}` | `build` | The set of files to compile/check. See below for details on patterns. || `exclude` | `{string}` | `build` | The set of files to exclude. See below for details on patterns. | `-s --source-dir` | `source_dir` | `string` | `build` | Set the directory to be searched for files. `build` will compile every .tl file in every subdirectory by default. @@ -55,6 +55,10 @@ choose what is the minimum Lua version you want to target. Valid options are `5.1` (for Lua 5.1 and above, including LuaJIT) and `5.3` for Lua 5.3 and above. Using `5.1`, Teal will generate compatibility code for the integer division operator, +a compatibility forward declaration for `table.unpack` and will use the `bit` +library (from LuaJIT or the luabitop compat module) for bitwise operators. + +Using `5.2`, Teal will generate compatibility code for the integer division operator, a compatibility forward declaration for `table.unpack` and will use the `bit32` library for bitwise operators. diff --git a/docs/tutorial.md b/docs/tutorial.md index 3acb20680..6c2b72e14 100644 --- a/docs/tutorial.md +++ b/docs/tutorial.md @@ -1045,7 +1045,8 @@ your own. The Teal compiler also supports Lua-5.3-style bitwise operators (`&`, `|`, `~`, `<<`, `>>`) and the integer division `//` operator on all supported Lua versions. For Lua versions that do not support it natively, it generates code -using the bit32 library, which is also included in compat-5.3 for Lua 5.1. +using the bit32 library for Lua 5.2, and using the bit library for Lua 5.1 +(included in LuaJIT or from the luabitop compat module). You can explicitly disable the use of compat-5.3 with the `--skip-compat53` flag and equivalent option in `tlconfig.lua`. However, if you do so, the Lua diff --git a/spec/cli/gen_spec.lua b/spec/cli/gen_spec.lua index 2d79e03ad..c8b511b0b 100644 --- a/spec/cli/gen_spec.lua +++ b/spec/cli/gen_spec.lua @@ -197,6 +197,48 @@ describe("tl gen", function() util.assert_popen_close(0, pd:close()) local lua_name = tl_to_lua(name) assert.match("Wrote: " .. lua_name, output, 1, true) + util.assert_line_by_line([[ + local bit = bit; if not bit then local p, m = pcall(require, 'bit'); if p then bit = m end end + local x = math.floor(2 / 3) + local y = bit.lshift(2, 3) + ]], util.read_file(lua_name)) + end) + + it("generates bit operations even for invalid variables (regression test for #673)", function() + local name = util.write_tmp_file(finally, [[ + + local foo = require("nonexisting") + local y = 2 | (foo.wat << 9) + local x = ~y + local z = aa // bb + ]]) + local pd = io.popen(util.tl_cmd("gen", "--gen-target=5.1", name), "r") + local output = pd:read("*a") + util.assert_popen_close(0, pd:close()) + local lua_name = tl_to_lua(name) + assert.match("Wrote: " .. lua_name, output, 1, true) + util.assert_line_by_line([[ + local bit = bit; if not bit then local p, m = pcall(require, 'bit'); if p then bit = m end end + local foo = require("nonexisting") + local y = bit.bor(2, (bit.lshift(foo.wat, 9))) + local x = bit.bnot(y) + local z = math.floor(aa / bb) + ]], util.read_file(lua_name)) + end) + end) + + describe("with --gen-target=5.2", function() + it("targets generated code to Lua 5.2+", function() + local name = util.write_tmp_file(finally, [[ + + local x = 2 // 3 + local y = 2 << 3 + ]]) + local pd = io.popen(util.tl_cmd("gen", "--gen-target=5.2", name), "r") + local output = pd:read("*a") + util.assert_popen_close(0, pd:close()) + local lua_name = tl_to_lua(name) + assert.match("Wrote: " .. lua_name, output, 1, true) util.assert_line_by_line([[ local bit32 = bit32; if not bit32 then local p, m = pcall(require, 'bit32'); if p then bit32 = m end end local x = math.floor(2 / 3) @@ -212,7 +254,7 @@ describe("tl gen", function() local x = ~y local z = aa // bb ]]) - local pd = io.popen(util.tl_cmd("gen", "--gen-target=5.1", name), "r") + local pd = io.popen(util.tl_cmd("gen", "--gen-target=5.2", name), "r") local output = pd:read("*a") util.assert_popen_close(0, pd:close()) local lua_name = tl_to_lua(name) diff --git a/spec/compat/lua_versions_spec.lua b/spec/compat/lua_versions_spec.lua index cd0083630..ff7a78846 100644 --- a/spec/compat/lua_versions_spec.lua +++ b/spec/compat/lua_versions_spec.lua @@ -23,10 +23,10 @@ describe("Lua version compatibility", function() local c = 0xcafebabe local x = 2 & (c >> ~4 | 0xff) ]], [[ - local bit32 = bit32; if not bit32 then local p, m = pcall(require, 'bit32'); if p then bit32 = m end end + local bit = bit; if not bit then local p, m = pcall(require, 'bit'); if p then bit = m end end local c = 0xcafebabe - local x = bit32.band(2, (bit32.bor(bit32.rshift(c, bit32.bnot(4)), 0xff))) + local x = bit.band(2, (bit.bor(bit.rshift(c, bit.bnot(4)), 0xff))) ]], "5.1")) it("generates compat code for bitwise unary operator metamethods", util.gen([[ diff --git a/tl b/tl index c8c49339d..b9bddd70f 100755 --- a/tl +++ b/tl @@ -766,7 +766,7 @@ local function validate_config(config) quiet = "boolean", skip_compat53 = "boolean", gen_compat = { ["off"] = true, ["optional"] = true, ["required"] = true }, - gen_target = { ["5.1"] = true, ["5.3"] = true, ["5.4"] = true }, + gen_target = { ["5.1"] = true, ["5.2"] = true, ["5.3"] = true, ["5.4"] = true }, disable_warnings = "{string}", warning_error = "{string}", @@ -842,7 +842,7 @@ local function get_args_parser() :defmode("a") parser:option("--gen-target", "Minimum targeted Lua version for generated code.") - :choices({ "5.1", "5.3", "5.4" }) + :choices({ "5.1", "5.2", "5.3", "5.4" }) parser:flag("--skip-compat53", "Skip compat53 insertions.") :hidden(true) diff --git a/tl.lua b/tl.lua index 61a3a854a..91c1baced 100644 --- a/tl.lua +++ b/tl.lua @@ -132,6 +132,7 @@ local tl = {TypeCheckOptions = {}, Env = {}, Symbol = {}, Result = {}, Error = { + tl.version = function() @@ -4951,6 +4952,8 @@ local function add_compat_entries(program, used_set, gen_compat) for _, name in ipairs(used_list) do if name == "table.unpack" then load_code(name, "local _tl_table_unpack = unpack or table.unpack") + elseif name == "bit" then + load_code(name, "local bit = bit; if not bit then local p, m = " .. req("bit") .. "; if p then bit = m end") elseif name == "bit32" then load_code(name, "local bit32 = bit32; if not bit32 then local p, m = " .. req("bit32") .. "; if p then bit32 = m end") elseif name == "mt" then @@ -10010,6 +10013,16 @@ tl.type_check = function(ast, opts) end if node.op.op == "~" and env.gen_target == "5.1" then + if meta_on_operator then + all_needs_compat["mt"] = true + convert_node_to_compat_mt_call(node, unop_to_metamethod[node.op.op], 1, node.e1) + else + all_needs_compat["bit"] = true + convert_node_to_compat_call(node, "bit", "bnot", node.e1) + end + end + + if node.op.op == "~" and env.gen_target == "5.2" then if meta_on_operator then all_needs_compat["mt"] = true convert_node_to_compat_mt_call(node, unop_to_metamethod[node.op.op], 1, node.e1) @@ -10048,7 +10061,7 @@ tl.type_check = function(ast, opts) node.known = FACT_TRUTHY end - if node.op.op == "//" and env.gen_target == "5.1" then + if node.op.op == "//" and (env.gen_target == "5.1" or env.gen_target == "5.2") then if meta_on_operator then all_needs_compat["mt"] = true convert_node_to_compat_mt_call(node, "__idiv", meta_on_operator, node.e1, node.e2) @@ -10057,6 +10070,14 @@ tl.type_check = function(ast, opts) convert_node_to_compat_call(node, "math", "floor", div) end elseif bit_operators[node.op.op] and env.gen_target == "5.1" then + if meta_on_operator then + all_needs_compat["mt"] = true + convert_node_to_compat_mt_call(node, binop_to_metamethod[node.op.op], meta_on_operator, node.e1, node.e2) + else + all_needs_compat["bit"] = true + convert_node_to_compat_call(node, "bit", bit_operators[node.op.op], node.e1, node.e2) + end + elseif bit_operators[node.op.op] and env.gen_target == "5.2" then if meta_on_operator then all_needs_compat["mt"] = true convert_node_to_compat_mt_call(node, binop_to_metamethod[node.op.op], meta_on_operator, node.e1, node.e2) diff --git a/tl.tl b/tl.tl index 54082ee90..64911e0ce 100644 --- a/tl.tl +++ b/tl.tl @@ -20,6 +20,7 @@ local record tl enum TargetMode "5.1" + "5.2" "5.3" "5.4" end @@ -4951,6 +4952,8 @@ local function add_compat_entries(program: Node, used_set: {string: boolean}, ge for _, name in ipairs(used_list) do if name == "table.unpack" then load_code(name, "local _tl_table_unpack = unpack or table.unpack") + elseif name == "bit" then + load_code(name, "local bit = bit; if not bit then local p, m = " .. req("bit") .. "; if p then bit = m end") elseif name == "bit32" then load_code(name, "local bit32 = bit32; if not bit32 then local p, m = " .. req("bit32") .. "; if p then bit32 = m end") elseif name == "mt" then @@ -10010,6 +10013,16 @@ tl.type_check = function(ast: Node, opts: TypeCheckOptions): Result, string end if node.op.op == "~" and env.gen_target == "5.1" then + if meta_on_operator then + all_needs_compat["mt"] = true + convert_node_to_compat_mt_call(node, unop_to_metamethod[node.op.op], 1, node.e1) + else + all_needs_compat["bit"] = true + convert_node_to_compat_call(node, "bit", "bnot", node.e1) + end + end + + if node.op.op == "~" and env.gen_target == "5.2" then if meta_on_operator then all_needs_compat["mt"] = true convert_node_to_compat_mt_call(node, unop_to_metamethod[node.op.op], 1, node.e1) @@ -10048,7 +10061,7 @@ tl.type_check = function(ast: Node, opts: TypeCheckOptions): Result, string node.known = FACT_TRUTHY end - if node.op.op == "//" and env.gen_target == "5.1" then + if node.op.op == "//" and (env.gen_target == "5.1" or env.gen_target == "5.2") then if meta_on_operator then all_needs_compat["mt"] = true convert_node_to_compat_mt_call(node, "__idiv", meta_on_operator, node.e1, node.e2) @@ -10057,6 +10070,14 @@ tl.type_check = function(ast: Node, opts: TypeCheckOptions): Result, string convert_node_to_compat_call(node, "math", "floor", div) end elseif bit_operators[node.op.op] and env.gen_target == "5.1" then + if meta_on_operator then + all_needs_compat["mt"] = true + convert_node_to_compat_mt_call(node, binop_to_metamethod[node.op.op], meta_on_operator, node.e1, node.e2) + else + all_needs_compat["bit"] = true + convert_node_to_compat_call(node, "bit", bit_operators[node.op.op], node.e1, node.e2) + end + elseif bit_operators[node.op.op] and env.gen_target == "5.2" then if meta_on_operator then all_needs_compat["mt"] = true convert_node_to_compat_mt_call(node, binop_to_metamethod[node.op.op], meta_on_operator, node.e1, node.e2)