Skip to content
Topaz6.0 · Before release
Download

Search documentation

Specification 6.0 · Not released · Tested product 5.21.3

Use arrow keys to choose a result. Press Enter to open.

Type an API name, diagnostic number, rule, or project topic.

    Language · chapter 11 · 14 rules

    Blocks and control flow

    Blocks group execution and produce final values. Conditional branches, loops, explicit transfers, and deferred actions determine which code runs next.

    Scope and ownership

    Link

    Edition: 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

    Link
    EBNF
    1. Block = "{" Statement* Expression? "}" ;
    2. IfExpression = "if" IfHead Block ("else" (IfExpression | Block))? ;
    3. IfHead = ControlOperand | "let" Pattern "=" ControlOperand ;
    4. WhileStatement = "while" WhileHead Block ;
    5. WhileHead = ControlOperand | "let" Pattern "=" ControlOperand ;
    6. ForExpression = "for" Pattern "in" ControlOperand Block ;
    7. LoopExpression = "loop" Label? Block ;
    8. BreakStatement = "break" Label? Expression? ;
    9. ContinueStatement = "continue" Label? ;
    10. ReturnStatement = "return" Expression? ;
    11. DeferStatement = "defer" Block ;
    12. 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..<6 by 2) { ... }, by 2 is part of the range expression. Range construction and bounds belong to Expressions and operators.

    Source evidence

    Blocks and final values

    Link
    Ruletopaz.rule.control-flow.block-sequence

    Block 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 _ = expression 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

    If, else, and if let

    Link
    Ruletopaz.rule.control-flow.conditional-selection

    Choosing 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
    Ruletopaz.rule.control-flow.if-let-selection

    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

    While, while let, and for

    Link
    Ruletopaz.rule.control-flow.while-iteration

    Testing 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
    Ruletopaz.rule.control-flow.while-let-iteration

    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
    Ruletopaz.rule.control-flow.for-iteration

    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
    Ruletopaz.rule.control-flow.for-admission

    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

    Loop labels, break, continue, and return

    Link
    Ruletopaz.rule.control-flow.loop-targets

    Selecting 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
    Ruletopaz.rule.control-flow.loop-value

    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
    Ruletopaz.rule.control-flow.return-transfer

    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
    Ruletopaz.rule.control-flow.non-value-loop-break

    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

    Deferred cleanup

    Link
    Ruletopaz.rule.control-flow.defer-shape

    Registering 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
    Ruletopaz.rule.control-flow.defer-order

    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
    Ruletopaz.rule.control-flow.fault-skips-cleanup

    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

    Complete examples and expected results

    Link

    Each 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

    Link
    example.tpzA block value and a selected branchOpen in Playground
    1. import std.io
    2. function choose() -> int {
    3. let value = {
    4. let base = 4
    5. base + 3
    6. }
    7. if (value > 5) {
    8. value * 2
    9. } else {
    10. 0
    11. }
    12. }
    13. io.writeLine("{choose()}")
    Recorded outputexit 0
    14
    
    $ topaz run example.tpz --compiler self · recorded with topaz 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. 14
    Observed assertions · passed · 5.21.3

    Exit code 0.

    Observed stdout
    1. 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

    Using if let and while let

    Link
    example.tpzUsing if let and while letOpen in Playground
    1. import std.io
    2. let mut next: Option<int> = Some(3)
    3. if let Some(value) = (next) {
    4. io.writeLine("ready: {value}")
    5. } else {
    6. io.writeLine("empty")
    7. }
    8. while let Some(value) = (next) {
    9. io.writeLine("{value}")
    10. next = None()
    11. }
    12. io.writeLine("done")
    Recorded outputexit 0
    ready: 3
    3
    done
    
    $ topaz run example.tpz --compiler self · recorded with topaz 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. ready: 3
    2. 3
    3. done
    Observed assertions · passed · 5.21.3

    Exit code 0.

    Observed stdout
    1. ready: 3
    2. 3
    3. done

    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

    Continue in while and break in for

    Link
    example.tpzContinue in while and break in forOpen in Playground
    1. import std.io
    2. let mut tick = 0
    3. while (tick < 3) {
    4. tick += 1
    5. if (tick == 2) {
    6. continue
    7. }
    8. io.writeLine("{tick}")
    9. }
    10. for n in (0..<8 by 2) {
    11. if (n == 4) {
    12. break
    13. }
    14. io.writeLine("{n}")
    15. }
    16. io.writeLine("done")
    Recorded outputexit 0
    1
    3
    0
    2
    done
    
    $ topaz run example.tpz --compiler self · recorded with topaz 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. 1
    2. 3
    3. 0
    4. 2
    5. done
    Observed assertions · passed · 5.21.3

    Exit code 0.

    Observed stdout
    1. 1
    2. 3
    3. 0
    4. 2
    5. done

    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

    Breaking out of an enclosing labeled loop

    Link
    example.tpzBreaking out of an enclosing labeled loopOpen in Playground
    1. import std.io
    2. let result: int = loop 'outer {
    3. loop {
    4. break 'outer 7
    5. }
    6. }
    7. io.writeLine("{result}")
    Recorded outputexit 0
    7
    
    $ topaz run example.tpz --compiler self · recorded with topaz 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. 7
    Observed assertions · passed · 5.21.3

    Exit code 0.

    Observed stdout
    1. 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

    Running deferred actions before returning

    Link
    example.tpzRunning deferred actions before returningOpen in Playground
    1. import std.io
    2. function result() -> int {
    3. io.writeLine("value")
    4. 7
    5. }
    6. function finish() -> int {
    7. defer { io.writeLine("first") }
    8. defer {
    9. io.writeLine("second")
    10. }
    11. io.writeLine("body")
    12. return result()
    13. }
    14. io.writeLine("{finish()}")
    Expected outputexit 0
    body
    value
    second
    first
    7
    
    $ topaz run example.tpz --compiler self · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. body
    2. value
    3. second
    4. first
    5. 7

    The return operand runs before cleanup. Deferred blocks run in reverse registration order.

    Source evidence

    Cleaning up scopes during error propagation

    Link
    example.tpzCleaning up scopes during error propagationOpen in Playground
    1. import std.io
    2. function fail() -> Result<int, string> {
    3. io.writeLine("operand")
    4. Err("stop")
    5. }
    6. function finish() -> Result<int, string> {
    7. defer { io.writeLine("outer") }
    8. let value = {
    9. defer { io.writeLine("inner-first") }
    10. defer { io.writeLine("inner-second") }
    11. fail()?
    12. }
    13. io.writeLine("unreachable")
    14. Ok(value)
    15. }
    16. io.writeLine(match (finish()) {
    17. case Ok(value) => "value={value}"
    18. case Err(error) => error
    19. })
    Expected outputexit 0
    operand
    inner-second
    inner-first
    outer
    stop
    
    $ topaz run example.tpz --compiler self · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. operand
    2. inner-second
    3. inner-first
    4. outer
    5. stop

    The operand runs once before cleanup. Error propagation runs inner defers in reverse order, then the outer defer, before returning the error.

    Source evidence

    Rejecting an invalid deferred expression

    Link
    example.tpzRejecting an invalid deferred expressionOpen in Playground
    1. defer 5
    Expected outputNonzero exit
    $ topaz check example.tpz --compiler self · target product 5.21.3
    Expected result

    Expected result

    Rejection during parse. Nonzero exit.

    • TPZ2004 defer-body-not-block at example.tpz:1:7

    The integer operand is rejected at the start of 5. Replace it with a block that performs cleanup.

    Source evidence

    Break operands run before cleanup in statement loops

    Link
    example.tpzBreak operands run before cleanup in statement loopsOpen in Playground
    1. import std.io
    2. function operand(label: string) -> int {
    3. io.writeLine(label)
    4. 7
    5. }
    6. while (true) {
    7. defer { io.writeLine("while cleanup") }
    8. break operand("while value")
    9. }
    10. for item in ([1, 2]) {
    11. defer { io.writeLine("for cleanup") }
    12. break operand("for value")
    13. }
    14. io.writeLine("done")
    Expected outputexit 0
    while value
    while cleanup
    for value
    for cleanup
    done
    
    $ topaz run example.tpz --compiler self · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. while value
    2. while cleanup
    3. for value
    4. for cleanup
    5. done

    The expected result follows from current SELF source. Execution against public Topaz 5.21.3 is pending CI.

    Source evidence

    Break in a bound for preserves its unit type

    Link
    example.tpzBreak in a bound for preserves its unit typeOpen in Playground
    1. let result: () = for item in ([1]) {
    2. break 7
    3. }
    Expected outputexit 0
    No stdout.
    $ topaz check example.tpz --compiler self --format json · target product 5.21.3
    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

    Arrays, sets, ranges, and matching element patterns

    Link
    example.tpzArrays, sets, ranges, and matching element patternsOpen in Playground
    1. import std.io
    2. for item in ([1, 2]) {
    3. io.writeLine("array {item}")
    4. }
    5. for item in (set { 3, 1, 3 }) {
    6. io.writeLine("set {item}")
    7. }
    8. for item in (2..4) {
    9. io.writeLine("range {item}")
    10. }
    11. for Some(item) in ([Some(7), Some(8)]) {
    12. io.writeLine("matched {item}")
    13. }
    Expected outputexit 0
    array 1
    array 2
    set 3
    set 1
    range 2
    range 3
    range 4
    matched 7
    matched 8
    
    $ topaz run example.tpz --compiler self · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. array 1
    2. array 2
    3. set 3
    4. set 1
    5. range 2
    6. range 3
    7. range 4
    8. matched 7
    9. matched 8

    The expected result follows from current SELF source. Execution against public Topaz 5.21.3 is pending CI.

    Source evidence

    A string must be converted to scalar elements before iteration

    Link
    example.tpzA string must be converted to scalar elements before iterationOpen in Playground
    1. for item in ("hi") {
    2. }
    Expected outputNonzero exit
    $ topaz check example.tpz --compiler self · target product 5.21.3
    Expected result

    Expected result

    Rejection during check. Nonzero exit.

    • TPZ5001 for-non-iterable-type at example.tpz:1:14

    The expected result follows from current SELF source. Execution against public Topaz 5.21.3 is pending CI.

    Source evidence

    An omitted else discards values and preserves selected effects

    Link
    example.tpzAn omitted else discards values and preserves selected effectsOpen in Playground
    1. import std.io
    2. let taken = if (true) {
    3. io.writeLine("then")
    4. 7
    5. }
    6. let skipped = if (false) {
    7. io.writeLine("unexpected")
    8. 9
    9. }
    10. io.writeLine("{taken}")
    11. io.writeLine("{skipped}")
    Expected outputexit 0
    then
    ()
    ()
    
    $ topaz run example.tpz --compiler self · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. then
    2. ()
    3. ()
    Source evidence

    An omitted else cannot produce an int result

    Link
    example.tpzAn omitted else cannot produce an int resultOpen in Playground
    1. let value: int = if (true) { 7 }
    Expected outputNonzero exit
    No stdout.
    $ topaz check example.tpz --compiler self · target product 5.21.3
    Expected result

    Expected result

    Rejection during check. Nonzero exit.

    • TPZ5001 if-without-else-result-type-mismatch at example.tpz:1:18
    Source evidence

    Rejecting a deferred call without a block

    Link
    example.tpzRejecting a deferred call without a blockOpen in Playground
    1. import std.io
    2. function finish() -> () {
    3. defer io.writeLine("cleanup")
    4. }
    5. finish()
    Expected outputNonzero exit
    $ topaz check example.tpz --compiler self · target product 5.21.3
    Expected result

    Expected result

    Rejection during parse. Nonzero exit.

    • TPZ2004 defer-body-not-block at example.tpz:4:11

    Wrap the cleanup call in a defer block. The current SELF parser still accepts this removed form.

    Source evidence

    Running a deferred block at scope exit

    Link
    example.tpzRunning a deferred block at scope exitOpen in Playground
    1. import std.io
    2. function finish() -> () {
    3. defer { io.writeLine("cleanup") }
    4. io.writeLine("body")
    5. }
    6. finish()
    Expected outputexit 0
    body
    cleanup
    
    $ topaz run example.tpz --compiler self · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. body
    2. cleanup

    The body prints before the registered cleanup block runs.

    Source evidence

    Returning through a match arm block

    Link
    example.tpzReturning through a match arm blockOpen in Playground
    1. import std.io
    2. function choose(flag: bool) -> int {
    3. while (true) {
    4. let value = match (flag) {
    5. case true => { return 7 }
    6. case false => 3
    7. }
    8. return value
    9. }
    10. 0
    11. }
    12. io.writeLine("{choose(true)}")
    13. io.writeLine("{choose(false)}")
    Expected outputexit 0
    7
    3
    
    $ topaz run example.tpz --compiler self · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. 7
    2. 3

    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

    For results stay unit in grouped and bound positions

    Link
    example.tpzFor results stay unit in grouped and bound positionsOpen in Playground
    1. import std.io
    2. function items(label: string) -> Array<int> {
    3. io.writeLine(label)
    4. [1, 2]
    5. }
    6. let bound = for item in (items("bound")) {
    7. io.writeLine("{item}")
    8. item * 2
    9. }
    10. let grouped = (for item in (items("grouped")) {
    11. io.writeLine("{item}")
    12. item * 3
    13. })
    14. let emptyItems: Array<int> = []
    15. let empty = for item in (emptyItems) { item }
    16. io.writeLine("{bound} {grouped} {empty}")
    Expected outputexit 0
    bound
    1
    2
    grouped
    1
    2
    () () ()
    
    $ topaz run example.tpz --compiler self · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. bound
    2. 1
    3. 2
    4. grouped
    5. 1
    6. 2
    7. () () ()

    The iterator calls and body output establish evaluation order. Binding, parentheses, and an empty iterator all retain the unit result.

    Source evidence

    Break and continue survive grouping and binding

    Link
    example.tpzBreak and continue survive grouping and bindingOpen in Playground
    1. import std.io
    2. function grouped() -> () {
    3. (for item in ([1, 2, 3, 4]) {
    4. if (item == 2) { continue }
    5. if (item == 4) { break }
    6. io.writeLine("grouped {item}")
    7. })
    8. }
    9. grouped()
    10. let result = for item in ([1, 2, 3, 4]) {
    11. if (item == 2) { continue }
    12. if (item == 4) { break }
    13. io.writeLine("bound {item}")
    14. }
    15. io.writeLine("{result}")
    Expected outputexit 0
    grouped 1
    grouped 3
    bound 1
    bound 3
    ()
    
    $ topaz run example.tpz --compiler self · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. grouped 1
    2. grouped 3
    3. bound 1
    4. bound 3
    5. ()

    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

    For cannot supply an array result

    Link
    example.tpzFor cannot supply an array resultOpen in Playground
    1. let values: Array<int> = for item in ([1, 2]) { item * 2 }
    Expected outputNonzero exit
    No stdout.
    $ topaz check example.tpz --compiler self --format json · target product 5.21.3
    Expected result

    Expected result

    Rejection during check. Nonzero exit.

    • TPZ5001 for-result-type-mismatch at example.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

    Collect values with an array comprehension

    Link
    example.tpzCollect values with an array comprehensionOpen in Playground
    1. import std.io
    2. let values: Array<int> = [for item in ([1, 2]) => item * 2]
    3. io.writeLine("{values[0]} {values[1]}")
    Expected outputexit 0
    2 4
    
    $ topaz run example.tpz --compiler self · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. 2 4

    The collection delimiters request array construction. The body supplies one element per visited input.

    Source evidence

    Trailing layout preserves values and discard evaluates once

    Link
    example.tpzTrailing layout preserves values and discard evaluates onceOpen in Playground
    1. import std.io
    2. function inlineTail() -> int { 7 }
    3. function linesTail() -> int {
    4. 7
    5. }
    6. function commentedTail() -> int {
    7. 7 // Trailing layout preserves this value.
    8. }
    9. function mark(value: int) -> int {
    10. io.writeLine("mark {value}")
    11. value
    12. }
    13. let discarded: () = {
    14. let _ = mark(9)
    15. }
    16. let bound: int = { 11 }
    17. io.writeLine("{inlineTail()}|{linesTail()}|{commentedTail()}|{bound}")
    18. io.writeLine("done")
    Expected outputexit 0
    mark 9
    7|7|7|11
    done
    
    $ topaz run example.tpz --compiler self · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. mark 9
    2. 7|7|7|11
    3. done

    A unit context accepts successful and failed if let expressions without else

    Link
    example.tpzA unit context accepts successful and failed if let expressions without elseOpen in Playground
    1. let takenInput: int = 7
    2. let skippedInput: int = 8
    3. let taken: () = if let 7 = (takenInput) { 9 }
    4. let skipped: () = if let 7 = (skippedInput) { "discarded" }
    Expected outputexit 0
    No stdout.
    $ topaz check example.tpz --compiler self --format json · target product 5.21.3
    Expected result

    Expected result

    Checking accepts the program. Exit code 0.

    Source evidence

    An if let without else discards branch values and preserves selected effects

    Link
    example.tpzAn if let without else discards branch values and preserves selected effectsOpen in Playground
    1. import std.io
    2. let takenInput: int = 7
    3. let skippedInput: int = 8
    4. let taken = if let 7 = (takenInput) {
    5. io.writeLine("then")
    6. 9
    7. }
    8. let skipped = if let 7 = (skippedInput) {
    9. io.writeLine("unexpected")
    10. "discarded"
    11. }
    12. io.writeLine("{taken}")
    13. io.writeLine("{skipped}")
    Expected outputexit 0
    then
    ()
    ()
    
    $ topaz run example.tpz --compiler self · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. then
    2. ()
    3. ()
    Source evidence

    A runtime fault leaves defer output absent

    Link
    example.tpzA runtime fault leaves defer output absentOpen in Playground
    1. import std.io
    2. function divide(divisor: int) -> int {
    3. defer { io.writeLine("cleanup") }
    4. io.writeLine("before fault")
    5. 12 / divisor
    6. }
    7. io.writeLine("{divide(0)}")
    Expected outputNonzero exit
    before fault
    
    $ topaz run example.tpz --compiler self · target product 5.21.3
    Expected result

    Expected result

    Rejection during run. Nonzero exit.

    • TPZ4002 diagnostic-tpz4002 at example.tpz:5:5
    Expected stdout
    1. before fault

    The registered defer is not executed. The program prints before fault, then terminates at the division.

    Source evidence

    Normal completion runs the registered defer

    Link
    example.tpzNormal completion runs the registered deferOpen in Playground
    1. import std.io
    2. function divide(divisor: int) -> int {
    3. defer { io.writeLine("cleanup") }
    4. io.writeLine("before result")
    5. 12 / divisor
    6. }
    7. io.writeLine("{divide(2)}")
    Expected outputexit 0
    before result
    cleanup
    6
    
    $ topaz run example.tpz --compiler self · target product 5.21.3
    Expected result

    Expected result

    Execution completes. Exit code 0.

    Expected stdout
    1. before result
    2. cleanup
    3. 6
    Source evidence

    Related rules and diagnostics

    Link

    Invalid defer body

    Functions and generics

    See Pattern matching for pattern forms and matching, Expressions and operators for range expressions, and Functions and generics for function declarations and return inference.

    TPZ5001

    Division or remainder by zero

    Menu

    Download