Scope and ownership
LinkEdition: Topaz 5.21. Authority: the current SELF compiler at source revision b48527d0a17456dc8830ec31a2de59b7e1c6aea9. The complete examples have independently authored expectations and await execution with the public Topaz 5.21.x CLI.
This page owns block execution, conditional and iterative control flow, labels, explicit transfers, and deferred cleanup. Pattern syntax belongs to Pattern matching. Range operators belong to Expressions and operators. Function return inference belongs to Functions and generics. Collection construction and comprehensions belong to Collections and comprehensions. Concurrent timeout behavior and CLI lifecycle are outside this page.
Control-flow syntax
LinkBlock = "{" Statement* Expression? "}" ;IfExpression = "if" IfHead Block ("else" (IfExpression | Block))? ;IfHead = ControlOperand | "let" Pattern "=" ControlOperand ;WhileStatement = "while" WhileHead Block ;WhileHead = ControlOperand | "let" Pattern "=" ControlOperand ;ForExpression = "for" Pattern "in" ControlOperand Block ;LoopExpression = "loop" Label? Block ;BreakStatement = "break" Label? Expression? ;ContinueStatement = "continue" Label? ;ReturnStatement = "return" Expression? ;DeferStatement = "defer" Block ;ControlOperand = "(" Expression ")" ;
This is a manually transcribed source-syntax excerpt. Statement boundaries belong to Lexical structure. Patterns belong to Pattern matching. Call arguments belong to Functions and generics.
A label starts with an apostrophe, as in loop 'outer { ., break 'outer 7, or continue 'outer. The label follows loop, break, or continue.
The iterator in for item in (expression) { . can be a range. In for n in (0., by 2 is part of the range expression. Range construction and bounds belong to Expressions and operators.
Source evidence
- parseBlockBody and parseBlock at
b48527d0a174· 941-1003 - parseIfLet and parseIf at
b48527d0a174· 590-677 - parseFor, labelNode, and parseLoop at
b48527d0a174· 679-720 - parseReturnStatement and parseLoopExitStatement at
b48527d0a174· 656-708 - whileLetNode and parseWhileStatement at
b48527d0a174· 710-814 - parseDeferStatement at
b48527d0a174· 816-842
Blocks and final values
LinkBlock execution and final value
On normal completion, a block executes its statements in source order. It then evaluates its optional final expression and produces that value. Trailing line breaks, blank lines, and comments preserve that final value. Without a final expression, it produces (). Write let _ to evaluate an expression once without retaining its value. A final discard binding leaves the block without a tail, so the block produces (). A statement separator never consumes a value.
Why
A block has one final-value rule. Formatting the closing delimiter, adding blank lines, or ending a line comment cannot consume that value. A discard binding names the intention to run an expression once and finish with unit. Statement and control-flow effect contexts retain their existing result rules.
Source evidence
- parseBlockBody and parseBlock at
b48527d0a174· 941-1003 - checkBlock and checkBlockExpected at
b48527d0a174· 884-950 - emitIf and emitBlock at
b48527d0a174· 1309-1415
If, else, and if let
LinkChoosing an if branch
An if condition has type bool. A true condition selects the then branch. A false condition selects the else arm when present and otherwise skips the then branch. An else arm can be a block or another if expression. The unselected branch is not evaluated. An if expression with an else arm produces the selected branch's value. Without an else arm, the expression always has the unit type and produces () on normal completion, including in a value position. A selected then branch runs for its effects and its value is discarded.
Why
Boolean conditions avoid implicit truth conversions. An if without else has the same unit result in statement and value positions.
Source evidence
- parseIfLet and parseIf at
b48527d0a174· 590-677 - inferIfExpression at
b48527d0a174· 107-213 - emitIf and emitBlock at
b48527d0a174· 1309-1415 - issues/31 at
570530e69141
Conditional pattern binding
if let evaluates its scrutinee once. A successful pattern selects the then block and makes its bindings available in that branch. Failure selects the else arm when present. The result follows the if expression result rules. Without an else arm, the expression has the unit type and produces () on normal completion whether the pattern matches or fails. A selected then block discards its value when the else arm is absent.
Why
Evaluating the scrutinee once avoids repeating its effects. Keeping bindings in the successful branch prevents access to values from a failed match.
Source evidence
- parseIfLet and parseIf at
b48527d0a174· 590-677 - emitMatch at
b48527d0a174· 2274-2308 - issues/31 at
570530e69141 - parseIfLet at
23582e1d56db· 590-660 - inferMatchExpression at
23582e1d56db· 214-315 - emitMatch at
23582e1d56db· 2274-2308
While, while let, and for
LinkTesting before each while iteration
A while loop tests its bool condition before each body execution. A true condition runs the body, and a false condition finishes the loop. A continue targeting this loop resumes at the next condition test. A break targeting it transfers control after the loop.
Why
Testing before the body lets an initially false condition skip the loop. Testing again after continue preserves the condition for every iteration.
Source evidence
- whileLetNode and parseWhileStatement at
b48527d0a174· 710-814 - checkWhileStatement at
b48527d0a174· 769-807 - drain, emitWhile, emitLoop, and emitLoopControl at
b48527d0a174· 1748-1814
Matching before each while let iteration
while let evaluates its scrutinee before each body execution. A successful pattern runs the body with its bindings. Pattern failure finishes the loop. A continue targeting this loop resumes at the next scrutinee evaluation.
Why
A fresh match checks each iteration against the current value. Its bindings describe the value that admitted that iteration.
Source evidence
- whileLetNode and parseWhileStatement at
b48527d0a174· 710-814 - checkWhileStatement at
b48527d0a174· 769-807 - drain, emitWhile, emitLoop, and emitLoopControl at
b48527d0a174· 1748-1814
Statement and value uses of for
A for expression evaluates its iterator expression once, visits elements in iterator order, and uses its pattern to bind each element.
Every for runs its body for effects, discards each completed body value, and produces (). This type and its break and continue targets stay the same in a statement, parentheses, initializer, argument, or function tail. An empty iterator also produces ().
Construct an array with [for item in (values) => expression]. Set and map comprehensions use their corresponding collection delimiters. A for expression does not collect body results. Requiring an array where a for produces () is an ordinary type mismatch.
Iterable admission and element-pattern failure are specified below.
Why
A for expression performs iteration for effects, while a comprehension constructs a collection. Giving every for the same unit result prevents parentheses, binding, and argument position from changing its operation or loop targets. The existing collection delimiters make collection construction explicit. Collecting values with an early stop has a different control-flow intent. Use an accumulator and break for that case. A comprehension expresses collection that runs to completion.
Source evidence
- parseFor, labelNode, and parseLoop at
b48527d0a174· 679-720 - inferLoopExpression and inferForExpression at
b48527d0a174· 493-600 - iteration, iterationBinding, and emitFor at
b48527d0a174· 1840-1893 - comprehensionClauses and emitComprehension collect into the requested collection at
271329b482bc· 1890-1923 - issues/31 at
b48527d0a174
Iterable admission and element-pattern failure
For expressions admit Array, Set, Range and bind their element type in every context. Other resolved concrete types are rejected with TPZ5001. Iterate map keys or entries explicitly. Convert a string with scalars() or graphemes() to choose the text unit.
A generic, foreign, or unresolved inferred type defers the element decision. At runtime the value must still be an array, set, or range. Array and set iteration traverse the collection value at entry under collection value semantics.
A refutable element pattern must match each visited element. A miss raises a runtime type fault before the body executes for that element.
Why
The collection value at entry keeps an iteration stable when the source variable changes. A failed element pattern faults before the body can use missing bindings.
Source evidence
- iterableElement at
151141d7bb44· 55-67 - checkedIterableElement at
151141d7bb44· 479-497 - for_items at
151141d7bb44· 599-636 - emitPatternLet loopBinding fault at
151141d7bb44· 2267-2272 - inferForExpression statement and value contexts at
151141d7bb44· 544-600 - iteration, iterationBinding, and emitFor at
151141d7bb44· 1840-1893
Loop labels, break, continue, and return
LinkSelecting a break or continue target
break, continue can include a target label. Lookup stays inside the current function and respects concurrent arm boundaries. An unlabeled transfer selects the innermost loop frame. A labeled transfer selects the nearest enclosing loop with that label. Targets include while loops, for expressions in every context, and loop expressions.
A transfer without an enclosing loop is rejected during checking with TPZ5001. An unknown loop label is rejected during checking with TPZ5720.
Why
Function boundaries prevent loop transfers from escaping into a caller. A label makes the intended enclosing loop visible when loops are nested.
Source evidence
- parseReturnStatement and parseLoopExitStatement at
b48527d0a174· 656-708 - resolveLoopTarget and checkLoopExitStatement at
b48527d0a174· 314-491 - Function return context and loop floor at
b48527d0a174· 249-283
Leaving a loop with a value
A loop label appears after loop. A break targeting the loop completes it with the evaluated break operand, or () when the operand is absent. A continue targeting it repeats the body. Without a contextual result type, break types must be assignable in at least one direction after widening. Disagreement is rejected during checking with TPZ5721.
Why
The break operand supplies the result at the exit point. Checking its type against other exits gives the loop a usable result type.
Source evidence
- parseFor, labelNode, and parseLoop at
b48527d0a174· 679-720 - resolveLoopTarget and checkLoopExitStatement at
b48527d0a174· 314-491 - inferLoopExpression and inferForExpression at
b48527d0a174· 493-600 - drain, emitWhile, emitLoop, and emitLoopControl at
b48527d0a174· 1748-1814
Returning from a function
A return is a statement and requires a function context. Its target cannot cross a concurrent arm boundary. Its value expression is evaluated before cleanup. A return without a value supplies (). Cleanup for crossed scopes runs before control transfers to the enclosing function's caller. A return outside a function is rejected during checking with TPZ5001.
Why
Evaluating the return value before cleanup preserves the selected result when cleanup changes state. The enclosing function fixes the transfer destination.
Source evidence
- parseReturnStatement and parseLoopExitStatement at
b48527d0a174· 656-708 - checkReturn at
b48527d0a174· 613-662 - Return value evaluation before cleanup at
b48527d0a174· 1983-1991
Break values in while and for loops
A break targeting a while loop or for expression evaluates its operand once before cleanup. The operand result is discarded. The target loop completes with (). A break without an operand follows the same cleanup and exit order. These rules also apply when the for expression is grouped or bound to a name.
Why
Evaluating the operand before cleanup preserves its effects. All for expressions and while loops complete with unit, so grouping or binding does not change the meaning of a break. Cleanup order remains consistent with return and value-producing break.
Source evidence
- resolveLoopTarget and checkLoopExitStatement at
151141d7bb44· 339-491 - emitWhile and emitLoopControl at
151141d7bb44· 1770-1814 - emitFor statement loop frame at
151141d7bb44· 1871-1893
Deferred cleanup
LinkRegistering a deferred block
A defer operand must be a block. A call without a block and other operands are rejected during parsing with TPZ2004. Write defer { cleanup() } to defer a call. The block is registered when execution reaches the defer statement and runs during cleanup.
Why
A block gives a single call and a sequence of cleanup steps the same syntax. Ordinary block rules determine how the action runs. A call form would evaluate its arguments when cleanup is registered, although cleanup happens when the scope exits. A block keeps evaluation at cleanup time. Use let to capture a value earlier when that is intended.
Source evidence
- parseDeferStatement at
b48527d0a174· 816-842 - emitDefer at
b48527d0a174· 1936-1943 - defer_push and run_defers at
b48527d0a174· 27-60 - parseDeferStatement still admits call operands at
9b1bbdeceba9· 816-843 - issues/31 at
b48527d0a174
Cleanup order on normal completion and control transfer
A reached defer belongs to its enclosing block. Cleanup runs on normal block completion and when return, break, continue, or an Err propagated by ? leaves that scope. An Ok operand of ? continues without leaving the scope or running its cleanup.
Actions run in reverse registration order within each scope. Crossed scopes are cleaned up from innermost to outermost. The block or transfer result is evaluated before cleanup. An ordinary runtime fault follows the fault cleanup rule.
Why
An Err propagated by ? leaves scopes just as an explicit return does. A common cleanup order keeps resources available until their dependents finish.
Source evidence
- emitIf and emitBlock at
b48527d0a174· 1309-1415 - drain, emitWhile, emitLoop, and emitLoopControl at
b48527d0a174· 1748-1814 - Return value evaluation before cleanup at
b48527d0a174· 1983-1991 - defer_push and run_defers at
b48527d0a174· 27-60 - issues/31 at
1cbf38d3d422
Runtime faults skip remaining cleanup
An ordinary runtime fault is an uncatchable termination. Remaining defer blocks and using cleanup do not run. Normal completion and structured exits retain the defer order.
Normal completion and structured exits follow the defer order.
Why
A single fault outcome makes output reproducible. Skipping cleanup also prevents a second cleanup fault after termination.
Source evidence
- issues/31 at
1cbf38d3d422
Complete examples and expected results
LinkEach example below is a complete file. Its expected result is specified before execution. No current execution result is attached to these examples.
A block value and a selected branch
Linkimport std.iofunction choose() -> int {let value = {let base = 4base + 3}if (value > 5) {value * 2} else {0}}io.writeLine("{choose()}")
14
Expected result
Expected result
Execution completes. Exit code 0.
14
Observed assertions · passed · 5.21.3
Exit code 0.
14
No stderr.
- Passed · expected 0; observed 0
- Passed · stdout matches the authored expectation
- Passed · stderr matches the authored expectation
- Passed · stdout matches the authored expectation
Expected stdout is 14 followed by a newline.
Source evidence
- parseBlockBody and parseBlock at
b48527d0a174· 941-1003 - checkBlock and checkBlockExpected at
b48527d0a174· 884-950 - inferIfExpression at
b48527d0a174· 107-213 - emitIf and emitBlock at
b48527d0a174· 1309-1415
Using if let and while let
Linkimport std.iolet mut next: Option<int> = Some(3)if let Some(value) = (next) {io.writeLine("ready: {value}")} else {io.writeLine("empty")}while let Some(value) = (next) {io.writeLine("{value}")next = None()}io.writeLine("done")
ready: 3 3 done
Expected result
Expected result
Execution completes. Exit code 0.
ready: 33done
Observed assertions · passed · 5.21.3
Exit code 0.
ready: 33done
No stderr.
- Passed · expected 0; observed 0
- Passed · stdout matches the authored expectation
- Passed · stderr matches the authored expectation
- Passed · stdout matches the authored expectation
The while-let body changes the next scrutinee to None(). The following test ends the loop.
Source evidence
- parseIfLet and parseIf at
b48527d0a174· 590-677 - whileLetNode and parseWhileStatement at
b48527d0a174· 710-814 - emitMatch at
b48527d0a174· 2274-2308
Continue in while and break in for
Linkimport std.iolet mut tick = 0while (tick < 3) {tick += 1if (tick == 2) {continue}io.writeLine("{tick}")}for n in (0..<8 by 2) {if (n == 4) {break}io.writeLine("{n}")}io.writeLine("done")
1 3 0 2 done
Expected result
Expected result
Execution completes. Exit code 0.
1302done
Observed assertions · passed · 5.21.3
Exit code 0.
1302done
No stderr.
- Passed · expected 0; observed 0
- Passed · stdout matches the authored expectation
- Passed · stderr matches the authored expectation
- Passed · stdout matches the authored expectation
- Passed · stdout matches the authored expectation
Both loops are used for their effects. The range syntax and step are defined under Expressions and operators.
Source evidence
- checkWhileStatement at
b48527d0a174· 769-807 - resolveLoopTarget and checkLoopExitStatement at
b48527d0a174· 314-491 - drain, emitWhile, emitLoop, and emitLoopControl at
b48527d0a174· 1748-1814 - iteration, iterationBinding, and emitFor at
b48527d0a174· 1840-1893
Breaking out of an enclosing labeled loop
Linkimport std.iolet result: int = loop 'outer {loop {break 'outer 7}}io.writeLine("{result}")
7
Expected result
Expected result
Execution completes. Exit code 0.
7
Observed assertions · passed · 5.21.3
Exit code 0.
7
No stderr.
- Passed · expected 0; observed 0
- Passed · stdout matches the authored expectation
- Passed · stderr matches the authored expectation
- Passed · stdout matches the authored expectation
The label selects the enclosing loop. The break supplies its result.
Source evidence
- parseFor, labelNode, and parseLoop at
b48527d0a174· 679-720 - resolveLoopTarget and checkLoopExitStatement at
b48527d0a174· 314-491 - inferLoopExpression and inferForExpression at
b48527d0a174· 493-600 - drain, emitWhile, emitLoop, and emitLoopControl at
b48527d0a174· 1748-1814
Running deferred actions before returning
Linkimport std.iofunction result() -> int {io.writeLine("value")7}function finish() -> int {defer { io.writeLine("first") }defer {io.writeLine("second")}io.writeLine("body")return result()}io.writeLine("{finish()}")
body value second first 7
Expected result
Expected result
Execution completes. Exit code 0.
bodyvaluesecondfirst7
The return operand runs before cleanup. Deferred blocks run in reverse registration order.
Source evidence
- parseDeferStatement at
b48527d0a174· 816-842 - emitDefer at
b48527d0a174· 1936-1943 - Return value evaluation before cleanup at
b48527d0a174· 1983-1991 - defer_push and run_defers at
b48527d0a174· 27-60
Cleaning up scopes during error propagation
Linkimport std.iofunction fail() -> Result<int, string> {io.writeLine("operand")Err("stop")}function finish() -> Result<int, string> {defer { io.writeLine("outer") }let value = {defer { io.writeLine("inner-first") }defer { io.writeLine("inner-second") }fail()?}io.writeLine("unreachable")Ok(value)}io.writeLine(match (finish()) {case Ok(value) => "value={value}"case Err(error) => error})
operand inner-second inner-first outer stop
Expected result
Expected result
Execution completes. Exit code 0.
operandinner-secondinner-firstouterstop
The operand runs once before cleanup. Error propagation runs inner defers in reverse order, then the outer defer, before returning the error.
Source evidence
- parseDeferStatement at
b48527d0a174· 816-842 - emitDefer at
b48527d0a174· 1936-1943 - Return value evaluation before cleanup at
b48527d0a174· 1983-1991 - defer_push and run_defers at
b48527d0a174· 27-60
Rejecting an invalid deferred expression
Linkdefer 5
Expected result
The integer operand is rejected at the start of 5. Replace it with a block that performs cleanup.
Source evidence
- parseDeferStatement at
b48527d0a174· 816-842
Break operands run before cleanup in statement loops
Linkimport std.iofunction operand(label: string) -> int {io.writeLine(label)7}while (true) {defer { io.writeLine("while cleanup") }break operand("while value")}for item in ([1, 2]) {defer { io.writeLine("for cleanup") }break operand("for value")}io.writeLine("done")
while value while cleanup for value for cleanup done
Expected result
Expected result
Execution completes. Exit code 0.
while valuewhile cleanupfor valuefor cleanupdone
The expected result follows from current SELF source. Execution against public Topaz 5.21.3 is pending CI.
Source evidence
- resolveLoopTarget and checkLoopExitStatement at
151141d7bb44· 339-491 - emitWhile and emitLoopControl at
151141d7bb44· 1770-1814 - emitFor statement loop frame at
151141d7bb44· 1871-1893
Break in a bound for preserves its unit type
Linklet result: () = for item in ([1]) {break 7}
No stdout.
Expected result
Expected result
Checking accepts the program. Exit code 0.
The explicit unit annotation also applies when the for is a binding initializer. The break operand does not become a collected element.
Source evidence
- resolveLoopTarget and checkLoopExitStatement at
151141d7bb44· 339-491 - emitWhile and emitLoopControl at
151141d7bb44· 1770-1814 - emitFor statement loop frame at
151141d7bb44· 1871-1893
Arrays, sets, ranges, and matching element patterns
Linkimport std.iofor item in ([1, 2]) {io.writeLine("array {item}")}for item in (set { 3, 1, 3 }) {io.writeLine("set {item}")}for item in (2..4) {io.writeLine("range {item}")}for Some(item) in ([Some(7), Some(8)]) {io.writeLine("matched {item}")}
array 1 array 2 set 3 set 1 range 2 range 3 range 4 matched 7 matched 8
Expected result
Expected result
Execution completes. Exit code 0.
array 1array 2set 3set 1range 2range 3range 4matched 7matched 8
The expected result follows from current SELF source. Execution against public Topaz 5.21.3 is pending CI.
Source evidence
- iterableElement at
151141d7bb44· 55-67 - checkedIterableElement at
151141d7bb44· 479-497 - for_items at
151141d7bb44· 599-636 - emitPatternLet loopBinding fault at
151141d7bb44· 2267-2272 - inferForExpression statement and value contexts at
151141d7bb44· 544-600 - iteration, iterationBinding, and emitFor at
151141d7bb44· 1840-1893
A string must be converted to scalar elements before iteration
Linkfor item in ("hi") {}
Expected result
Expected result
Rejection during check. Nonzero exit.
TPZ5001for-non-iterable-type atexample.tpz:1:14
The expected result follows from current SELF source. Execution against public Topaz 5.21.3 is pending CI.
Source evidence
- iterableElement at
151141d7bb44· 55-67 - checkedIterableElement at
151141d7bb44· 479-497 - for_items at
151141d7bb44· 599-636 - emitPatternLet loopBinding fault at
151141d7bb44· 2267-2272 - inferForExpression statement and value contexts at
151141d7bb44· 544-600 - iteration, iterationBinding, and emitFor at
151141d7bb44· 1840-1893
An omitted else discards values and preserves selected effects
Linkimport std.iolet taken = if (true) {io.writeLine("then")7}let skipped = if (false) {io.writeLine("unexpected")9}io.writeLine("{taken}")io.writeLine("{skipped}")
then () ()
Expected result
Expected result
Execution completes. Exit code 0.
then()()
Source evidence
- issues/31 at
570530e69141 - inferIfExpression joins then and implicit unit at
570530e69141· 107-211 - emitIf discards a then value when else is absent at
570530e69141· 1309-1339
An omitted else cannot produce an int result
Linklet value: int = if (true) { 7 }
No stdout.
Expected result
Expected result
Rejection during check. Nonzero exit.
TPZ5001if-without-else-result-type-mismatch atexample.tpz:1:18
Source evidence
- issues/31 at
570530e69141 - inferIfExpression joins then and implicit unit at
570530e69141· 107-211 - emitIf discards a then value when else is absent at
570530e69141· 1309-1339 - reportExpectedType and checkExpressionExpectedResult at
570530e69141· 1660-1748
Rejecting a deferred call without a block
Linkimport std.iofunction finish() -> () {defer io.writeLine("cleanup")}finish()
Expected result
Expected result
Rejection during parse. Nonzero exit.
TPZ2004defer-body-not-block atexample.tpz:4:11
Wrap the cleanup call in a defer block. The current SELF parser still accepts this removed form.
Source evidence
- parseDeferStatement still admits call operands at
9b1bbdeceba9· 816-843
Running a deferred block at scope exit
Linkimport std.iofunction finish() -> () {defer { io.writeLine("cleanup") }io.writeLine("body")}finish()
body cleanup
Expected result
Expected result
Execution completes. Exit code 0.
bodycleanup
The body prints before the registered cleanup block runs.
Source evidence
- emitDefer at
b48527d0a174· 1936-1943 - defer_push and run_defers at
b48527d0a174· 27-60
Returning through a match arm block
Linkimport std.iofunction choose(flag: bool) -> int {while (true) {let value = match (flag) {case true => { return 7 }case false => 3}return value}0}io.writeLine("{choose(true)}")io.writeLine("{choose(false)}")
7 3
Expected result
Expected result
Execution completes. Exit code 0.
73
The true arm returns from the function through the surrounding binding and loop. The false arm supplies a value to the ordinary return statement.
Source evidence
- emitMatch at
b48527d0a174· 2274-2308 - checkReturn at
b48527d0a174· 613-662 - Return value evaluation before cleanup at
b48527d0a174· 1983-1991
For results stay unit in grouped and bound positions
Linkimport std.iofunction items(label: string) -> Array<int> {io.writeLine(label)[1, 2]}let bound = for item in (items("bound")) {io.writeLine("{item}")item * 2}let grouped = (for item in (items("grouped")) {io.writeLine("{item}")item * 3})let emptyItems: Array<int> = []let empty = for item in (emptyItems) { item }io.writeLine("{bound} {grouped} {empty}")
bound 1 2 grouped 1 2 () () ()
Expected result
Expected result
Execution completes. Exit code 0.
bound12grouped12() () ()
The iterator calls and body output establish evaluation order. Binding, parentheses, and an empty iterator all retain the unit result.
Source evidence
- inferLoopExpression and inferForExpression at
b48527d0a174· 493-600 - iteration, iterationBinding, and emitFor at
b48527d0a174· 1840-1893
Break and continue survive grouping and binding
Linkimport std.iofunction grouped() -> () {(for item in ([1, 2, 3, 4]) {if (item == 2) { continue }if (item == 4) { break }io.writeLine("grouped {item}")})}grouped()let result = for item in ([1, 2, 3, 4]) {if (item == 2) { continue }if (item == 4) { break }io.writeLine("bound {item}")}io.writeLine("{result}")
grouped 1 grouped 3 bound 1 bound 3 ()
Expected result
Expected result
Execution completes. Exit code 0.
grouped 1grouped 3bound 1bound 3()
Both loops skip item 2 and stop before item 4. Grouping a for at a function tail and binding its result preserve the same loop targets.
Source evidence
- inferLoopExpression and inferForExpression at
b48527d0a174· 493-600 - iteration, iterationBinding, and emitFor at
b48527d0a174· 1840-1893
For cannot supply an array result
Linklet values: Array<int> = for item in ([1, 2]) { item * 2 }
No stdout.
Expected result
Expected result
Rejection during check. Nonzero exit.
TPZ5001for-result-type-mismatch atexample.tpz:1:26
A for expression has type (). An array annotation therefore rejects this initializer. Use an array comprehension to collect the body values.
Source evidence
- inferLoopExpression and inferForExpression at
b48527d0a174· 493-600 - iteration, iterationBinding, and emitFor at
b48527d0a174· 1840-1893 - reportExpectedType and checkExpressionExpectedResult at
570530e69141· 1660-1748
Collect values with an array comprehension
Linkimport std.iolet values: Array<int> = [for item in ([1, 2]) => item * 2]io.writeLine("{values[0]} {values[1]}")
2 4
Expected result
Expected result
Execution completes. Exit code 0.
2 4
The collection delimiters request array construction. The body supplies one element per visited input.
Source evidence
- inferLoopExpression and inferForExpression at
b48527d0a174· 493-600 - iteration, iterationBinding, and emitFor at
b48527d0a174· 1840-1893 - comprehensionClauses and emitComprehension collect into the requested collection at
271329b482bc· 1890-1923
Trailing layout preserves values and discard evaluates once
Linkimport std.iofunction inlineTail() -> int { 7 }function linesTail() -> int {7}function commentedTail() -> int {7 // Trailing layout preserves this value.}function mark(value: int) -> int {io.writeLine("mark {value}")value}let discarded: () = {let _ = mark(9)}let bound: int = { 11 }io.writeLine("{inlineTail()}|{linesTail()}|{commentedTail()}|{bound}")io.writeLine("done")
mark 9 7|7|7|11 done
Expected result
Expected result
Execution completes. Exit code 0.
mark 97|7|7|11done
A unit context accepts successful and failed if let expressions without else
Linklet takenInput: int = 7let skippedInput: int = 8let taken: () = if let 7 = (takenInput) { 9 }let skipped: () = if let 7 = (skippedInput) { "discarded" }
No stdout.
Expected result
Expected result
Checking accepts the program. Exit code 0.
Source evidence
- issues/31 at
570530e69141 - parseIfLet at
23582e1d56db· 590-660 - inferMatchExpression at
23582e1d56db· 214-315 - emitMatch at
23582e1d56db· 2274-2308
An if let without else discards branch values and preserves selected effects
Linkimport std.iolet takenInput: int = 7let skippedInput: int = 8let taken = if let 7 = (takenInput) {io.writeLine("then")9}let skipped = if let 7 = (skippedInput) {io.writeLine("unexpected")"discarded"}io.writeLine("{taken}")io.writeLine("{skipped}")
then () ()
Expected result
Expected result
Execution completes. Exit code 0.
then()()
Source evidence
- issues/31 at
570530e69141 - parseIfLet at
23582e1d56db· 590-660 - inferMatchExpression at
23582e1d56db· 214-315 - emitMatch at
23582e1d56db· 2274-2308
A runtime fault leaves defer output absent
Linkimport std.iofunction divide(divisor: int) -> int {defer { io.writeLine("cleanup") }io.writeLine("before fault")12 / divisor}io.writeLine("{divide(0)}")
before fault
Expected result
Expected result
Rejection during run. Nonzero exit.
TPZ4002diagnostic-tpz4002 atexample.tpz:5:5
before fault
The registered defer is not executed. The program prints before fault, then terminates at the division.
Source evidence
- issues/31 at
1cbf38d3d422
Normal completion runs the registered defer
Linkimport std.iofunction divide(divisor: int) -> int {defer { io.writeLine("cleanup") }io.writeLine("before result")12 / divisor}io.writeLine("{divide(2)}")
before result cleanup 6
Expected result
Expected result
Execution completes. Exit code 0.
before resultcleanup6
Source evidence
- issues/31 at
1cbf38d3d422
Related rules and diagnostics
LinkSee Pattern matching for pattern forms and matching, Expressions and operators for range expressions, and Functions and generics for function declarations and return inference.