Add labeled break and continue statements - #219
Conversation
|
My preference would be to have the label follow the for i = 1, 10 do: label
if i == 8 then
continue label
end
endWhile I'm not strictly proposing this syntax (it gets weird due to e.g.
|
|
While this would be a nice feature I am not sure about the syntax to to add a label. Could we lean into attributes here with something like |
|
I like it.
Never was attached to it. It looks ugly :) |
|
I like this, something like this would be nice to have. I enjoy the extended do block syntax, an uncommon pattern I do sometimes when I need to check a bunch of different things that chain off of each other is something like: -- execution starts
repeat
local character = Player.Character
if not character then
break
end
local humanoid = character:FindFirstChildOfClass("Humanoid")
if not humanoid then
break
end
ApplyDamage(humanoid, 10)
until true
-- execution continues |
I was unsure about the syntax at first, but using |
|
Even |
imo, |
|
After internal discussion with the team we decided to split this PR in two moving away labeled for i in 1,10 do
if i > 5 then do break end end
endWe still consider breaking labeled |
There was a problem hiding this comment.
this opinion is orthogonal to my feedback, but in addition to many quality concerns (a lot of the wording here is excessively fluffy imo - llms are well known for that), i think merging an ai-generated rfc as-is sets a potentially antisocial precedent for future collaborators.
| - It reuses `return` with different semantics than the enclosing function, which can confuse readers. | ||
| - Luau's type checker must reason through the IIFE boundary, which complicates type narrowing and return type inference. | ||
| - It is stylistically alien — the construct exists solely to work around a language limitation rather than to express intent. | ||
| - If compiled without optimization(or inlining doesn't happen), it introduces a closure allocation and call overhead. |
There was a problem hiding this comment.
there should be a space before the parenthesized text here
|
|
||
| ## Summary | ||
|
|
||
| Introduce a labeled form of loop, and extend `break` and `continue` to optionally reference a label. The label is written as a colon-suffix on the loop's `do` keyword — `for i = 1, 10 do: outer` — making it structurally bound to the loop it names. This allows structured non-local exits from nested loops without workarounds such as flag variables or immediately-invoked function expressions. |
There was a problem hiding this comment.
i think 'a labeled form of loop', 'extend break and continue', 'label is written as a colon-suffix on the loop's do keyword', etc... are good things to communicate, but could be said more concisely by replacing this entire section with something along the lines of 'introduce named control flow manipulation in loops with do: label, repeat: label, break label, and continue label'
| end | ||
| ``` | ||
|
|
||
| **Immediately-invoked function expressions (IIFEs)** are commonly used in Luau codebases: |
There was a problem hiding this comment.
'commonly used' seems very subjective, there are lots of luau codebases without this pattern. perhaps 'may be used' would be better?
|
|
||
| A label is in scope from the `do:` (or `repeat:`) token to the end of the enclosing block. It is visible in all nested blocks — but only for the purpose of `break`/`continue`; a label cannot be referenced in any other context, and it is never captured as an upvalue. | ||
|
|
||
| Label names must be unique within their immediately enclosing block. Redeclaring a label name at the same scope level is a compile-time error. Shadowing an outer label with an inner label of the same name is permitted, following the same rules as variable shadowing. |
There was a problem hiding this comment.
Label names must be unique within their immediately enclosing block.
this wording makes no sense to me. if the meaning is what follows it, why have this at all?
Redeclaring a label name at the same scope level is a compile-time error. Shadowing an outer label with an inner label of the same name is permitted, following the same rules as variable shadowing.
why a compile error for making the same label twice in the same scope? does this imply:
repeat: retries
local ok, err = attempt()
if err == "fatal" then break retries end
until ok or retries_exceeded()
-- invalid
repeat: retries
local ok, err = attempt_other()
if err == "fatal" then break retries end
until ok or retries_exceeded()if so, that seems extremely different from local shadowing semantics - those scopes are completely unrelated, since the label stops existing as soon as you leave the loop. secondly, there already exist lints for shadowing locals. i'd argue if anything we should lint or compile error the opposite way - inner shadowing disallowed because it results in rules which may be significantly more confusing, but otherwise there should just be no limitation imo
|
|
||
| **`repeat` asymmetry.** All other labeled constructs attach the label to `do`, which is the shared block-opening keyword. `repeat` has no `do`, so its label sits on `repeat` itself (`repeat: name`). This is a minor inconsistency programmers must learn. | ||
|
|
||
| **Contextual keyword extension.** `continue` is already a contextual keyword. `break label` and `continue label` require the parser to recognize that the identifier following `break`/`continue` on the same line is a label reference. This is straightforward but must be carefully specified to avoid edge cases at line boundaries. |
There was a problem hiding this comment.
This is straightforward but must be carefully specified to avoid edge cases at line boundaries.
could this be explained further? i don't really see how this results in any edge cases, and line boundaries feel orthogonal
|
|
||
| ### Interaction with `continue` | ||
|
|
||
| `continue` is already a contextual keyword in Luau. The labeled form `continue Name` is parsed the same way: `continue` followed by an identifier on the same line. Since `continue` only appears inside loop bodies and cannot start a function call, there is no ambiguity with expression statements. |
There was a problem hiding this comment.
Since
continueonly appears inside loop bodies and cannot start a function call, there is no ambiguity with expression statements.
this sentence doesn't make a lot of sense. why would a keyword start a function call, and how is that related? additionally, expressions and statements are fundamentally disjoint in luau - there are no expression statements. there are things which are valid as expressions and valid as statements, but to me this paragraph seems really unnecessary generally?
|
|
||
| ### Type checking | ||
|
|
||
| Labels have no direct type implications. The type checker treats labeled `break` the same as an unconditional `break` for the purposes of control-flow analysis: it is a definite exit from the labeled statement. Labeled `continue` is treated as a definite jump to the loop condition of the labeled statement. |
There was a problem hiding this comment.
that doesn't make a lot of sense, you say it has no implications and then explain how it behaves in control flow analysis. isn't non-local jumping from an inner loop an inherently more complicated control flow which wasn't possible before? even the drawbacks section addresses this directly.
the behavior here is also worded somewhat confusingly explaining the implications of this, possibly because it seemingly ignores potential implications
|
|
||
| ### Bytecode | ||
|
|
||
| `break label` compiles to the same `JUMP` instruction as an unlabeled `break`, targeting the instruction immediately following the labeled statement(extra `CLOSEUPVALS` for all outer loops should be also emitted though). `continue label` compiles to the same `JUMP` as an unlabeled `continue`, targeting the loop condition of the labeled statement. No new bytecode instructions are required. |
There was a problem hiding this comment.
space missing before parenthesized section here
|
|
||
| ### Bytecode | ||
|
|
||
| `break label` compiles to the same `JUMP` instruction as an unlabeled `break`, targeting the instruction immediately following the labeled statement(extra `CLOSEUPVALS` for all outer loops should be also emitted though). `continue label` compiles to the same `JUMP` as an unlabeled `continue`, targeting the loop condition of the labeled statement. No new bytecode instructions are required. |
There was a problem hiding this comment.
perhaps the CLOSEUPVALS bytecode should be mentioned in the drawbacks section? a vm developer would have to tell me if that's super significant because idrk the implementation of that
|
|
||
| ## Drawbacks | ||
|
|
||
| **`repeat` asymmetry.** All other labeled constructs attach the label to `do`, which is the shared block-opening keyword. `repeat` has no `do`, so its label sits on `repeat` itself (`repeat: name`). This is a minor inconsistency programmers must learn. |
There was a problem hiding this comment.
although i'm not against a very exhaustive drawbacks section, i'm not sure this item is very significant. it could also be merged with the later one that talks about the learnability of this syntax.
Rendered.