7. Statements and control flow
7.1 Selection and results
CS-SELECT-001. Selection uses first-match semantics with no fall-through to another case. Statement-form select does not require a take.
CS-SELECT-002. In an expression-valued select, take expression produces the value of the selected case. It is not an action return. Ordinary statements may precede the result.
let fee = select customer.Type
case .Vip
take 0
case .Business
take 10
else
take 20
end select
CS-SELECT-003. Every normally completing selected path must produce a value with a final take. The take must be the final normally executed item on that case path. An exceptional or non-completing path does not need to manufacture a value.
CS-SELECT-004. An expression selection must have an else producing a value unless the compiler proves the selection otherwise complete.
CS-SELECT-005. Result expressions must have a valid common or contextual target type under ordinary DirectSharp typing rules. Only the selected path supplies the result; unselected result expressions are not evaluated.
CS-SELECT-006. return exits its enclosing action/callable body. An inline run establishes its own callable boundary: return there completes that branch under CS-CONCURRENT-023, not its enclosing action. take does not become valid in if, loops, arbitrary blocks, structured try, actions or constructors merely because expression selection supports it.
The supporting scopes are use/use_if, bounded using, multiline try_get, and expression-valued select, each with its own rules.
7.2 Diagnostics and absence
Reject a normally completing selected path with no result, reachable ordinary statements after its final take, incomplete expression selection without else, and incompatible result types.
let fee = select customer.Type
case .Vip
LogVip(customer) // invalid: normal path has no take
else
take 20
end select
7.3 Reference C# lowering
For a bound enum subject and Integer results, the first example can use a C# switch:
int fee;
switch (customer.Type)
{
case CustomerType.Vip:
fee = 0;
break;
case CustomerType.Business:
fee = 10;
break;
default:
fee = 20;
break;
}
CustomerType is the illustrative bound enum type. This is not a required emitted name. More complex cases need control-flow analysis: exceptions propagate, result expressions run only on the selected path, and action returns retain their meaning. The backend must not assign a dummy value to hide a missing normal-path result.
7.4 Conditions and case binding
CS-STMT-001. if condition then selects one path; else if conditions are tested in authored order only until a path is selected. Conditions must produce Boolean. A missing else simply continues after the statement when no branch is selected. The block ends with end if.
if orderTotal > availableCredit then
status = "Credit review"
else if orderTotal >= 100 then
status = "Free delivery"
else
status = "Standard delivery"
end if
CS-STMT-002. select evaluates its subject once and considers cases in source order. A case can list exact values, compare against the subject, use a range predicate or perform supported type/pattern binding. A bound case local exists only on its selected path. There is no fall-through or second execution of the subject expression.
Known enum context permits shorthand members such as .Approved. An unqualified member is not resolved by searching arbitrary enum types; the required enum type must already be unambiguous.
7.5 Sequential enumeration and loop control
CS-LOOP-001. for_each item in source obtains source items in enumeration order. A where condition filters each candidate before body entry. order_by, skip and take sequence shaping retain the established filter/order/skip/take pipeline; current loop keys refer to original source positions where those exist. Count/range-based work is sequential unless concurrent enumeration is explicitly requested.
Numeric range iteration uses for_each i in range start to end [step expression] ... end for_each; it does not introduce a separate for i = ... statement family. to is inclusive. The default step follows the direction from start to end; an explicit step supplies the increment. Range iteration must not wrap on numeric overflow. The iteration binding is implicitly readonly, so the body cannot assign or increment i; the range supplies each value.
for_each i in range 1 to 10
ProcessNumber(i)
end for_each
for_each i in range 10 to 0 step -2
ProcessNumber(i)
end for_each
The application supplies ProcessNumber. The first loop visits 1 through 10; the second visits 10, 8, 6, 4, 2 and 0. Optional loop-shaping clauses appear in the order where, order_by, skip, take: filter candidates, order them, skip the requested count, then take the requested count before entering the body. These clauses are distinct from the scoped result-producing take statement and ordinary collection APIs.
for_each total in totals
if total < 0 then
continue
end if
if total > creditLimit then
break
end if
ProcessTotal(total)
end for_each
ProcessTotal is an application action and totals supplies a typed sequence. The first rejected total enters the loop body and continues; a where-filtered item would not enter the body at all.
CS-LOOP-002. Unqualified break exits the innermost enclosing loop. Bare exit is exactly equivalent to break, including cleanup and concurrent scheduling behavior. Unqualified continue ends only the current iteration of the innermost loop and proceeds according to that loop's condition/acquisition rule. An ordinary loop return exits its enclosing action/callable body; inside an inline run it completes that branch under CS-CONCURRENT-023. Bare exit, break and continue are invalid outside an applicable loop or across a callable boundary. Loops have no else clause.
For sequential nested loops, exit for, exit while and exit repeat leave the nearest enclosing loop of the named kind, crossing intervening loops of other kinds when necessary. exit for targets the counter/range/enumeration family, including for_each. A post-test repeat with a trailing while condition remains a repeat loop and is targeted by exit repeat. A target with no enclosing matching loop is a compile-time error. Required finally, resource disposal and other structured cleanup run on the transfer; a targeted exit cannot skip them. exit try is a separate structured-try operation defined in chapter 13.
CS-LOOP-003. while condition tests before each body entry and ends with end while; a false first condition produces zero iterations. repeat count times runs zero iterations for a nonpositive count; repeat forever is explicit unbounded repetition. Post-test loops use the repeat keyword, execute a body at least once and test afterward. A continue in a post-test loop must still reach the loop's condition evaluation.
A post-test loop closes with until condition or while condition, without a subsequent end repeat. until stops when its condition is true; while continues while its condition is true. Both conditions must be Boolean. Counted repetition and repeat forever close with end repeat.
while remaining > 0
remaining -= 1
ProcessNext()
end while
repeat 3 times
RefreshCache()
end repeat
repeat
remaining -= 1
ProcessNext()
until remaining <= 0
repeat
remaining -= 1
ProcessNext()
while remaining > 0
The application actions and counters in this fragment are supplied by its enclosing action. The two post-test examples each execute their body at least once, even if remaining is initially zero; choose a pre-test while when an initially false condition must prevent body entry. Counter-based iteration does not become parallel from the presence of await; parallel range processing is concurrent enumeration over a range source.
CS-LOOP-004. Ordinary enumeration over IAsyncEnumerable<T> selects asynchronous acquisition from the source type. There is no separate await for_each keyword. Async suspension must still satisfy the enclosing action's async contract and backend awaitability rules.
7.6 Loop metadata
CS-LOOP-005. CurrentLoop describes the innermost executing loop. Iteration is the one-based count of body entries; filtered-out candidates do not increment it. ItemKey is the original zero-based index or source key where the source supplies one. An unsupported key property is a compile-time error, not a synthesized index.
CS-LOOP-006. LastLoop belongs to the lexical block containing the completed loop and does not leak from an inner block. NumberOfIterations records body entries; zero entries produce zero. Item and ItemKey are available only for loop/source forms that define them; no-entry item metadata is nothing under its supported contextual type contract. Reading LastLoop before a preceding loop has completed on the control-flow path is invalid.
LastLoop.TerminatedByExit is true when the loop is explicitly terminated by break, bare exit or a matching targeted loop exit. It is false when the loop completes normally through its condition or source exhaustion, including zero iterations. continue ends an iteration rather than terminating the loop. The flag describes the loop being exited and does not change the equivalence of bare exit and break. Concurrent completion is observed only after the required join.
These intrinsics do not require heap-allocated ordinary objects. Their lifetime and typing are compiler context, not a global process-wide last-loop state.
7.7 Concurrent loop scheduling
CS-LOOP-007. Concurrent enumeration accepts a positive integer limit or a shared Gate. A nonpositive constant is rejected at compile time; a nonpositive dynamic integer raises a runtime argument error. An empty source completes normally with zero iterations. Variables declared within an iteration are iteration-local.
CS-LOOP-008. The coordinator evaluates concurrent loop conditions sequentially. When the limit is full, it waits for capacity before evaluating the next condition. A condition that disallows another iteration stops scheduling and joins active iterations: false for a while condition, true for an until condition. A concurrent post-test loop starts at least one iteration, then checks the condition before later scheduling.
Pre-test syntax is concurrent while condition with limit: expression ... end while. Post-test syntax is concurrent repeat with limit: expression ... until condition or concurrent repeat with limit: expression ... while condition, without a subsequent end repeat. The limit accepts a positive integer or Gate under the same capacity rules as concurrent enumeration. Conditions must be Boolean; the coordinator performs each test, not an independent test in each worker.
CS-LOOP-009. Concurrent break or equivalent bare exit stops new scheduling and joins active iterations without automatically cancelling them. Concurrent continue ends the current iteration only. Direct return from a concurrent iteration body is a compile-time error. Nested concurrent loops have separate structured lifetimes; a nested break or bare exit affects only the innermost enclosing loop and joins its active iterations.
A targeted loop exit in a concurrent iteration may leave a nested loop within that iteration or target its own containing concurrent loop. Targeting that concurrent loop stops its scheduling and joins active iterations without automatic cancellation. Targeting a loop outside the containing concurrent loop is a compile-time error. Apply this boundary independently to each nested concurrent loop: an iteration of an inner concurrent loop cannot target an outer concurrent or ordinary loop beyond its containing loop. Target resolution does not cross the boundary merely because the named loop kind matches.
CS-LOOP-010. Unsynchronized shared captured writes in concurrent iteration must be diagnosed. Explicit atomic/Gate coordination is used for shared state where appropriate; swap has the specific automatically coordinated exception in chapter 17. Iteration-local declarations do not share one mutable slot. A concurrency limit alone is not proof of race freedom.
Chapter 17 defines structured concurrent operations, result publication, Gates and cancellation. Condition coordination and loop-exit joining are distinct from worker completion order and exception policy.
7.8 Diagnostics and lowering
Reject non-Boolean conditions, unresolved case member context, unsupported contextual metadata, bare exit/break/continue outside a loop, a targeted loop exit without a matching enclosing loop, a targeted exit to a loop outside the containing concurrent loop, return crossing a concurrent iteration boundary, invalid limits and unsafely shared captured writes. Diagnose the authored source construct and the relevant declarations; for an invalid outward targeted exit, identify the concurrent boundary and requested outer target.
Sequential for_each over a known IEnumerable<T> can lower to ordinary foreach; the asynchronous counterpart uses await foreach inside an async backend action. A coordinator for bounded concurrent enumeration must await capacity before evaluating the next scheduling condition, and must join already-started children after normal scheduling stop or break. Lowering a break to unconditional cancellation would change observable behavior.
A sequential targeted exit can lower to a generated label outside its target loop when a backend break would leave only an intervening loop. For example, batches below is an existing queue of numeric sequences:
while batches.Count > 0
for_each total in batches.Dequeue()
if total < 0 then
exit while
end if
ProcessTotal(total)
end for_each
end while
ContinueAfterBatches()
Reference C# lowering:
while (batches.Count > 0)
{
foreach (var total in batches.Dequeue())
{
if (total < 0)
goto __after_batches;
ProcessTotal(total);
}
}
__after_batches:
ContinueAfterBatches();
exit while stops the outer loop. At the same source location, break, bare exit or exit for would stop only the inner enumeration, allowing the outer loop to process another batch. The generated label illustrates one backend technique. Source-level marks and goto are defined in section 7.10; required disposal and cleanup must still occur before reaching a destination.
7.9 Named loops
CS-LOOP-011. A loop may carry the optional clause with name: identifier. It names that loop for control transfers; it does not declare an ordinary value, change iteration order or change the loop's matching end. Names follow DirectSharp's case-insensitive identifier rules.
The clause applies to applicable for, for_each, while and repeat loop forms, including counted, unbounded, post-test and concurrent forms. It does not supply a new iteration grammar: numeric ranges retain the for_each … in range form defined in section 7.5. Write the name on the opening header, including on a post-test repeat; its trailing condition remains the closer.
| Loop form | Named opening header |
|---|---|
| Enumeration | for_each customer in customers with name: customers_loop |
| Numeric range | for_each i in range 1 to 10 with name: numbers_loop |
| Pre-test | while remaining > 0 with name: batches_loop |
| Counted repetition | repeat 3 times with name: attempts_loop |
| Unbounded repetition | repeat forever with name: service_loop |
| Post-test repetition | repeat with name: polling_loop |
| Concurrent enumeration | concurrent for_each item in items with limit: 4 with name: workers_loop |
| Concurrent pre-test | concurrent while HasWork() with limit: 4 with name: workers_loop |
| Concurrent post-test | concurrent repeat with limit: 4 with name: polling_loop |
The name clause is separate from limit/timeout and sequence-shaping clauses. Naming does not change their evaluation or the established loop pipeline.
CS-LOOP-012. break identifier leaves the named enclosing loop. continue identifier finishes the current iteration of the named enclosing loop and proceeds to that loop's next acquisition, increment or condition test, as appropriate. A named continue to a post-test loop still evaluates its trailing condition. Neither operation restarts the loop or reinitializes its iteration source. An unresolved, ambiguous or non-enclosing loop target is invalid.
for_each customer in customers with name: customers_loop
for_each order in customer.Orders
if order.IsInvalid then
// Skip the rest of this customer's orders.
continue customers_loop
end if
if order.IsCritical then
// Stop processing customers altogether.
break customers_loop
end if
ProcessOrder(order)
end for_each
end for_each
The enclosing application supplies customers and ProcessOrder. The name selects a particular enclosing loop, while exit while selects the nearest enclosing loop of a kind. Bare break, continue and exit retain their existing meanings; this amendment adds no exit identifier form. Naming does not change CurrentLoop into a name-indexed API.
CS-LOOP-013. Named loop transfers preserve required cleanup and existing callable/concurrent iteration boundaries. A named break targeting its containing concurrent loop stops new scheduling and joins admitted iterations without automatic cancellation. A named continue targeting that loop completes the current iteration only. Neither can target a loop outside that concurrent iteration's containing loop. Apply the same boundary independently to nested concurrent loops and inline run callables.
For example, naming an outer ordinary loop does not make it a legal target from an inner concurrent iteration. A loop name cannot be used to bypass a finally, resource disposal or required owned-work settlement.
7.10 Marks and general jumps
CS-JUMP-001. mark identifier declares a general-purpose jump destination at a statement position. It is not an ordinary variable or a loop declaration, and it does not open a block. Reaching the mark sequentially simply proceeds with the following statement. The source spelling has no trailing colon.
CS-JUMP-002. goto identifier transfers control to the corresponding mark. Both forward and backward jumps are supported; destination identity is case-insensitive. A goto names a mark, whereas a named break or continue names an enclosing loop. These statements are not interchangeable. A goto does not call an action, return a value, catch a failure or introduce retry semantics.
public class DeliveryAttempt
public static action Send()
returns Boolean
let remaining = 3
mark next_attempt
if TrySend() then
goto sent
end if
remaining -= 1
if remaining > 0 then
goto next_attempt
end if
return false
mark sent
return true
end action
end class
TrySend is a Boolean application action. All state in this example is declared before the marks. The forward jump reaches the successful exit; the backward jump performs another attempt while the counter permits it. A backward goto is ordinary control flow, not a structured retry of a try body.
CS-JUMP-003. A mark does not authorize bypassing lexical scope, definite assignment, required cleanup or structured ownership. A goto cannot cross an action/callable boundary or provide an additional escape across a concurrent iteration boundary. Exited scopes retain their cleanup obligations; a goto alone does not complete an action. A later normal return or normal fallthrough still checks applicable ensure contracts after required cleanup. Unresolved or ambiguous destinations are invalid.
These rules do not make jumping into a protected region or across an initialization automatically legal. Detailed destination visibility and region-entry cases must be reconciled with the existing scope and resource rules before accepting such transfers. Use a named loop transfer when the intent is to end an enclosing loop or its iteration; use a mark for a distinct statement destination.
Specification contents · Previous: 6. Expressions and operators · Next: 8. Actions and callables