Fixes #1598
A sync try or catch block is compiled to a Lua function (pcall(function() ... end) / local function ____catch() ... end). A break or continue inside it was emitted as a plain break / goto inside that function, so Lua refused to load the file:
for (const f of factionKeys) {
try {
GetFaction(f);
} catch {
break;
}
}
local function ____catch()
break -- error: break outside a loop
end
Async functions already handle this with flags (#1707). This uses the same approach for sync try/catch: break / continue set ____hasBroken / ____hasContinued and return, and the try statement checks the flag at its end (after finally and the rethrow check), then does the real break or continue:
do
local ____hasBroken
local function ____catch()
____hasBroken = true
return
end
local ____try = pcall(function()
GetFaction(f)
end)
if not ____try then
____catch()
end
if ____hasBroken then
break
end
end
break/continue and the post-try checks now share one helper (createBreak / createContinue in break-continue.ts), for both sync and async try. That also fixes two cases the async code got wrong:
break inside a switch inside a try broke out of the loop, not the switch. The search for an enclosing try now stops at the switch for break.
- Nested try statements: the inner try's check emitted a plain
break inside the outer try's function. Now only the outermost try inside the loop declares the flag; inner ones share it and return out of the outer try function.
Tests:
test/unit/error.spec.ts: break/continue from try, catch (with and without the error variable), try/finally, try/catch/finally, nested try, and a mix with return, in for, for...of, while and do...while. Run on universal, 5.0, 5.1 and 5.4, plus Luau for the break-only ones (the test VM can't run Luau's continue). Also checks that a break inside a switch or an inner loop inside a try only leaves that switch or loop.
test/unit/builtins/async-await.spec.ts: switch inside try, and nested try, in an async loop.
47 of the new tests fail before the change. The 6 that already pass are the switch / inner-loop guards in error.spec.ts and the 5.0/5.1 async runs, which only check the existing diagnostic. All pass after. Full jest run passes (6316 tests), and npm run lint passes.