5.4.2.10 Select Case Statement
Note
This section describes the implementation of MS-VBAL §5.4.2.10 Select Case Statement.
Syntax
| AST node | Instruction kind(s) | Notes |
|---|---|---|
| SelectCaseStatementNode | Select |
The Select Case opener. Its End is right past the block. |
| CaseExpressionStatementNode | ConditionalBranch |
A Case header. Its Matching names the offset of its Select opener. Its Else is the offset to go to on no match: the next header in the chain, the first instruction of the Case Else body, or right past the whole construct. |
| CaseElseClauseStatementNode | — | Case Else needs no condition. |
| — (synthesized) | Jump |
The trailing jump at the end of each branch's body. Node is null; Target is right past the whole construct. |
Each Case line's comma-separated conditions form their own node hierarchy under the abstract
CaseRangeClauseNode. The three condition forms
of MS-VBAL §5.4.2.10 are modelled independently, as three node types:
| Condition form | Example | AST node |
|---|---|---|
| Single value | Case 5 |
CaseValueRangeClauseNode |
| Comparison | Case Is > 5 |
CaseComparisonRangeClauseNode |
| Inclusive range | Case 1 To 10 |
CaseToRangeClauseNode |
See RD-VBAL §3.4.1 Block Statements and RD-VBAL §3.5.2 Instruction.
Runtime Semantics
Select
- Evaluate the header expression (the selector) once.
- Store the selector on the activation, keyed by the
Selectinstruction's own offset. - Fall through into the body, to the first
Caseheader.
MS-VBAL §5.4.2.10 says "the select-expression is immediately evaluated": the selector is evaluated once, ahead of every case-clause. RD-VBA passes the evaluated selector to every clause comparison, in accordance with this.
Case Header
The executor dispatches ConditionalBranch separately for a Case header: a Case header has
Instruction.Matching set instead of a Boolean
condition.
- Read the enclosing
Select's stored selector, through the header'sMatchingoffset. - If the selector is a
Variantthat holdsNull, go directly to the header'sElseoffset without evaluating any range clause. EveryCaseheader does the same, so control reachesCase Else. - Otherwise, match the header's range clauses against the selector, as a comparison or logical expression, with
the value of each clause converted to the declared type of the selector (see
The Value of a Clause Is Converted to the Selector's Type).
A
Variantselector is compared as theVariantit is. - On a match, fall through into the branch's body. Otherwise, go to the header's
Elseoffset: the next header in the chain, the first instruction of theCase Elsebody, or right past the whole construct. - After the branch's body runs, the synthesized trailing
Jumpgoes right past the whole construct.
MS-VBAL §5.4.2.10: "If select-expression is the data value Null, only the case-else-clause is executed". A
Null can reach Select Case only through a Variant selector: a local declared Long can never hold Null.
Range Clause Matching
A Case header's range clauses are matched as a comparison or logical expression, the way
MS-VBAL §5.4.2.10 phrases its own runtime semantics:
| Range clause | Example | Matched as |
|---|---|---|
| Value | Case 5 |
selector = 5 |
| Comparison | Case Is > 5 |
selector > 5. The clause's own, already-normalized comparison-operator token selects the relational operator strategy directly. |
To |
Case 1 To 10 |
(selector >= 1) And (selector <= 10) |
Each comparison is evaluated through the same operator strategies every other expression uses
(BinaryRelationalOperatorRuntimeSemantics, BinaryAndLogicalOperatorRuntimeSemantics;
RD-VBAL §5.6.9.5 Relational Operators,
RD-VBAL §5.6.9.8 Logical Operators).
The already-evaluated selector is passed to each clause comparison as an operand value. It is not re-evaluated per clause.
The Value of a Clause Is Converted to the Selector's Type
Before it is compared, the value of each range clause (each end of a To clause) is let-coerced to the declared type of
the selector, and the two are compared in that type. This is what MS-VBA does (Excel, oracle\s46 F15a-g and
oracle\s48). Neither MS-VBAL §5.4.2.10 nor Microsoft Learn's Select Case page says so.
| Selector | Clause | MS-VBA, and RD-VBA | The expression of the two |
|---|---|---|---|
n As Long = 2 |
Case 1.7 |
matches: 1.7 is the Long 2 |
n = 1.7 is False |
CInt(5) |
Case Is < 5.4 |
no match: 5.4 is the Integer 5 |
5 < 5.4 is True |
b As Boolean = False |
Case Is < 1 |
no match: 1 is True, and False < True is False |
b < 1 is True |
s As String = "1.0" |
Case 1 |
no match: 1 is "1" |
s = 1 is True |
s As String = "10" |
Case Is < 5 |
matches: "10" < "5" as text |
s < 5 is False |
n As Long = 2 |
Case 1.5 To 2.4 |
matches: both ends are the Long 2 |
False |
an Enum value of 5 |
Case 4.6 |
matches: an Enum is a Long |
False |
- The declared type is the selector expression's:
Select Case n * 1withn As Longconverts toLong, andSelect Case n + 0.5toDouble. - A fixed-length
Stringselector converts toString:Case "ab"does not match aString * 5holding"ab", which is"ab ". - A
SingleorDoubleselector converts a value too: withs As Single=0.1,Case 0.1matches, and withd As Double=0.1,Case CSng(0.1)does not (oracle\s48M01-M07). An integer is the exception: aSingleselector compares it at its exact value, as MS-VBA loads it on the x87 without rounding it to aSingle(see RD-VBAL §5.6.9.3). Withs=16777216,Case 16777217does not match, andCase Is < 16777217does (oracle\s48M02,oracle\s42D22). - A value the conversion cannot make raises the conversion's error, at the
Case:Case "abc"raises 13 for aLongselector,Case 40000raises 6 for anInteger,Case Nullraises 94. UnderOn Error Resume Next, execution goes on in thatCase's body, as it does in MS-VBA. - A selector declared
Variantis compared with the value as it is, as in an expression: withvholding theLong2,Case 1.7does not match, and withhholding100,Case "100"matches andCase Is > "9"does not (aVariantnumber compared with aStringis compared as text; RD-VBAL §5.6.9.5).
MS-VBAL §5.4.2.10 says only that a single value "is compared with the value of select-expression", so Case 1.7
has nothing to depart from. For the two other forms it states the expression that is evaluated:
select-expression comparison-operator expression for Case Is, and an And of two such comparisons for To. Where
the conversion makes a Case Is clause a different comparison from that expression, RDCore.Diagnostics reports
RDC11014, a warning that stands in for the comparison the specification states.
The runtime states it as a CaseClauseStatementFact with CaseClauseSemanticFlags.ComparedInSelectorType: the
conversion is stated when the relational operator's effective type for the two
(RD-VBAL §5.6.9.5) is not the selector's type. With n As Long,
Case Is < 5 is not stated: an Integer compared with a Long is compared as a Long either way.
Implementation
ProcedureExecutordispatchesSelect, and theConditionalBranchof aCaseheader.- The selector is stored through
CallStackFrame.SetBlockStateand read back through ICallStackFrame.TryGetBlockState, keyed by the opener's offset. A single block-state value is enough for the selector. There is no separate closer instruction to pop the stored state on exit (RD-VBAL §3.5.4 Execution). - A
Caseheader'sMatchingis the same field aForNextinstruction uses for its back-reference to its opener (RD-VBAL §5.4.2.3 For Statement). Select Caseneeds no synthesized closer: leaving the last branch, orCase Else, already reaches the offset that the construct's ownEnd/Elsechaining designates (RD-VBAL §3.5.3 Lowering Block Statements).ProcedureExecutor.ExecuteCaseHeader'sNull-selector short-circuit unwraps aVariantselector before testing it forNull. The clauses are matched against the selector as it was evaluated, theVariantincluded.CaseMatchEvaluatorconverts the value of a clause to the selector's declared type through theILetCoercionRuntimeSemanticsProvider, withConversionSite.CaseTest. A selector evaluated on payloads (IR lowering S1.6) is converted to the payload's type when the conversion is exact; anything else takes the path above. A selector declaredVariantis never evaluated on payloads, since the payload would be of theVariant's subtype.RDCore.Runtime.Execution.CaseMatchEvaluatormatches the range clauses against the selector. It calls the specific operator strategy directly rather than going throughOperatorRuntimeSemanticsProvider's token dispatch, because the range clause already determines the operator strategy.CaseMatchEvaluatorpasses the clause's own operand expression as the location-bearing node of each operator call, never a fabricated node standing in for the already-evaluated selector.- This relies on the node parameter of
OperatorRuntimeSemantics<TContext,TFlags>being typed ExpressionNode (RD-VBAL §5.6.9.2 Simple Data Operators).
⏮️ RD-VBAL §5.4.2.9 Single-line If Statement | ⏭️ RD-VBAL §5.4.2.11 Stop Statement