diff --git a/spec/lang/code_gen/fornum_spec.lua b/spec/lang/code_gen/fornum_spec.lua new file mode 100644 index 00000000..3eaa904f --- /dev/null +++ b/spec/lang/code_gen/fornum_spec.lua @@ -0,0 +1,97 @@ +local util = require("spec.util") + +describe("fornum", function() + it("5.3: doesn't generate control variable that is local to the iteration", util.gen([[ + local t: {string} = { "a", "b", "c" } + + for i = 1, #t do + i = i + 1 + print(t[i]) + end + ]], [[ + local t = { "a", "b", "c" } + + for i = 1, #t do + i = i + 1 + print(t[i]) + end + ]], "5.3")) + + it("5.4: generates control variable that is local to the iteration", util.gen([[ + local t: {string} = { "a", "b", "c" } + + for i = 1, #t do + i = i + 1 + print(t[i]) + end + ]], [[ + local t = { "a", "b", "c" } + + for i = 1, #t do local i = i + i = i + 1 + print(t[i]) + end + ]], "5.4")) + + it("5.4: does not generate control variable if not assigned to", util.gen([[ + local t: {string} = { "a", "b", "c" } + + for i = 1, #t do + local j = i + 1 + print(t[j]) + end + ]], [[ + local t = { "a", "b", "c" } + + for i = 1, #t do + local j = i + 1 + print(t[j]) + end + ]], "5.4")) + + it("5.4: generates control variable for a loop with a step", util.gen([[ + for i = 10, 1, -2 do + i = i // 2 + print(i) + end + ]], [[ + for i = 10, 1, -2 do local i = i + i = i // 2 + print(i) + end + ]], "5.4")) + + it("5.4: detects an assignment made from a nested function", util.gen([[ + for i = 1, 3 do + local function bump() + i = i + 1 + end + bump() + print(i) + end + ]], [[ + for i = 1, 3 do local i = i + local function bump() + i = i + 1 + end + bump() + print(i) + end + ]], "5.4")) + + it("5.4: only shadows the loops that are assigned to", util.gen([[ + for i = 1, 3 do + for j = 1, 3 do + j = j + 1 + print(i, j) + end + end + ]], [[ + for i = 1, 3 do + for j = 1, 3 do local j = j + j = j + 1 + print(i, j) + end + end + ]], "5.4")) +end) diff --git a/teal.lua b/teal.lua index df1fec85..5a0f48a8 100644 --- a/teal.lua +++ b/teal.lua @@ -236,6 +236,7 @@ local parse_typeargs_if_any + local ast = {} @@ -8486,7 +8487,14 @@ visit_node.cbs = { "number" self:add_var(node.var, node.var.tk, a_type(node.var, typename, {})) end, - after = end_scope_and_none_type, + after = function(self, node, _children) + local var = self:find_var(node.var.tk) + if var and var.has_been_written_to then + node.fornum_modifies_control_var = true + end + self:end_scope(node) + return NONE + end, }, ["return"] = { before = function(self, node) @@ -11154,6 +11162,25 @@ local bit_operators = { ["<<"] = "lshift", } + +local function shadow_control_var(node, control_var) + if #node.body == 0 then + return + end + local stmt = node_at(node, { + kind = "local_declaration", + vars = node_at(node, { + kind = "variable_list", + node_at(node, { kind = "variable", is_lvalue = true, tk = control_var }), + }), + exps = node_at(node, { + kind = "expression_list", + node_at(node, { kind = "variable", tk = control_var }), + }), + }) + table.insert(node.body, 1, stmt) +end + local function adjust_code(ast, needs_compat, gen_compat, gen_target) local visit = false @@ -11283,24 +11310,15 @@ local function adjust_code(ast, needs_compat, gen_compat, gen_target) visit = true visit_node.cbs["forin"] = { after = function(_, node, _children) - if #node.body == 0 then - return - end if node.forin_modifies_control_var then - local control_var = node.vars[1].tk - local loc_at = node - local stmt = node_at(loc_at, { - kind = "local_declaration", - vars = node_at(loc_at, { - kind = "variable_list", - node_at(loc_at, { kind = "variable", is_lvalue = true, tk = control_var }), - }), - exps = node_at(loc_at, { - kind = "expression_list", - node_at(loc_at, { kind = "variable", tk = control_var }), - }), - }) - table.insert(node.body, 1, stmt) + shadow_control_var(node, node.vars[1].tk) + end + end, + } + visit_node.cbs["fornum"] = { + after = function(_, node, _children) + if node.fornum_modifies_control_var then + shadow_control_var(node, node.var.tk) end end, } diff --git a/teal/ast.lua b/teal/ast.lua index ffef498b..feb2a260 100644 --- a/teal/ast.lua +++ b/teal/ast.lua @@ -234,6 +234,7 @@ local parse_typeargs_if_any + local ast = {} diff --git a/teal/ast.tl b/teal/ast.tl index 65306fb6..307c857b 100644 --- a/teal/ast.tl +++ b/teal/ast.tl @@ -162,6 +162,7 @@ local record Node from: Node to: Node step: Node + fornum_modifies_control_var: boolean -- forin vars: Node diff --git a/teal/check/visitors.lua b/teal/check/visitors.lua index 9c1399ee..2db4c422 100644 --- a/teal/check/visitors.lua +++ b/teal/check/visitors.lua @@ -1217,7 +1217,14 @@ visit_node.cbs = { "number" self:add_var(node.var, node.var.tk, a_type(node.var, typename, {})) end, - after = end_scope_and_none_type, + after = function(self, node, _children) + local var = self:find_var(node.var.tk) + if var and var.has_been_written_to then + node.fornum_modifies_control_var = true + end + self:end_scope(node) + return NONE + end, }, ["return"] = { before = function(self, node) diff --git a/teal/check/visitors.tl b/teal/check/visitors.tl index 13d512a3..b14694e0 100644 --- a/teal/check/visitors.tl +++ b/teal/check/visitors.tl @@ -1217,7 +1217,14 @@ visit_node.cbs = { or "number" self:add_var(node.var, node.var.tk, a_type(node.var, typename, {})) end, - after = end_scope_and_none_type, + after = function(self: Context, node: Node, _children: {Type}): Type + local var = self:find_var(node.var.tk) + if var and var.has_been_written_to then + node.fornum_modifies_control_var = true + end + self:end_scope(node) + return NONE + end, }, ["return"] = { before = function(self: Context, node: Node) diff --git a/teal/gen/lua_compat.lua b/teal/gen/lua_compat.lua index e61c0de3..f1550508 100644 --- a/teal/gen/lua_compat.lua +++ b/teal/gen/lua_compat.lua @@ -117,6 +117,25 @@ local bit_operators = { ["<<"] = "lshift", } + +local function shadow_control_var(node, control_var) + if #node.body == 0 then + return + end + local stmt = node_at(node, { + kind = "local_declaration", + vars = node_at(node, { + kind = "variable_list", + node_at(node, { kind = "variable", is_lvalue = true, tk = control_var }), + }), + exps = node_at(node, { + kind = "expression_list", + node_at(node, { kind = "variable", tk = control_var }), + }), + }) + table.insert(node.body, 1, stmt) +end + local function adjust_code(ast, needs_compat, gen_compat, gen_target) local visit = false @@ -246,24 +265,15 @@ local function adjust_code(ast, needs_compat, gen_compat, gen_target) visit = true visit_node.cbs["forin"] = { after = function(_, node, _children) - if #node.body == 0 then - return - end if node.forin_modifies_control_var then - local control_var = node.vars[1].tk - local loc_at = node - local stmt = node_at(loc_at, { - kind = "local_declaration", - vars = node_at(loc_at, { - kind = "variable_list", - node_at(loc_at, { kind = "variable", is_lvalue = true, tk = control_var }), - }), - exps = node_at(loc_at, { - kind = "expression_list", - node_at(loc_at, { kind = "variable", tk = control_var }), - }), - }) - table.insert(node.body, 1, stmt) + shadow_control_var(node, node.vars[1].tk) + end + end, + } + visit_node.cbs["fornum"] = { + after = function(_, node, _children) + if node.fornum_modifies_control_var then + shadow_control_var(node, node.var.tk) end end, } diff --git a/teal/gen/lua_compat.tl b/teal/gen/lua_compat.tl index c60750ef..bfadd2a7 100644 --- a/teal/gen/lua_compat.tl +++ b/teal/gen/lua_compat.tl @@ -117,6 +117,25 @@ local bit_operators: {string:string} = { ["<<"] = "lshift", } +-- shadow var function +local function shadow_control_var(node: Node, control_var: string) + if #node.body == 0 then + return + end + local stmt = node_at(node, { + kind = "local_declaration", + vars = node_at(node, { + kind = "variable_list", + node_at(node, { kind = "variable", is_lvalue = true, tk = control_var }), + }), + exps = node_at(node, { + kind = "expression_list", + node_at(node, { kind = "variable", tk = control_var }), + }), + }) + table.insert(node.body, 1, stmt) +end + local function adjust_code(ast: Node, needs_compat: {string:boolean}, gen_compat: GenCompat, gen_target: string) local visit = false @@ -246,24 +265,15 @@ local function adjust_code(ast: Node, needs_compat: {string:boolean}, gen_compat visit = true visit_node.cbs["forin"] = { after = function(_: nil, node: Node, _children: {nil}): nil - if #node.body == 0 then - return - end if node.forin_modifies_control_var then - local control_var = node.vars[1].tk - local loc_at = node - local stmt = node_at(loc_at, { - kind = "local_declaration", - vars = node_at(loc_at, { - kind = "variable_list", - node_at(loc_at, { kind = "variable", is_lvalue = true, tk = control_var }), - }), - exps = node_at(loc_at, { - kind = "expression_list", - node_at(loc_at, { kind = "variable", tk = control_var }), - }), - }) - table.insert(node.body, 1, stmt) + shadow_control_var(node, node.vars[1].tk) + end + end, + } + visit_node.cbs["fornum"] = { + after = function(_: nil, node: Node, _children: {nil}): nil + if node.fornum_modifies_control_var then + shadow_control_var(node, node.var.tk) end end, } diff --git a/tl.lua b/tl.lua index 97af5566..08fec085 100644 --- a/tl.lua +++ b/tl.lua @@ -490,6 +490,7 @@ local parse_typeargs_if_any + local ast = {} @@ -8740,7 +8741,14 @@ visit_node.cbs = { "number" self:add_var(node.var, node.var.tk, a_type(node.var, typename, {})) end, - after = end_scope_and_none_type, + after = function(self, node, _children) + local var = self:find_var(node.var.tk) + if var and var.has_been_written_to then + node.fornum_modifies_control_var = true + end + self:end_scope(node) + return NONE + end, }, ["return"] = { before = function(self, node) @@ -11408,6 +11416,25 @@ local bit_operators = { ["<<"] = "lshift", } + +local function shadow_control_var(node, control_var) + if #node.body == 0 then + return + end + local stmt = node_at(node, { + kind = "local_declaration", + vars = node_at(node, { + kind = "variable_list", + node_at(node, { kind = "variable", is_lvalue = true, tk = control_var }), + }), + exps = node_at(node, { + kind = "expression_list", + node_at(node, { kind = "variable", tk = control_var }), + }), + }) + table.insert(node.body, 1, stmt) +end + local function adjust_code(ast, needs_compat, gen_compat, gen_target) local visit = false @@ -11537,24 +11564,15 @@ local function adjust_code(ast, needs_compat, gen_compat, gen_target) visit = true visit_node.cbs["forin"] = { after = function(_, node, _children) - if #node.body == 0 then - return - end if node.forin_modifies_control_var then - local control_var = node.vars[1].tk - local loc_at = node - local stmt = node_at(loc_at, { - kind = "local_declaration", - vars = node_at(loc_at, { - kind = "variable_list", - node_at(loc_at, { kind = "variable", is_lvalue = true, tk = control_var }), - }), - exps = node_at(loc_at, { - kind = "expression_list", - node_at(loc_at, { kind = "variable", tk = control_var }), - }), - }) - table.insert(node.body, 1, stmt) + shadow_control_var(node, node.vars[1].tk) + end + end, + } + visit_node.cbs["fornum"] = { + after = function(_, node, _children) + if node.fornum_modifies_control_var then + shadow_control_var(node, node.var.tk) end end, }