Zax switch, case, and default
| Field | Value |
|---|---|
| Status | Current conceptual design |
| Audience | Human developers selecting runtime behavior from one value |
| Applies To | Programmer-facing switch, case, and default behavior; not a formal grammar or specification |
| Implementation State | Not established by this repository |
| Owns | Runtime selector capture; ordinary ordered case testing; direct, equality, and pre-unary case interpretation; variant-specific alternative routing and payload binding; comma-separated alternatives; case and switch posts; switch and case labels; switch-specific break, continue, next, and goto behavior; default, incomplete selection, exhaustiveness, overlap, reachability; case hint and promise slots; outward declarations in cases; costs, diagnostics, formatting, and source stability |
| Does Not Own | Shared flow-transfer and unwinding rules (core flow control); ordinary operator selection (operators); complete optional behavior (optional values); complete managed-alternative lifecycle (variants); enum reachable domains (enums); general source layout (source structure); or generalized pattern matching |
| Source / Provenance | Current flow, operator, enum, optional, lifetime, source, and intent designs, incorporating reviewed legacy switch evidence |
| Supersedes | Legacy switch sections formerly preserved on flow control |
Start with one selected body
switch evaluates one value and chooses the first matching case:
switch status {
case Status.Ready
startWork()
case Status.Waiting
showWaiting()
default
reportUnknownStatus()
}
Cases are tested from top to bottom. The first successful case runs exactly one
body, and normal completion of that body exits the switch. Zax does not
implicitly flow into the following case, so a trailing break is not required.
default runs only when every preceding case test fails. It is optional. A
switch with no match and no default runs no body and completes normally.
Several alternatives can select one body:
switch responseCode {
case 200, 201, 204
handleSuccess()
case 400, 404
handleClientFailure()
default
handleOtherResponse()
}
Alternatives are tested left to right. Once one succeeds, later alternatives and later cases are not evaluated.
Evaluate and retain the selector once
The selector expression evaluates exactly once:
switch selected := readResponse() ;; selected {
case expectedResponse
use(selected)
default
reportUnexpected(selected)
}
The initializer runs once, then the selector evaluates once. The selected
binding remains visible to every case test and body until the complete switch
exits.
A named selector is observed through a reference to its existing place. A temporary selector is materialized once, retained for the complete switch lifetime, and tested through a reference to that temporary. Selection does not silently copy either form.
switch makeResponse() {
case expectedResponse
handleExpected()
default
handleUnexpected()
}
The temporary has no invented source name. Use an initializer when a body or post needs to name it.
The selector is the receiver for every selector-relative test. Its receiver
qualification and transfer stance participate in ordinary operation selection.
An existing place can therefore be mutated when the selected operation permits
it. A retained temporary can offer last because its lifetime really ends with
the switch, but receiver stance does not itself transfer the selector as a
right-hand argument. No test ends the retained selector lifetime before the
switch exits.
Complete receiver stance and qualification behavior belongs to transfer stances and qualifiers.
Switch headers
switch uses the shared conditional header shapes:
selector
initializer ;; selector
initializer ;; selector ;; post
;; selector ;; post
The initializer accepts one effective statement. Completed initializer bindings are visible to the selector, every reached test, all clause bodies and posts, and the switch post. The switch post runs once on normal completion while those bindings and the retained selector remain alive.
A flow label follows the complete introducer:
switch choose_response: ;; readResponse() ;; recordCompletion() {
default
handleResponse()
}
Header separator spacing and continuation are defined by source structure. Shared initializer, post, and binding-scope behavior is defined by core flow control.
Cases, bodies, and posts
A tested case has one or more comma-separated tests, an optional post operation, and one required effective body statement:
switch value {
case small: 1, 2, 3 ;; recordSmall() {
handleSmall(value)
}
case 5
handleFive(value)
default fallback: ;; recordFallback()
handleFallback(value)
}
The common conceptual source shapes are:
case [shadowable label:] test [, test ...] [;; post] effective-body
default [shadowable label:] [;; post] effective-body
The optional label follows case or default. The optional post follows the
test list after ;;; a default begins its post with ;;. Transfer-only cases
are introduced later with the goto behavior that makes them reachable.
One effective body can be simple, composed with ;, or braced:
case 1
handleOne()
case 2
prepare();
handlePrepared()
case 3 {
prepare()
handlePrepared()
}
Every clause owns a scope around its complete effective body. Operands of a composed body share that scope, but body bindings do not escape to another clause:
case 1
myValue := makeValue();
use(myValue)
case 2 {
use(myValue) // error: myValue belonged to the preceding clause
}
Every tested case and every default requires a body. Consecutive bodyless tests never share a later body:
case -1 // error: tested case has no body
case > 2 {
handleLarge()
}
Use comma alternatives for a shared body:
case -1, > 2 {
handleSelected()
}
Use { } to select and intentionally do nothing:
case -1 { }
case 0 ; is invalid because ; lacks a right statement operand. case 2 {}
is invalid because the established empty-block spelling is { }.
Body-local values are destroyed before the clause post runs. The post can use switch-header bindings and surrounding state but cannot use body locals.
Cases have no initializer section. In particular, the following is not a case header shape:
// case local := makeValue() ;; local > 15 // error: cases have no initializer
Use ordinary body declarations and nested selection when later work needs a named value.
Zero or more positional default clauses may appear. Each is the unconditional
fallback for the ordered search that reaches it. Its body must explicitly
continue when selection should proceed into a later test segment.
How one case test is interpreted
For an ordinary non-variant selector, every comma-separated alternative must
ultimately produce exactly Boolean. The case supplies the retained selector as
an omitted operand through three ordered interpretations.
A variant selector instead uses the dedicated alternative-routing mode; its case names are not expressions passed through these interpretations.
1. Try a direct selector-relative operation
First use the selector as the omitted left or receiver operand:
switch value {
case < upperBound
handleBelow()
case 'is hot'
cool(value)
}
The tests mean:
value < upperBound
value is hot
This stage accepts binary and post-unary symbolic or phrase operations. Ordinary operator discovery, operand mapping, viability, preference, qualification, and availability apply.
If one direct interpretation selects an exact-Boolean operation, it wins.
Direct ambiguity and a uniquely best unavailable exact-Boolean operation are
errors rather than permission to try another interpretation.
If no direct interpretation applies, or the direct result is not exactly
Boolean, resolution tries implicit equality.
2. Try equality with the written expression
The complete case alternative becomes the right operand of ==:
switch value {
case expectedValue
handleExpected()
case -amount
handleNegativeAmount()
}
The tests mean:
value == expectedValue
value == (-amount)
The selected equality must return exactly Boolean. It uses the ordinary
operator model, including direct/complement fallback and unavailable-best
behavior.
Grouping commits to equality interpretation:
case (similar expected)
// value == (similar expected)
An explicit operator states the same intent directly:
case == (similar expected)
3. Try an omitted-operand pre-unary operation
Finally, a case alternative containing an exact pre-unary operator component and no supplied operand may apply that operation to the selector:
switch optionalValue {
case ?
use(optionalValue.)
case !
handleAbsent()
}
These tests mean:
?optionalValue
!optionalValue
A phrase can use the same rule:
case 'bad'
// bad value, when `bad` is an applicable pre-unary phrase
This stage never searches arbitrary right-to-left binary trees. It supplies only the missing sole operand of a recognized pre-unary component.
An exact phrase fence fixes the phrase component. It does not override ordinary operation selection. Complete phrase behavior belongs to operator phrases.
Direct-before-equality source stability
The ordered interpretations are part of the switch contract. Adding a newly
applicable exact-Boolean direct operation can change a case that previously
used equality:
case similar expected
It may change from:
value == (similar expected)
to:
value similar expected
Use grouping or explicit == when the equality interpretation must remain
fixed. Tooling should show which interpretation and operation each case selected.
Ordering, effects, and overlap
Selection proceeds in visible order:
- visit clauses from top to bottom;
- visit one case’s alternatives from left to right;
- evaluate each reached alternative’s runtime operands once;
- invoke its selected operation;
- stop testing on the first
trueresult; and - execute that clause body once.
Case expressions and user-defined operations may have effects:
switch value {
case nextCandidate()
useFirstMatch()
case fallbackCandidate()
useSecondMatch()
}
fallbackCandidate() does not run when the first test succeeds. Optimization
may use a table or another dispatch mechanism only when evaluation, selected
operations, effects, panic behavior, lifetimes, and the chosen body remain
unchanged.
Overlap is legal and frequently useful:
switch temperature {
case < freezingPoint
reportFrozen()
case < boilingPoint
reportLiquid()
default
reportAtOrAboveBoiling()
}
The second predicate receives only values not selected by the first. Zax does not assume purity, algebraic laws, or repeatability for arbitrary user-defined operations.
A clause body is reachable through any reachable successful test, positional
default, continue search, or direct goto body entry:
switch mode {
case MyMode.Primary
if usePrimaryFails()
goto alias_version:
case alias_version: MyMode.PrimaryAlias
useAlias()
}
Although generated equality prevents ordinary selection of the alias body after
Primary matched, goto alias_version: makes it reachable. A self-edge inside
an otherwise unreachable body does not bootstrap reachability.
A clause still proven unreachable under the complete flow graph requires a clause-local acknowledgement:
switch mode {
case MyMode.Primary
usePrimary()
// PrimaryAlias has the same value as Primary under generated equality.
intent<unreachable-selection-clause>{
case MyMode.PrimaryAlias
useAlias()
}
}
The enclosure occupies one complete contextual clause position and includes the required body. It cannot enclose only a test while borrowing a body from outside. Uncertain or runtime-dependent overlap requires no acknowledgement.
Case hints and promises
The branch hints and path promises
used on if also apply to cases. The directive goes in the slot after the
case tests or after default, and applies to that clause’s test, not to its
body:
switch value {
case 1 [<likely>]
handleCommon()
case 2
handleOther()
default [<unlikely>]
handleUnexpected()
}
Hints never change which clause is selected or which test effects happen. The ordering rules above still hold: a compiler may try a likely clause’s test first only when that cannot change the selected clause, the operations that run, or their effects. With overlapping tests, or tests that call functions, the visible order stays.
[<never>] on a case promises that the case is never selected.
[<always>] promises that it is always the selected case:
switch mode {
case MyMode.Legacy [<never>] // promise: never selected
legacyPath()
case MyMode.Modern [<always>] // promise: always the selected case
modernPath()
}
These are checked by default, like the if forms: breaking one panics in the
never-path-reached or always-path-missed category, and disabling that
category makes a broken promise undefined behavior.
Outward declarations in cases
An outward declaration in
a case body belongs to the scope around the switch. Every path that continues
past the switch must declare it exactly once, with the same type and
qualifiers. A case that leaves the switch another way needs no declaration:
switch mode {
case 1
limit outward final : Integer = 10
case 2
limit outward final : Integer = 1000
case 3
return // leaves: needs no limit
default
limit outward final : Integer = 100
}
process(limit)
When one body enters another with goto, both bodies run on that path, so only
one of them may declare a given outward name.
To select a case while compiling, so that only the selected body becomes part of
the program, write switch compile; see
compile-time code.
Normal completion and posts
After a selected body completes normally:
- destroy its body-local values;
- run its clause post;
- run the switch post;
- destroy the retained selector and switch-header bindings; and
- exit the switch.
When no case matches, default runs when present. Without default, the switch
runs no clause. Either normal path still runs the switch post.
A post is ordinary work, not guaranteed cleanup. Transfers select which posts run, while every exited scope is still destroyed. Shared post and unwinding behavior belongs to core flow control.
Every re-entry is a complete scope transition. next, continue, and goto
first destroy the old body scope in reverse construction order. Any resulting
entry begins again at the target body’s first statement with fresh body-local
lifetimes. next runs post and ordinary test/progression first; continue skips
post but performs ordinary test/progression; goto skips both. Active
target-header lifetimes remain alive throughout re-entry.
Transfer summary
next, continue, and goto select three different phases:
nextperforms the target’s normal-completion post phase;continueskips post and resumes the target’s testing or progression;gotoskips post and testing and enters an eligible body directly.
| Transfer | Target point | Target test | Target post | Later behavior |
|---|---|---|---|---|
| Normal clause completion | Current clause completion | Already succeeded | Run case post | Run switch post, exit |
next current_case: |
Current clause completion | Do not repeat | Run case post | Run switch post, exit |
next switch_label: |
Switch completion | Do not repeat | Run switch post | Exit |
continue case_label: |
Target clause’s test entry | Run target tests | Skip source case post | If false, continue with following clauses |
continue switch_label: |
First switch test | Restart testing | Skip source case and switch posts | Retain the existing selector |
Bare goto in a switch clause |
Following unlabeled clause’s body entry | Bypass | Skip source case post | Target post runs if its body later completes normally |
goto case_label: |
Target clause’s body entry | Bypass | Skip source case post | Target post runs if its body later completes normally |
break / break switch_label: |
Switch exit | Bypass | Skip case and switch posts | Exit |
switch choose_value: ;; value ;; recordSelection() {
case first: > 15 ;; recordFirst() {
if shouldRetestSecond()
continue second: // run second:'s tests
if shouldEnterSecondDirectly()
goto second: // bypass second:'s tests
if shouldFinishFirst()
next first: // recordFirst(), recordSelection(), then exit
if shouldFinishSwitch()
next choose_value: // skip recordFirst(), recordSelection(), then exit
}
case second: < 100 ;; recordSecond() {
handleSecond()
}
default fallback:
handleFallback()
}
This table summarizes complete operations. Post execution and test/body entry are not separate hooks that can be combined arbitrarily.
continue: resume testing
Bare continue resumes testing after the current clause:
switch value {
case 1 {
updateValue()
continue // begin testing at case 2
}
case 2
handleTwo()
}
A bare continue skips intervening transfer-only cases while looking for the
next tested case or positional default. In the final searchable segment it is
an error. It does not silently select an outer construct; use an explicit outer
label.
continue case_label: begins with the named case’s first alternative. When all
its alternatives fail, testing continues with later clauses:
switch value {
case retry_small: < 10
handleSmall()
case retry_large: > 100 {
adjust(value)
continue retry_small:
}
}
The target may precede or follow the source. Backward continuation can form an intentional cycle.
continue cannot name a transfer-only case because that label has no test entry.
continue default_label: enters that default body because default is an
unconditional search fallback. continue switch_label: restarts at the first
case without rerunning the switch initializer or selector expression.
Every switch-local continue destroys the source body scope and skips its case
post. The switch remains active, so its post does not run yet.
goto: enter a body directly
Bare goto enters the immediately following unlabeled clause body without
running its tests:
switch value {
case 0 {
prepare()
goto // enter case 1's body directly
}
case 1
usePreparedState()
}
A labeled following clause blocks bare transfer:
case 0 {
goto // error: ready: must be named
}
case ready: 1
usePreparedState()
Bare goto in the final clause is an error and never silently selects an outer
construct. Outside a switch, bare goto directly restarts the nearest eligible
unlabeled active loop, forever, each, or explicit scope body.
goto case_label: bypasses the target tests:
switch state {
case read: State.Read {
readNext()
goto process:
}
case process: State.Process {
processCurrent()
if needsMoreWork()
goto read:
}
}
Forward and backward targets are legal. This can express a compact state machine and can intentionally run forever.
Before entry, goto destroys every exited clause or nested scope in reverse
construction order. The old body scope ends completely. The target then receives
a fresh body scope entered from its beginning; no body-local value survives the
transfer. Active target-header lifetimes remain alive. When the new body later
completes normally, its own case post and the switch post run.
goto does not carry facts established by bypassed tests:
switch optionalValue {
case present: ? {
use(optionalValue.) // error if the goto path below lacks presence proof
}
default {
goto present:
}
}
The body must be valid under the facts common to every incoming path.
Case goto is one application of the shared active-body transfer defined by
core flow control. It is not
an arbitrary jump into the middle of source.
There is no special goto default form. Nonadjacent direct entry to a default
requires an explicit declared label such as goto fallback:.
Every combined goto ... with erase form is invalid. Erasure ends the current
each entry while direct body re-entry requires that entry to remain live.
Perform an explicit targeted erase first, then transfer beyond that traversal.
Transfer-only cases
A transfer-only case has a required label, no tests, an optional post, and one required body:
case [shadowable] label: [;; post] effective-body
It may appear before, between, or after tested cases and defaults. Ordinary searches skip it without running effects or its post.
switch state {
case failure: {
handleFailure()
}
case State.Ready {
if failed()
goto failure:
run()
}
}
failure: is reachable through the backward goto. Without a reachable incoming
edge, its body is unreachable and needs
intent<unreachable-selection-clause>{...}.
A transfer-only label is eligible for goto, and for next while its body is
active. It is not eligible for continue because it has no test entry.
next, break, and outward transfer
next can target the active case or the switch:
switch choose: ;; value ;; recordSwitch() {
case chosen: 1 ;; recordCase() {
if finishCase()
next chosen: // run recordCase(), then recordSwitch(), then exit
if skipCasePost()
next choose: // run recordSwitch(), then exit
}
}
It cannot target a sibling case. A bare next selects the nearest eligible
unlabeled target under the shared target rules.
break exits the complete switch, skips both posts, and still destroys every
scope it exits. return and transfers to outer constructs likewise skip the
posts belonging to the switch they leave.
Redundant tail break
Normal completion already exits the switch, so a trailing switch-targeting
break usually teaches the wrong fallthrough model:
switch value {
case 1
doSomething();
break // error: normal case completion already exits the switch
case 2 {
if condition()
break // valid: skips doSomethingElse()
doSomethingElse()
}
case 3 {
doSomethingAgain()
if condition()
break // error: both paths now exit the switch identically
}
}
A switch-targeting break is a non-acknowledgeable intent error when replacing
it with normal clause completion would execute no additional body work or post
before leaving the same switch. This is semantic tail position, not merely the
last textual token.
Scope destruction does not distinguish the two paths: both destroy the same exited lifetimes in the same order.
A post or outer target makes the transfer meaningful:
switch ;; value ;; recordSwitch() {
case 1 ;; recordCase() {
doSomething()
break // valid: skips both posts
}
}
while outer: moreWork() {
switch value {
case 1
break outer: // valid: exits the loop
}
}
Effect analysis does not erase the source-semantic distinction created by a written post.
Labels and target visibility
Tested-case and default labels are known throughout their containing switch for
continue and goto. They therefore permit forward and backward transfer.
A transfer-only case label has no test entry. It is eligible for goto, and for
next while its body is active, but not for continue.
The currently executing case is also a next target because it has a
normal-completion post phase. A sibling case is not a next target, and no case
is a break target.
shadowable follows the established label placement:
switch shadowable choose: value {
case shadowable retry: 1 {
// A nested eligible target may reuse retry: with permission.
}
}
Target lookup considers the written transfer keyword and the source position. When a nearer same-named label is ineligible but an outer label is eligible, selecting the outer target is defined but suspicious:
while shadowable retry: condition {
switch value {
case retry: 1 {
next retry: // current case is eligible
}
case 2 {
intent<outer-target-through-ineligible-label>{
next retry: // deliberately target the outer loop
}
}
}
}
Without the acknowledgement, the second next retry: is an intent error. For
continue and goto, sibling retry: is eligible and hides the outer target
normally.
Complete shared label spelling, shadowing, eligibility, and active-target rules belong to core flow control.
default, incomplete selection, and exhaustiveness
default is optional and positional. It is the unconditional fallback for the
ordered search that reaches it. A switch may contain several defaults when
explicit continuation creates several test segments:
switch normalized := value ;; normalized {
case < 0
handleNegative()
default normalize: {
normalized = normalizeValue(normalized)
continue
}
case < 10
handleSmallNormalized()
case < 100
handleMediumNormalized()
default out_of_range:
handleLargeNormalized()
}
Initial search reaches normalize: when < 0 fails. Its continue destroys
that body scope and resumes after the default. out_of_range: is then the
fallback for the later segment.
A search can begin from initial switch entry, continue switch_label:,
continue case_label:, or bare continue. During that search:
- transfer-only cases are skipped;
- tested cases run in source order;
- the first successful test selects its body;
- the first reached
defaultselects unconditionally; and - reaching the end selects no clause and completes normally.
A default reachable only through goto and through no ordered search is a
non-acknowledgeable intent error. Write it as a transfer-only case instead.
Body-local calculations do not survive continue. State needed by a later
segment must live in the switch header or surrounding scope, or the later
selection should be nested inside the default body.
Every no-match path participates in construction and result-completion analysis. Required state must be complete after each normally completing case or default and after each search that reaches the end without selecting a clause.
Enum coverage has two dimensions
Enum selection audits two different questions:
- Declared-member coverage: did the source explicitly handle every distinct declared member value for the applicable search entry?
- Reachable-domain coverage: can every other reachable backing value select a body?
default satisfies the second question for the segment that reaches it. It does
not count as explicit handling of omitted declared members.
Member names also do not necessarily cover every enum value:
- a strict owner operation may produce an unnamed value;
- relaxed enums admit every backing value;
- flags admit unnamed combinations;
unsafe fromcan establish any backing representation;- duplicate-valued names overlap; and
- an empty strict enum may hold its unnamed backing default.
switch permissions {
case Permission.Read
allowRead()
case Permission.Write
allowWrite()
default
handleOtherPermissionValue()
}
This source handles both declared members explicitly and uses default for
unnamed or unsafe-admitted values. Adding a new declared member makes it
member-incomplete even though default remains.
A deliberately partial enum switch requires:
intent<partial-enum-selection>{
switch status {
case Status.Ready
startWork()
default
handleOtherDeclaredOrUnnamedStatus()
}
}
Duplicate-valued aliases count as one distinct enum value. Flags and relaxed enums still need a fallback or another domain proof for unnamed values. Coverage is assessed for every reachable search entry; a later segment does not retroactively complete the initial one. Arbitrary user-defined predicates count only when the language can prove their covered domain.
Only reachable tests count as explicit member coverage:
| Entry path | Body reachable | Test reachable | Counts as explicit member coverage |
|---|---|---|---|
| Ordinary successful test | Yes | Yes | Yes |
continue member_case: followed by a successful test |
Yes | Yes | Yes, for that search entry |
goto member_case: |
Yes | No | No |
| No reachable incoming edge | No | No | No |
A case physically following a positional default does not count merely because
its member name appears in source. goto may make its body reachable while still
bypassing its test. Only a reachable ordered search through that test can
contribute coverage.
Complete enum admission and operation behavior belongs to enums.
Optional presence and nested layers
The recognized optional-presence operation works through the pre-unary case interpretation:
switch optionalValue {
case ?
use(optionalValue.)
default
handleAbsent()
}
The successful ? test proves the exact boxed lifetime present in its ordinary
body entry.
Failure of a recognized absence test can establish presence for later ordinary selection:
switch optionalValue {
case !
handleAbsent()
default
use(optionalValue.) // recognized absence test failed
}
Proof at a shared body is the intersection of all alternatives that can select it:
case ?, emergencyMode {
use(optionalValue.) // error if emergencyMode can succeed while absent
}
continue retests and can establish the target test’s facts. goto enters the
body directly and cannot supply facts from bypassed tests. Analysis considers
every incoming path.
Nested optionals cross one wrapper at a time:
switch nested {
case ? {
switch nested. {
case ?
use(nested..)
default
handleInnerAbsent()
}
}
default
handleOuterAbsent()
}
Cases do not bind optional payloads. Postfix . remains proven access, not a case
test:
// case . > 15 // error: postfix access is not a Boolean case test
Use ordinary access and nested selection:
switch optionalValue {
case !
handleAbsent()
default {
value : MyValue & = optionalValue.
switch value {
case > 15
handleLarge(value)
default
handleOther(value)
}
}
}
Complete wrapper state, per-layer proof, and boxed lifetime behavior belongs to optional values.
Variant alternative selection
When the retained selector has variant type, case enters a
dedicated alternative-routing mode. Case entries name alternatives rather than
ordinary expressions or Boolean operations:
switch message {
case text {
print(text)
}
case number, alternateNumber bind selectedNumber {
print(selectedNumber)
}
case ! {
handleAbsent()
}
}
Names route and bind payloads
case text resolves text only in the selector variant’s alternative
namespace. A successful route introduces clause-local reference-shaped payload
access under that same name.
Several alternatives may share one body. bind gives them one common local:
case number, alternateNumber bind selectedNumber {
print(selectedNumber)
}
The body is checked independently for every alternative that can route to it. It is valid only when every specialization succeeds. A diagnostic identifies the failing alternative, its payload type, and the unavailable operation.
A several-name entry without bind only routes control and introduces no
payload local. This lets several states share behavior that does not inspect the
payload.
The binding performs no copy. It preserves payload origin, qualifications, and the transfer stance projected from the retained selector. An accepted later consumer performs any move or terminal transfer.
Ordinary local shadowing rules apply. Rename a same-name alternative when needed:
case text bind selectedText {
print(selectedText)
}
Presence and absence entries
Variant mode reserves:
case ? bind selected
handlePresent(selected)
case !
handleAbsent()
case ? selects any present alternative that reaches it. With bind, its body
is specialized for every such payload type. Bare case ? introduces no payload
binding. case ! selects absence and introduces none.
Ordering remains visible. Specific names can appear before the present catch-all:
switch message {
case text
handleText(text)
case ? bind other
handleOtherPresent(other)
case !
handleAbsent()
}
default retains its ordinary positional fallback meaning. It can receive
absence and any present alternative not selected earlier. It does not
intrinsically mean absence.
Coverage has two dimensions
Variant selection audits:
- Alternative coverage: every declared name is handled by a named case or
case ?. - State coverage: absence reaches
case !ordefault.
default satisfies fallback state coverage but does not silently count as
explicit handling of omitted alternative names. Deliberately partial source
uses:
intent<partial-variant-selection>{
switch message {
case text
handleText(text)
default
handleOtherState()
}
}
The acknowledgement permits omitted names without changing order, fallback, or
payload binding. Adding an alternative makes a named-complete switch incomplete.
case ? deliberately accepts later alternatives when its new body
specialization succeeds.
Coverage is assessed for every reachable ordered-search entry, just as enum coverage is. A direct body entry bypasses the route and contributes no alternative coverage.
Transfers and binding establishment
A payload-bound clause body cannot be a goto target: direct entry would
bypass routing and could not construct its local binding.
continue case_label: may target a routed variant clause because it starts at
that clause’s route entry. It enters the body only after matching the current
selection and establishing the binding; if it does not match, search continues.
Bare case ?, case !, default, and transfer-only clauses create no payload
binding. An otherwise eligible goto may enter such a body, but that path
inherits no presence or absence fact from the bypassed route. The body remains
valid only under facts common to every incoming path.
next, break, clause and switch posts, scope destruction, and outward
transfer retain their ordinary behavior.
This finite variant mode is not generalized pattern matching. It provides no recursive destructuring, structural pattern, guard, or optional-payload flattening.
Empty selection
A switch containing no clauses still evaluates its initializer and selector and runs its post on normal completion. It nevertheless strongly resembles unfinished source and requires explicit acknowledgement:
intent<empty-selection>{
switch initialize() ;; selectValue() ;; recordCompletion() { }
}
An ordinary block is clearer when only sequential work is intended.
Costs
The visible baseline cost is:
- one initializer execution when written;
- one selector evaluation and no hidden copy;
- one evaluation and selected operation for every reached case alternative;
- one selected body, or none on incomplete selection;
- applicable case and switch posts;
- destruction for every exited scope; and
- repeated tests or bodies when
continueorgotocycles.
There is no constant-time dispatch promise. User-defined operations, runtime case expressions, mixed operators, mutation, and observable effects may require ordered testing.
Variant mode tests the represented active name or absence rather than invoking
ordinary case operators. A polymorphic bind body adds compile-time checking
and potentially generated code for each routed payload type; ordinary
optimization may merge equivalent specializations without changing behavior.
Diagnostics
Diagnostics should distinguish:
- no viable direct or equality interpretation;
- direct or equality ambiguity;
- a uniquely best unavailable operation;
- a selected result that is not exactly
Boolean; - malformed case alternatives or comma continuation;
- a missing or malformed effective body;
- an unlabeled testless case;
;without a right effective statement or compact{}where{ }is required;- a default reachable only through
goto, which should be a transfer-only case; - proven unreachable selection requiring
intent<unreachable-selection-clause>{...}; - omitted declared enum members requiring
intent<partial-enum-selection>{...}; - an unknown variant alternative name or a lexical expression used where variant mode requires an alternative;
- a polymorphic variant body that fails for one routed alternative;
- omitted variant alternatives requiring
intent<partial-variant-selection>{...}; - a variant
gototarget whose payload binding cannot be established; - empty selection requiring
intent<empty-selection>{...}; - outer-target reach-through requiring
intent<outer-target-through-ineligible-label>{...}; - bare
continuewith no following clause; continuenaming a transfer-only case with no test entry;- bare
gotowith no following clause or stopped by a labeled eligible target; nextnaming a sibling case;gotonaming an ineligible or inactive target;- any combined
goto ... with eraseform; - missing proof on one matched, continued, or direct-entry path;
- use after the selector or a clause-local lifetime ends;
- incomplete state on a no-match path;
- redundant switch-targeting tail
break; and - legacy
case continueor another unsupported combined spelling.
unreachable-selection-clause, partial-enum-selection,
partial-variant-selection, empty-selection, and
outer-target-through-ineligible-label are acknowledgement-required intent
errors with the enclosure shapes shown above.
Redundant tail break is non-acknowledgeable. It must be removed unless it
actually skips a case/switch post or targets an outer construct.
A default reachable through no ordered search is likewise non-acknowledgeable: rewrite it as a transfer-only case rather than preserving misleading fallback spelling.
Formatting
switch, its optionalshadowablelabel, and the first header section begin on one physical line unless explicit continuation moves the complete header.- The outer switch clause region is braced.
caseanddefaultalign one level inside the switch.- A clause label immediately follows
caseordefault. - A transfer-only case has a required label followed directly by its optional post or body.
- A comma has no preceding whitespace and requires following whitespace.
- A comma continues the test list across a following indented line.
- In variant mode,
bindfollows the complete alternative list and precedes its body or post. ;;has whitespace on both sides.- A simple or composed body begins on the following line one level deeper.
- A braced body opens on the final physical clause-header line.
- A multiline body-closing
}aligns with its clause keyword. - An explicit transfer target retains its trailing
:.
Complete token, continuation, indentation, separator, and brace rules belong to source structure.
Source stability
- Case order and comma-alternative order are semantic.
- Adding a direct exact-
Booleanoperation may replace implicit equality for otherwise unchanged ungrouped case source. - Grouping or explicit
==preserves equality intent. - Adding a case can change which body first matches.
- Adding a positional default changes where an ordered search stops unless its body explicitly continues.
- Adding a post changes normal completion and whether a terminal
breakis redundant. - Adding or moving a label can change target lookup or require intent acknowledgement, but cannot silently redirect a transfer.
- Adding an enum member makes a previously member-complete switch incomplete
unless it already acknowledges partial enum selection;
defaultcontinues to cover the applicable unnamed domain rather than the omitted declaration. - Adding a variant alternative makes a named-complete switch incomplete.
case ?deliberately accepts the new alternative only when its additional body specialization succeeds. - Improved analysis may identify a language-defined unreachable clause and require its existing acknowledgement; it cannot reorder observable tests.
- Formatting and source-preserving tools retain phrase fences, grouping, alternative order, labels, posts, and intent acknowledgements.
Boundaries and maturity
This document defines current conceptual runtime value selection. It is not a formal grammar, implementation mapping, compatibility contract, or conformance specification.
Still future:
- generalized or recursive patterns;
- payload destructuring beyond the accepted variant binding;
- pattern guards;
- unmanaged-union selection, which has no active-lens state;
- value-producing selection and result convergence;
- mixfix or other multi-hole case tests;
- compile-time dispatch strategies beyond
switch compile; - generic specialization and reflection;
- formal reachability, path-merge, and termination algorithms; and
- compiler lowering or dispatch-table strategy.
Future pattern syntax must remain distinct from current Boolean case tests and must account for direct body entry that bypasses test-established proof.