Skip to content

Commit e200845

Browse files
mtdowlinghishamhm
authored andcommitted
fix: allow goto to a label at the end of a block
The goto validity check from #1121 rejected jumps over local declarations to a label at the end of a block, such as the common `goto continue` idiom. Lua permits this. A label followed only by other labels at the end of a block is outside the scope of the block's locals. The relaxation does not apply to labels at the end of a repeat body, whose locals remain visible in the until condition, nor to labels followed by a return statement, matching PUC-Lua.
1 parent 4b123b8 commit e200845

6 files changed

Lines changed: 111 additions & 12 deletions

File tree

spec/lang/statement/goto_spec.lua

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,4 +141,55 @@ describe("goto", function()
141141
]], {
142142
{ y = 2, msg = "goto jumps into scope of a local variable" }
143143
}))
144+
145+
it("accepts a goto over a local to a label at the end of a block", util.check([[
146+
for i = 1, 5 do
147+
goto next
148+
local _this = i
149+
::next::
150+
end
151+
]]))
152+
153+
it("accepts the continue idiom with locals in the loop body", util.check([[
154+
for i = 1, 3 do
155+
if i == 2 then
156+
goto continue
157+
end
158+
local doubled = i * 2
159+
print(doubled)
160+
::continue::
161+
end
162+
]]))
163+
164+
it("accepts a goto over a local to stacked labels at the end of a block", util.check([[
165+
do
166+
goto first
167+
local _foo = 0
168+
::first::
169+
::second::
170+
end
171+
local _bar = 0
172+
]]))
173+
174+
it("rejects a goto over a local to a label followed by return", util.check_type_error([[
175+
local function f(): integer
176+
goto finish
177+
local _foo = 0
178+
::finish::
179+
return 1
180+
end
181+
f()
182+
]], {
183+
{ y = 2, msg = "goto jumps into scope of a local variable" }
184+
}))
185+
186+
it("rejects a goto over a local to a label at the end of a repeat body", util.check_type_error([[
187+
repeat
188+
goto continue
189+
local _foo = 0
190+
::continue::
191+
until true
192+
]], {
193+
{ y = 2, msg = "goto jumps into scope of a local variable" }
194+
}))
144195
end)

teal/ast.lua

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,7 @@ local parse_typeargs_if_any
233233

234234

235235

236+
236237

237238

238239
local ast = {}

teal/ast.tl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,7 @@ local record Node
193193

194194
-- label
195195
used_label: boolean
196+
is_end_of_block: boolean
196197

197198
casttype: Type
198199

teal/check/visitors.lua

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -797,6 +797,19 @@ visit_node.cbs = {
797797
["statements"] = {
798798
before = function(self, node)
799799
self:begin_scope(node)
800+
801+
802+
803+
804+
805+
if not node.is_repeat then
806+
for i = #node, 1, -1 do
807+
if node[i].kind ~= "label" then
808+
break
809+
end
810+
node[i].is_end_of_block = true
811+
end
812+
end
800813
end,
801814
after = function(self, node, _children)
802815

@@ -1064,10 +1077,12 @@ visit_node.cbs = {
10641077

10651078
local scope = self.st[#self.st]
10661079
if scope.pending_labels and scope.pending_labels[label_id] then
1067-
local n_scope_vars = count_scope_vars(self)
1068-
for _, goto_node in ipairs(scope.pending_labels[label_id]) do
1069-
if n_scope_vars > goto_node.n_scope_vars then
1070-
self.errs:add(goto_node, "goto jumps into scope of a local variable")
1080+
if not node.is_end_of_block then
1081+
local n_scope_vars = count_scope_vars(self)
1082+
for _, goto_node in ipairs(scope.pending_labels[label_id]) do
1083+
if n_scope_vars > goto_node.n_scope_vars then
1084+
self.errs:add(goto_node, "goto jumps into scope of a local variable")
1085+
end
10711086
end
10721087
end
10731088
node.used_label = true

teal/check/visitors.tl

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -797,6 +797,19 @@ visit_node.cbs = {
797797
["statements"] = {
798798
before = function(self: Context, node: Node)
799799
self:begin_scope(node)
800+
-- a goto may jump to a label at the end of a block (followed only
801+
-- by other labels) even across local declarations, because the
802+
-- block's locals are already out of scope at that point. This does
803+
-- not apply to a repeat body, whose locals remain visible in the
804+
-- until condition.
805+
if not node.is_repeat then
806+
for i = #node, 1, -1 do
807+
if node[i].kind ~= "label" then
808+
break
809+
end
810+
node[i].is_end_of_block = true
811+
end
812+
end
800813
end,
801814
after = function(self: Context, node: Node, _children: {Type}): Type
802815
-- if at the top level
@@ -1064,10 +1077,12 @@ visit_node.cbs = {
10641077

10651078
local scope = self.st[#self.st]
10661079
if scope.pending_labels and scope.pending_labels[label_id] then
1067-
local n_scope_vars = count_scope_vars(self)
1068-
for _, goto_node in ipairs(scope.pending_labels[label_id]) do
1069-
if n_scope_vars > goto_node.n_scope_vars then
1070-
self.errs:add(goto_node, "goto jumps into scope of a local variable")
1080+
if not node.is_end_of_block then
1081+
local n_scope_vars = count_scope_vars(self)
1082+
for _, goto_node in ipairs(scope.pending_labels[label_id]) do
1083+
if n_scope_vars > goto_node.n_scope_vars then
1084+
self.errs:add(goto_node, "goto jumps into scope of a local variable")
1085+
end
10711086
end
10721087
end
10731088
node.used_label = true

tl.lua

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -495,6 +495,7 @@ local parse_typeargs_if_any
495495

496496

497497

498+
498499

499500

500501
local ast = {}
@@ -8325,6 +8326,19 @@ visit_node.cbs = {
83258326
["statements"] = {
83268327
before = function(self, node)
83278328
self:begin_scope(node)
8329+
8330+
8331+
8332+
8333+
8334+
if not node.is_repeat then
8335+
for i = #node, 1, -1 do
8336+
if node[i].kind ~= "label" then
8337+
break
8338+
end
8339+
node[i].is_end_of_block = true
8340+
end
8341+
end
83288342
end,
83298343
after = function(self, node, _children)
83308344

@@ -8592,10 +8606,12 @@ visit_node.cbs = {
85928606

85938607
local scope = self.st[#self.st]
85948608
if scope.pending_labels and scope.pending_labels[label_id] then
8595-
local n_scope_vars = count_scope_vars(self)
8596-
for _, goto_node in ipairs(scope.pending_labels[label_id]) do
8597-
if n_scope_vars > goto_node.n_scope_vars then
8598-
self.errs:add(goto_node, "goto jumps into scope of a local variable")
8609+
if not node.is_end_of_block then
8610+
local n_scope_vars = count_scope_vars(self)
8611+
for _, goto_node in ipairs(scope.pending_labels[label_id]) do
8612+
if n_scope_vars > goto_node.n_scope_vars then
8613+
self.errs:add(goto_node, "goto jumps into scope of a local variable")
8614+
end
85998615
end
86008616
end
86018617
node.used_label = true

0 commit comments

Comments
 (0)