|
' Licensed to the .NET Foundation under one or more agreements.
' The .NET Foundation licenses this file to you under the MIT license.
' See the LICENSE file in the project root for more information.
Imports System.Collections.Immutable
Imports System.Runtime.InteropServices
Imports Microsoft.CodeAnalysis.Collections
Imports Microsoft.CodeAnalysis.PooledObjects
Imports Microsoft.CodeAnalysis.Text
Imports Microsoft.CodeAnalysis.VisualBasic.Symbols
Imports Microsoft.CodeAnalysis.VisualBasic.Syntax
Imports TypeKind = Microsoft.CodeAnalysis.TypeKind
Namespace Microsoft.CodeAnalysis.VisualBasic
Partial Friend Class Binder
#Region "Bind select case statement"
Private Function BindSelectBlock(node As SelectBlockSyntax, diagnostics As BindingDiagnosticBag) As BoundStatement
Debug.Assert(node IsNot Nothing)
' Bind select expression
Dim selectExprStatementSyntax = node.SelectStatement
Dim expression = BindSelectExpression(selectExprStatementSyntax.Expression, diagnostics)
If expression.HasErrors Then
diagnostics = BindingDiagnosticBag.Discarded
End If
Dim exprPlaceHolder = New BoundRValuePlaceholder(selectExprStatementSyntax.Expression, expression.Type)
exprPlaceHolder.SetWasCompilerGenerated()
Dim expressionStmt = New BoundExpressionStatement(selectExprStatementSyntax, expression)
' Get the binder for the select block. This defines the exit label.
Dim selectBinder = GetBinder(DirectCast(node, VisualBasicSyntaxNode))
' Flag to determine if we need to generate switch table based code or If list based code.
' See OptimizeSelectStatement method for more details.
Dim recommendSwitchTable = False
' Bind case blocks.
Dim caseBlocks As ImmutableArray(Of BoundCaseBlock) = selectBinder.BindCaseBlocks(
node.CaseBlocks,
exprPlaceHolder,
convertCaseElements:=expression.Type.IsIntrinsicOrEnumType(),
recommendSwitchTable:=recommendSwitchTable,
diagnostics:=diagnostics)
' Create the bound node.
Return New BoundSelectStatement(node, expressionStmt, exprPlaceHolder, caseBlocks, recommendSwitchTable,
exitLabel:=selectBinder.GetExitLabel(SyntaxKind.ExitSelectStatement))
End Function
Private Function BindSelectExpression(node As ExpressionSyntax, diagnostics As BindingDiagnosticBag) As BoundExpression
' SPEC: A Select Case statement executes statements based on the value of an expression.
' SPEC: The expression must be classified as a value.
' We want to generate select case specific diagnostics if select expression is
' AddressOf expression or Lambda expression.
' For remaining expression kinds, bind the select expression as value.
' We also need to handle SyntaxKind.ParenthesizedExpression here.
' We might have a AddressOf expression or Lambda expression within a parenthesized expression.
' We want to generate ERRID.ERR_AddressOfInSelectCaseExpr/ERRID.ERR_LambdaInSelectCaseExpr for this case.
' See test BindingErrorTests.BC36635ERR_LambdaInSelectCaseExpr.
Dim errorId As ERRID = Nothing
Select Case node.Kind
Case SyntaxKind.ParenthesizedExpression
Dim parenthesizedExpr = DirectCast(node, ParenthesizedExpressionSyntax)
Dim boundExpression = BindSelectExpression(parenthesizedExpr.Expression, diagnostics)
Return New BoundParenthesized(node, boundExpression, boundExpression.Type)
Case SyntaxKind.AddressOfExpression
errorId = ERRID.ERR_AddressOfInSelectCaseExpr
Case SyntaxKind.MultiLineFunctionLambdaExpression, SyntaxKind.MultiLineSubLambdaExpression,
SyntaxKind.SingleLineFunctionLambdaExpression, SyntaxKind.SingleLineSubLambdaExpression
errorId = ERRID.ERR_LambdaInSelectCaseExpr
End Select
Dim boundExpr = BindExpression(node, diagnostics)
If boundExpr.HasErrors() Then
boundExpr = MakeRValue(boundExpr, diagnostics)
ElseIf errorId <> Nothing Then
ReportDiagnostic(diagnostics, node, errorId)
boundExpr = MakeRValueAndIgnoreDiagnostics(boundExpr)
Else
boundExpr = MakeRValue(boundExpr, diagnostics)
End If
Return boundExpr
End Function
Private Function BindCaseBlocks(
caseBlocks As SyntaxList(Of CaseBlockSyntax),
selectExpression As BoundRValuePlaceholder,
convertCaseElements As Boolean,
ByRef recommendSwitchTable As Boolean,
diagnostics As BindingDiagnosticBag
) As ImmutableArray(Of BoundCaseBlock)
If Not caseBlocks.IsEmpty() Then
Dim caseBlocksBuilder = ArrayBuilder(Of BoundCaseBlock).GetInstance()
' Bind case blocks.
For Each caseBlock In caseBlocks
caseBlocksBuilder.Add(BindCaseBlock(caseBlock, selectExpression, convertCaseElements, diagnostics))
Next
Return OptimizeSelectStatement(selectExpression, caseBlocksBuilder, recommendSwitchTable, diagnostics)
End If
Return ImmutableArray(Of BoundCaseBlock).Empty
End Function
Private Function BindCaseBlock(
node As CaseBlockSyntax,
selectExpression As BoundRValuePlaceholder,
convertCaseElements As Boolean,
diagnostics As BindingDiagnosticBag
) As BoundCaseBlock
Dim caseStatement As BoundCaseStatement = BindCaseStatement(node.CaseStatement, selectExpression, convertCaseElements, diagnostics)
Dim statementsSyntax As SyntaxList(Of StatementSyntax) = node.Statements
Dim bodyBinder = GetBinder(statementsSyntax)
Dim body As BoundBlock = bodyBinder.BindBlock(node, statementsSyntax, diagnostics).MakeCompilerGenerated()
Return New BoundCaseBlock(node, caseStatement, body)
End Function
Private Function BindCaseStatement(
node As CaseStatementSyntax,
selectExpressionOpt As BoundRValuePlaceholder,
convertCaseElements As Boolean,
diagnostics As BindingDiagnosticBag
) As BoundCaseStatement
Dim caseClauses As ImmutableArray(Of BoundCaseClause)
If node.Kind = SyntaxKind.CaseStatement Then
Dim caseClauseBuilder = ArrayBuilder(Of BoundCaseClause).GetInstance()
' Bind case clauses.
For Each caseClause In node.Cases
caseClauseBuilder.Add(BindCaseClause(caseClause, selectExpressionOpt, convertCaseElements, diagnostics))
Next
caseClauses = caseClauseBuilder.ToImmutableAndFree()
Else
Debug.Assert(node.Kind = SyntaxKind.CaseElseStatement)
caseClauses = ImmutableArray(Of BoundCaseClause).Empty
End If
Return New BoundCaseStatement(node, caseClauses, conditionOpt:=Nothing)
End Function
Private Function BindCaseClause(
node As CaseClauseSyntax,
selectExpressionOpt As BoundRValuePlaceholder,
convertCaseElements As Boolean,
diagnostics As BindingDiagnosticBag
) As BoundCaseClause
Select Case node.Kind
Case SyntaxKind.CaseEqualsClause, SyntaxKind.CaseNotEqualsClause,
SyntaxKind.CaseGreaterThanClause, SyntaxKind.CaseGreaterThanOrEqualClause,
SyntaxKind.CaseLessThanClause, SyntaxKind.CaseLessThanOrEqualClause
Return BindRelationalCaseClause(DirectCast(node, RelationalCaseClauseSyntax), selectExpressionOpt, convertCaseElements, diagnostics)
Case SyntaxKind.SimpleCaseClause
Return BindSimpleCaseClause(DirectCast(node, SimpleCaseClauseSyntax), selectExpressionOpt, convertCaseElements, diagnostics)
Case SyntaxKind.RangeCaseClause
Return BindRangeCaseClause(DirectCast(node, RangeCaseClauseSyntax), selectExpressionOpt, convertCaseElements, diagnostics)
Case Else
Throw ExceptionUtilities.UnexpectedValue(node.Kind)
End Select
End Function
Private Function BindRelationalCaseClause(
node As RelationalCaseClauseSyntax,
selectExpressionOpt As BoundRValuePlaceholder,
convertCaseElements As Boolean,
diagnostics As BindingDiagnosticBag
) As BoundCaseClause
' SPEC: A Case clause may take two forms.
' SPEC: One form is an optional Is keyword, a comparison operator, and an expression.
' SPEC: The expression is converted to the type of the Select expression;
' SPEC: if the expression is not implicitly convertible to the type of
' SPEC: the Select expression, a compile-time error occurs.
' SPEC: If the Select expression is E, the comparison operator is Op,
' SPEC: and the Case expression is E1, the case is evaluated as E OP E1.
' SPEC: The operator must be valid for the types of the two expressions;
' SPEC: otherwise a compile-time error occurs.
' Bind relational case clause as binary operator: E OP E1.
' BindBinaryOperator will generate the appropriate diagnostics.
Debug.Assert(SyntaxFacts.IsRelationalOperator(node.OperatorToken.Kind) OrElse node.ContainsDiagnostics)
Dim operatorKind As BinaryOperatorKind
Select Case node.Kind
Case SyntaxKind.CaseEqualsClause : operatorKind = BinaryOperatorKind.Equals
Case SyntaxKind.CaseNotEqualsClause : operatorKind = BinaryOperatorKind.NotEquals
Case SyntaxKind.CaseLessThanOrEqualClause : operatorKind = BinaryOperatorKind.LessThanOrEqual
Case SyntaxKind.CaseGreaterThanOrEqualClause : operatorKind = BinaryOperatorKind.GreaterThanOrEqual
Case SyntaxKind.CaseLessThanClause : operatorKind = BinaryOperatorKind.LessThan
Case SyntaxKind.CaseGreaterThanClause : operatorKind = BinaryOperatorKind.GreaterThan
Case Else
Throw ExceptionUtilities.UnexpectedValue(node.Kind)
End Select
Dim conditionOpt As BoundExpression = Nothing
Dim operandE1 As BoundExpression = BindCaseClauseExpression(
expressionSyntax:=node.Value,
caseClauseSyntax:=node,
selectExpressionOpt:=selectExpressionOpt,
operatorTokenKind:=node.OperatorToken.Kind,
operatorKind:=operatorKind,
convertCaseElements:=convertCaseElements,
conditionOpt:=conditionOpt,
diagnostics:=diagnostics)
Return New BoundRelationalCaseClause(node, operatorKind, operandE1, conditionOpt)
End Function
Private Function BindSimpleCaseClause(
node As SimpleCaseClauseSyntax,
selectExpressionOpt As BoundRValuePlaceholder,
convertCaseElements As Boolean,
diagnostics As BindingDiagnosticBag
) As BoundCaseClause
' SPEC: The other form is an expression optionally followed by the keyword To and
' SPEC: a second expression. Both expressions are converted to the type of the
' SPEC: Select expression; if either expression is not implicitly convertible to
' SPEC: the type of the Select expression, a compile-time error occurs.
' SPEC: If the Select expression is E, the first Case expression is E1,
' SPEC: and the second Case expression is E2, the Case is evaluated either
' SPEC: as E = E1 (if no E2 is specified) or (E >= E1) And (E <= E2).
' SPEC: The operators must be valid for the types of the two expressions;
' SPEC: otherwise a compile-time error occurs.
Dim conditionOpt As BoundExpression = Nothing
' Bind the Case clause as E = E1 (no E2 is specified)
Dim value As BoundExpression = BindCaseClauseExpression(
expressionSyntax:=node.Value,
caseClauseSyntax:=node,
selectExpressionOpt:=selectExpressionOpt,
operatorTokenKind:=SyntaxKind.EqualsToken,
operatorKind:=BinaryOperatorKind.Equals,
convertCaseElements:=convertCaseElements,
conditionOpt:=conditionOpt,
diagnostics:=diagnostics)
Return New BoundSimpleCaseClause(node, value, conditionOpt)
End Function
Private Function BindRangeCaseClause(
node As RangeCaseClauseSyntax,
selectExpressionOpt As BoundRValuePlaceholder,
convertCaseElements As Boolean,
diagnostics As BindingDiagnosticBag
) As BoundCaseClause
' SPEC: The other form is an expression optionally followed by the keyword To and
' SPEC: a second expression. Both expressions are converted to the type of the
' SPEC: Select expression; if either expression is not implicitly convertible to
' SPEC: the type of the Select expression, a compile-time error occurs.
' SPEC: If the Select expression is E, the first Case expression is E1,
' SPEC: and the second Case expression is E2, the Case is evaluated either
' SPEC: as E = E1 (if no E2 is specified) or (E >= E1) And (E <= E2).
' SPEC: The operators must be valid for the types of the two expressions;
' SPEC: otherwise a compile-time error occurs.
Dim lowerBoundConditionOpt As BoundExpression = Nothing
' Bind case clause lower bound value (E >= E1)
Dim lowerBound As BoundExpression = BindCaseClauseExpression(
expressionSyntax:=node.LowerBound,
caseClauseSyntax:=node,
selectExpressionOpt:=selectExpressionOpt,
operatorTokenKind:=SyntaxKind.GreaterThanEqualsToken,
operatorKind:=BinaryOperatorKind.GreaterThanOrEqual,
convertCaseElements:=convertCaseElements,
conditionOpt:=lowerBoundConditionOpt,
diagnostics:=diagnostics)
' Bind case clause upper bound value (E <= E2)
Dim upperBoundConditionOpt As BoundExpression = Nothing
Dim upperBound As BoundExpression = BindCaseClauseExpression(
expressionSyntax:=node.UpperBound,
caseClauseSyntax:=node,
selectExpressionOpt:=selectExpressionOpt,
operatorTokenKind:=SyntaxKind.LessThanEqualsToken,
operatorKind:=BinaryOperatorKind.LessThanOrEqual,
convertCaseElements:=convertCaseElements,
conditionOpt:=upperBoundConditionOpt,
diagnostics:=diagnostics)
Return New BoundRangeCaseClause(node, lowerBound, upperBound, lowerBoundConditionOpt, upperBoundConditionOpt)
End Function
Private Function BindCaseClauseExpression(
expressionSyntax As ExpressionSyntax,
caseClauseSyntax As CaseClauseSyntax,
selectExpressionOpt As BoundRValuePlaceholder,
operatorTokenKind As SyntaxKind,
operatorKind As BinaryOperatorKind,
convertCaseElements As Boolean,
ByRef conditionOpt As BoundExpression,
diagnostics As BindingDiagnosticBag
) As BoundExpression
Dim caseExpr As BoundExpression = BindValue(expressionSyntax, diagnostics)
If selectExpressionOpt Is Nothing Then
' In error scenarios, such as a Case statement outside of a
' Select statement, the Select expression may be Nothing.
conditionOpt = Nothing
Return MakeRValue(caseExpr, diagnostics)
End If
If convertCaseElements AndAlso caseExpr.Type.IsIntrinsicOrEnumType() Then
' SPEC: The expression is converted to the type of the Select expression;
' SPEC: if the expression is not implicitly convertible to the type of the Select expression, a compile-time error occurs.
Debug.Assert(selectExpressionOpt.Type IsNot Nothing)
Return ApplyImplicitConversion(expressionSyntax, selectExpressionOpt.Type, caseExpr, diagnostics)
Else
' SPEC: If the Select expression is E, the comparison operator is Op,
' SPEC: and the Case expression is E1, the case is evaluated as E OP E1.
' Bind binary operator "selectExpression OP caseExpr" to generate necessary diagnostics.
conditionOpt = BindBinaryOperator(
node:=caseClauseSyntax,
left:=selectExpressionOpt,
right:=caseExpr,
operatorTokenKind:=operatorTokenKind,
preliminaryOperatorKind:=operatorKind,
isOperandOfConditionalBranch:=False,
diagnostics:=diagnostics,
isSelectCase:=True).MakeCompilerGenerated()
Return Nothing
End If
End Function
#Region "Helper methods for Binding Select case statement"
' This function is identical to the Semantics::OptimizeSelectStatement in native compiler.
' It performs two primary tasks:
' 1) Determines what kind of byte codes will be used for select: Switch Table or If List.
' Ideally we would like to delay these kind of optimizations till rewriting/emit phase.
' However, the computation of the case statement conditional would be redundant if
' we are eventually going to emit Switch table based byte code. Hence we match the
' native compiler behavior and check RecommendSwitchTable() here and store this
' value in the BoundSelectStatement node to be reused in the rewriter.
' 2) If we are going to generate If list based byte code, this function computes the
' conditional expression for case statements and stores it in the BoundCaseStatement nodes.
' Condition for each case statement "Case clause1, clause2, ..., clauseN"
' is computed as "clause1 OrElse clause2 OrElse ... OrElse clauseN" expression.
Private Function OptimizeSelectStatement(
selectExpression As BoundRValuePlaceholder,
caseBlockBuilder As ArrayBuilder(Of BoundCaseBlock),
ByRef generateSwitchTable As Boolean,
diagnostics As BindingDiagnosticBag
) As ImmutableArray(Of BoundCaseBlock)
Debug.Assert(Not selectExpression.HasErrors)
generateSwitchTable = RecommendSwitchTable(selectExpression, caseBlockBuilder, diagnostics)
' CONSIDER: Do we need to compute the case statement conditional expression
' CONSIDER: even for generateSwitchTable case? We might want to do so to
' CONSIDER: maintain consistency of bound nodes coming out of the binder.
' CONSIDER: With the current design, value of BoundCaseStatement.ConditionOpt field
' CONSIDER: is dependent on the value of generateSwitchTable.
If Not generateSwitchTable AndAlso caseBlockBuilder.Any() Then
Dim booleanType = GetSpecialType(SpecialType.System_Boolean, selectExpression.Syntax, diagnostics)
Dim caseClauseBuilder = ArrayBuilder(Of BoundCaseClause).GetInstance()
For index = 0 To caseBlockBuilder.Count - 1
Dim caseBlock = caseBlockBuilder(index)
If caseBlock.Syntax.Kind <> SyntaxKind.CaseElseBlock AndAlso
Not caseBlock.CaseStatement.Syntax.IsMissing Then
Dim caseStatement = caseBlock.CaseStatement
Dim caseStatementSyntax = caseStatement.Syntax
Dim caseStatementCondition As BoundExpression = Nothing
Debug.Assert(caseStatement.CaseClauses.Any())
Dim clausesChanged = False
' Compute conditional expression for case statement
For Each caseClause In caseStatement.CaseClauses
Dim clauseCondition As BoundExpression = Nothing
' Compute conditional expression for case clause, if not already computed.
Dim newCaseClause = ComputeCaseClauseCondition(caseClause, clauseCondition, selectExpression, diagnostics)
caseClauseBuilder.Add(newCaseClause)
clausesChanged = clausesChanged OrElse Not newCaseClause.Equals(caseClause)
Debug.Assert(clauseCondition IsNot Nothing)
If caseStatementCondition Is Nothing Then
caseStatementCondition = clauseCondition
Else
' caseStatementCondition = caseStatementCondition OrElse clauseCondition
caseStatementCondition = BindBinaryOperator(
node:=caseStatementSyntax,
left:=caseStatementCondition,
right:=clauseCondition,
operatorTokenKind:=SyntaxKind.OrElseKeyword,
preliminaryOperatorKind:=BinaryOperatorKind.OrElse,
isOperandOfConditionalBranch:=False,
diagnostics:=diagnostics,
isSelectCase:=True).MakeCompilerGenerated()
End If
Next
Dim newCaseClauses As ImmutableArray(Of BoundCaseClause)
If clausesChanged Then
newCaseClauses = caseClauseBuilder.ToImmutable()
Else
newCaseClauses = caseStatement.CaseClauses
End If
caseClauseBuilder.Clear()
caseStatementCondition = ApplyImplicitConversion(caseStatementCondition.Syntax, booleanType, caseStatementCondition, diagnostics:=diagnostics, isOperandOfConditionalBranch:=True)
caseStatement = caseStatement.Update(newCaseClauses, caseStatementCondition)
caseBlockBuilder(index) = caseBlock.Update(caseStatement, caseBlock.Body)
End If
Next
caseClauseBuilder.Free()
End If
Return caseBlockBuilder.ToImmutableAndFree()
End Function
Private Function ComputeCaseClauseCondition(caseClause As BoundCaseClause, <Out()> ByRef conditionOpt As BoundExpression, selectExpression As BoundRValuePlaceholder, diagnostics As BindingDiagnosticBag) As BoundCaseClause
Select Case caseClause.Kind
Case BoundKind.RelationalCaseClause
Return ComputeRelationalCaseClauseCondition(DirectCast(caseClause, BoundRelationalCaseClause), conditionOpt, selectExpression, diagnostics)
Case BoundKind.SimpleCaseClause
Return ComputeSimpleCaseClauseCondition(DirectCast(caseClause, BoundSimpleCaseClause), conditionOpt, selectExpression, diagnostics)
Case BoundKind.RangeCaseClause
Return ComputeRangeCaseClauseCondition(DirectCast(caseClause, BoundRangeCaseClause), conditionOpt, selectExpression, diagnostics)
Case Else
Throw ExceptionUtilities.UnexpectedValue(caseClause.Kind)
End Select
End Function
Private Function ComputeRelationalCaseClauseCondition(boundClause As BoundRelationalCaseClause, <Out()> ByRef conditionOpt As BoundExpression, selectExpression As BoundRValuePlaceholder, diagnostics As BindingDiagnosticBag) As BoundCaseClause
Dim syntax = DirectCast(boundClause.Syntax, RelationalCaseClauseSyntax)
' Exactly one of the operand or condition must be non-null
Debug.Assert(boundClause.ConditionOpt IsNot Nothing Xor boundClause.ValueOpt IsNot Nothing)
conditionOpt = If(boundClause.ConditionOpt, BindBinaryOperator(node:=syntax,
left:=selectExpression,
right:=boundClause.ValueOpt,
operatorTokenKind:=syntax.OperatorToken.Kind,
preliminaryOperatorKind:=boundClause.OperatorKind,
isOperandOfConditionalBranch:=False,
diagnostics:=diagnostics,
isSelectCase:=True).MakeCompilerGenerated())
Return boundClause.Update(boundClause.OperatorKind, valueOpt:=Nothing, conditionOpt:=conditionOpt)
End Function
Private Function ComputeSimpleCaseClauseCondition(boundClause As BoundSimpleCaseClause, <Out()> ByRef conditionOpt As BoundExpression, selectExpression As BoundRValuePlaceholder, diagnostics As BindingDiagnosticBag) As BoundCaseClause
' Exactly one of the value or condition must be non-null
Debug.Assert(boundClause.ConditionOpt IsNot Nothing Xor boundClause.ValueOpt IsNot Nothing)
conditionOpt = If(boundClause.ConditionOpt, BindBinaryOperator(node:=boundClause.Syntax,
left:=selectExpression,
right:=boundClause.ValueOpt,
operatorTokenKind:=SyntaxKind.EqualsToken,
preliminaryOperatorKind:=BinaryOperatorKind.Equals,
isOperandOfConditionalBranch:=False,
diagnostics:=diagnostics,
isSelectCase:=True).MakeCompilerGenerated())
Return boundClause.Update(valueOpt:=Nothing, conditionOpt:=conditionOpt)
End Function
Private Function ComputeRangeCaseClauseCondition(boundClause As BoundRangeCaseClause, <Out()> ByRef conditionOpt As BoundExpression, selectExpression As BoundRValuePlaceholder, diagnostics As BindingDiagnosticBag) As BoundCaseClause
Dim syntax = boundClause.Syntax
' Exactly one of the LowerBoundOpt or LowerBoundConditionOpt must be non-null
Debug.Assert(boundClause.LowerBoundOpt IsNot Nothing Xor boundClause.LowerBoundConditionOpt IsNot Nothing)
Dim lowerBoundConditionOpt = boundClause.LowerBoundConditionOpt
If lowerBoundConditionOpt Is Nothing Then
lowerBoundConditionOpt = BindBinaryOperator(
node:=boundClause.Syntax,
left:=selectExpression,
right:=boundClause.LowerBoundOpt,
operatorTokenKind:=SyntaxKind.GreaterThanEqualsToken,
preliminaryOperatorKind:=BinaryOperatorKind.GreaterThanOrEqual,
isOperandOfConditionalBranch:=False,
diagnostics:=diagnostics,
isSelectCase:=True).MakeCompilerGenerated()
End If
' Exactly one of the UpperBoundOpt or UpperBoundConditionOpt must be non-null
Debug.Assert(boundClause.UpperBoundOpt IsNot Nothing Xor boundClause.UpperBoundConditionOpt IsNot Nothing)
Dim upperBoundConditionOpt = boundClause.UpperBoundConditionOpt
If upperBoundConditionOpt Is Nothing Then
upperBoundConditionOpt = BindBinaryOperator(
node:=syntax,
left:=selectExpression,
right:=boundClause.UpperBoundOpt,
operatorTokenKind:=SyntaxKind.LessThanEqualsToken,
preliminaryOperatorKind:=BinaryOperatorKind.LessThanOrEqual,
isOperandOfConditionalBranch:=False,
diagnostics:=diagnostics,
isSelectCase:=True).MakeCompilerGenerated()
End If
conditionOpt = BindBinaryOperator(
node:=syntax,
left:=lowerBoundConditionOpt,
right:=upperBoundConditionOpt,
operatorTokenKind:=SyntaxKind.AndAlsoKeyword,
preliminaryOperatorKind:=BinaryOperatorKind.AndAlso,
isOperandOfConditionalBranch:=False,
diagnostics:=diagnostics,
isSelectCase:=True).MakeCompilerGenerated()
Return boundClause.Update(lowerBoundOpt:=Nothing, upperBoundOpt:=Nothing, lowerBoundConditionOpt:=lowerBoundConditionOpt, upperBoundConditionOpt:=upperBoundConditionOpt)
End Function
' Helper method to determine if we must rewrite the select case statement as an IF list or a SWITCH table
Private Function RecommendSwitchTable(selectExpr As BoundRValuePlaceholder, caseBlocks As ArrayBuilder(Of BoundCaseBlock), diagnostics As BindingDiagnosticBag) As Boolean
' We can rewrite select case statement as an IF list or a SWITCH table
' This function determines which method to use.
' The conditions for choosing the SWITCH table are:
' select case expression type must be integral/boolean/string (see TypeSymbolExtensions.IsValidTypeForSwitchTable())
' no "Is <relop>" cases, except for <relop> = BinaryOperatorKind.Equals
' no "<lb> To <ub>" cases for string type
' for integral/boolean type, case values must be (or expand to, as in ranges) integer constants
' for string type, all case values must be string constants and OptionCompareText must be False.
' beneficial over IF lists (as per a threshold on size ratio)
' ranges must have lower bound first
' We also generate warnings for Invalid range clauses in this function.
' Ideally we would like to generate them in BindRangeCaseClause.
' However, Dev10 doesn't do this check individually for each CaseClause.
' It is performed only if bounds for all clauses in the Select are integer constants and
' all clauses are either range clauses or equality clause.
' Doing this differently will produce warnings in more scenarios - breaking change.
If Not caseBlocks.Any() OrElse Not selectExpr.Type.IsValidTypeForSwitchTable() Then
Return False
End If
Dim isSelectExprStringType = selectExpr.Type.IsStringType
If isSelectExprStringType AndAlso OptionCompareText Then
Return False
End If
Dim recommendSwitch = True
For Each caseBlock In caseBlocks
For Each caseClause In caseBlock.CaseStatement.CaseClauses
Select Case caseClause.Kind
Case BoundKind.RelationalCaseClause
Dim relationalClause = DirectCast(caseClause, BoundRelationalCaseClause)
' Exactly one of the operand or condition must be non-null
Debug.Assert(relationalClause.ValueOpt IsNot Nothing Xor relationalClause.ConditionOpt IsNot Nothing)
Dim operand = relationalClause.ValueOpt
If operand Is Nothing OrElse
relationalClause.OperatorKind <> BinaryOperatorKind.Equals OrElse
operand.ConstantValueOpt Is Nothing OrElse
Not SwitchConstantValueHelper.IsValidSwitchCaseLabelConstant(operand.ConstantValueOpt) Then
Return False
End If
Case BoundKind.RangeCaseClause
' TODO: RecommendSwitchTable for Range clause.
' TODO: If we decide to implement it we will need to
' TODO: add heuristic to determine when the range is
' TODO: big enough to prefer IF lists over SWITCH table.
' TODO: We will also need to add logic in the emitter
' TODO: to iterate through ConstantValues in a range.
' For now we use IF lists if we encounter BoundRangeCaseClause
Dim rangeCaseClause = DirectCast(caseClause, BoundRangeCaseClause)
' Exactly one of the LowerBoundOpt or LowerBoundConditionOpt must be non-null
Debug.Assert(rangeCaseClause.LowerBoundOpt IsNot Nothing Xor rangeCaseClause.LowerBoundConditionOpt IsNot Nothing)
' Exactly one of the UpperBoundOpt or UpperBoundConditionOpt must be non-null
Debug.Assert(rangeCaseClause.UpperBoundOpt IsNot Nothing Xor rangeCaseClause.UpperBoundConditionOpt IsNot Nothing)
Dim lowerBound = rangeCaseClause.LowerBoundOpt
Dim upperBound = rangeCaseClause.UpperBoundOpt
If lowerBound Is Nothing OrElse
upperBound Is Nothing OrElse
lowerBound.ConstantValueOpt Is Nothing OrElse
upperBound.ConstantValueOpt Is Nothing OrElse
Not SwitchConstantValueHelper.IsValidSwitchCaseLabelConstant(lowerBound.ConstantValueOpt) OrElse
Not SwitchConstantValueHelper.IsValidSwitchCaseLabelConstant(upperBound.ConstantValueOpt) Then
Return False
End If
recommendSwitch = False
Case Else
Dim simpleCaseClause = DirectCast(caseClause, BoundSimpleCaseClause)
' Exactly one of the value or condition must be non-null
Debug.Assert(simpleCaseClause.ValueOpt IsNot Nothing Xor simpleCaseClause.ConditionOpt IsNot Nothing)
Dim value = simpleCaseClause.ValueOpt
If value Is Nothing OrElse
value.ConstantValueOpt Is Nothing OrElse
Not SwitchConstantValueHelper.IsValidSwitchCaseLabelConstant(value.ConstantValueOpt) Then
Return False
End If
End Select
Next
Next
' TODO: beneficial over IF lists (as per a threshold on size ratio)
Return Not ReportInvalidSelectCaseRange(caseBlocks, diagnostics) AndAlso recommendSwitch
End Function
' Function reports WRN_SelectCaseInvalidRange for any invalid select case range.
' Returns True if an invalid range was found, False otherwise.
Private Function ReportInvalidSelectCaseRange(caseBlocks As ArrayBuilder(Of BoundCaseBlock), diagnostics As BindingDiagnosticBag) As Boolean
For Each caseBlock In caseBlocks
For Each caseClause In caseBlock.CaseStatement.CaseClauses
Select Case caseClause.Kind
Case BoundKind.RangeCaseClause
Dim rangeCaseClause = DirectCast(caseClause, BoundRangeCaseClause)
Dim lowerBound = rangeCaseClause.LowerBoundOpt
Dim upperBound = rangeCaseClause.UpperBoundOpt
Debug.Assert(lowerBound IsNot Nothing)
Debug.Assert(lowerBound.ConstantValueOpt IsNot Nothing)
Debug.Assert(upperBound IsNot Nothing)
Debug.Assert(upperBound.ConstantValueOpt IsNot Nothing)
Debug.Assert(rangeCaseClause.LowerBoundConditionOpt Is Nothing)
Debug.Assert(rangeCaseClause.UpperBoundConditionOpt Is Nothing)
If IsInvalidSelectCaseRange(lowerBound.ConstantValueOpt, upperBound.ConstantValueOpt) Then
ReportDiagnostic(diagnostics, rangeCaseClause.Syntax, ERRID.WRN_SelectCaseInvalidRange)
Return True
End If
End Select
Next
Next
Return False
End Function
Private Shared Function IsInvalidSelectCaseRange(lbConstantValue As ConstantValue, ubConstantValue As ConstantValue) As Boolean
Debug.Assert(lbConstantValue IsNot Nothing)
Debug.Assert(ubConstantValue IsNot Nothing)
Debug.Assert(lbConstantValue.SpecialType = ubConstantValue.SpecialType)
Select Case lbConstantValue.SpecialType
Case SpecialType.System_Boolean,
SpecialType.System_Byte,
SpecialType.System_UInt16,
SpecialType.System_UInt32,
SpecialType.System_UInt64
Return lbConstantValue.UInt64Value > ubConstantValue.UInt64Value
Case SpecialType.System_SByte,
SpecialType.System_Int16,
SpecialType.System_Int32,
SpecialType.System_Int64,
SpecialType.System_Char
Return lbConstantValue.Int64Value > ubConstantValue.Int64Value
Case SpecialType.System_Single,
SpecialType.System_Double
Return lbConstantValue.DoubleValue > ubConstantValue.DoubleValue
Case SpecialType.System_Decimal
Return lbConstantValue.DecimalValue > ubConstantValue.DecimalValue
Case Else
Return False
End Select
End Function
#End Region
#End Region
End Class
End Namespace
|