File: Lowering\LocalRewriter\LocalRewriter_ConditionalExpressions.vb
Web Access
Project: src\src\Compilers\VisualBasic\Portable\Microsoft.CodeAnalysis.VisualBasic.vbproj (Microsoft.CodeAnalysis.VisualBasic)
' 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 Microsoft.CodeAnalysis.VisualBasic.Symbols
 
Namespace Microsoft.CodeAnalysis.VisualBasic
 
    Partial Friend NotInheritable Class LocalRewriter
 
        Public Overrides Function VisitBinaryConditionalExpression(node As BoundBinaryConditionalExpression) As BoundNode
            If Me._inExpressionLambda Then
                ' If we are inside expression lambda we want to keep binary conditional expression
                Return RewriteBinaryConditionalExpressionInExpressionLambda(node)
            End If
 
            If node.TestExpression.Type IsNot Nothing AndAlso node.TestExpression.Type.IsNullableType Then
                Return RewriteNullableBinaryConditionalExpression(node)
            End If
 
            Dim convertedTestExpression As BoundExpression = node.ConvertedTestExpression
            If convertedTestExpression Is Nothing Then
                Debug.Assert(node.TestExpressionPlaceholder Is Nothing)
                Return TransformReferenceOrUnconstrainedRewrittenBinaryConditionalExpression(MyBase.VisitBinaryConditionalExpression(node))
            End If
 
            If convertedTestExpression.Kind = BoundKind.Conversion Then
                Dim boundConversion = DirectCast(node.ConvertedTestExpression, BoundConversion)
                Dim conversion = boundConversion.ConversionKind
 
                If Conversions.IsWideningConversion(conversion) AndAlso Conversions.IsCLRPredefinedConversion(conversion) AndAlso ((conversion And ConversionKind.TypeParameter) = 0) Then
                    Debug.Assert(boundConversion.Operand Is If(node.TestExpressionPlaceholder, node.TestExpression))
 
                    ' We can ignore conversion in this case
                    Return TransformReferenceOrUnconstrainedRewrittenBinaryConditionalExpression(
                                node.Update(VisitExpressionNode(node.TestExpression),
                                            Nothing,
                                            Nothing,
                                            VisitExpressionNode(node.ElseExpression),
                                            node.ConstantValueOpt,
                                            node.Type))
                End If
            End If
 
            ' Rewrite binary conditional expression using ternary conditional expression
 
            Dim placeholder As BoundValuePlaceholderBase = node.TestExpressionPlaceholder
 
            ' Rewrite test expression and create a local to capture the value if needed
            Dim rewrittenTestExpression = VisitExpressionNode(node.TestExpression)
            Dim rewrittenTestExpressionType As TypeSymbol = rewrittenTestExpression.Type
 
            ' NOTE: we create a temp variable if only it is really needed; 
            '       locals and parameters don't need it
            Dim tempVariableSymbol As SynthesizedLocal = Nothing
            Dim placeholderSubstitute As BoundExpression = Nothing
 
            Select Case rewrittenTestExpression.Kind
                Case BoundKind.Local,
                     BoundKind.Parameter,
                     BoundKind.Literal
                    placeholderSubstitute = rewrittenTestExpression
 
                Case Else
                    '  create a temp variable
                    tempVariableSymbol = New SynthesizedLocal(Me._currentMethodOrLambda, rewrittenTestExpressionType, SynthesizedLocalKind.LoweringTemp)
                    '  temp variable reference
                    placeholderSubstitute = New BoundLocal(rewrittenTestExpression.Syntax,
                                                      tempVariableSymbol,
                                                      isLValue:=False,
                                                      type:=rewrittenTestExpressionType)
            End Select
 
            ' Rewrite test expression conversion 
            If placeholder IsNot Nothing Then
                ' substitute placeholder with temp created or a local/parameter
                AddPlaceholderReplacement(placeholder, placeholderSubstitute)
            End If
 
            Dim rewrittenConvertedTestExpression = VisitExpressionNode(node.ConvertedTestExpression)
 
            If placeholder IsNot Nothing Then
                RemovePlaceholderReplacement(placeholder)
            End If
 
            Dim ternaryConditionalExpression As BoundExpression = placeholderSubstitute
 
            If tempVariableSymbol IsNot Nothing Then
                '  if there is a temp local assign a value to it
                ternaryConditionalExpression =
                    New BoundAssignmentOperator(rewrittenTestExpression.Syntax,
                                                left:=New BoundLocal(rewrittenTestExpression.Syntax,
                                                                     tempVariableSymbol,
                                                                     isLValue:=True,
                                                                     type:=rewrittenTestExpressionType),
                                                right:=rewrittenTestExpression,
                                                suppressObjectClone:=True,
                                                type:=rewrittenTestExpressionType)
            End If
 
            ' NOTE: newTestExpression is not actually a boolean expression, but it is of a reference 
            '       type and has 'boolean semantic' (Nothing = False, True otherwise)
            Dim result As BoundExpression =
                TransformRewrittenTernaryConditionalExpression(
                    New BoundTernaryConditionalExpression(node.Syntax,
                                                          condition:=ternaryConditionalExpression,
                                                          whenTrue:=rewrittenConvertedTestExpression,
                                                          whenFalse:=VisitExpressionNode(node.ElseExpression),
                                                          constantValueOpt:=node.ConstantValueOpt,
                                                          type:=node.Type))
 
            If tempVariableSymbol Is Nothing Then
                Return result
            End If
 
            Return New BoundSequence(result.Syntax,
                                     locals:=ImmutableArray.Create(Of LocalSymbol)(tempVariableSymbol),
                                     sideEffects:=ImmutableArray(Of BoundExpression).Empty,
                                     valueOpt:=result,
                                     type:=result.Type)
        End Function
 
        Private Function RewriteBinaryConditionalExpressionInExpressionLambda(node As BoundBinaryConditionalExpression) As BoundExpression
            ' If we are inside expression lambda we want to keep binary conditional expression
            Dim testExpression As BoundExpression = node.TestExpression
            Dim testExpressionType As TypeSymbol = testExpression.Type
            Dim rewrittenTestExpression As BoundExpression = VisitExpression(testExpression)
            Debug.Assert(TypeSymbol.Equals(testExpressionType, rewrittenTestExpression.Type, TypeCompareKind.ConsiderEverything))
 
            Dim rewrittenWhenTrue As BoundExpression = Nothing
 
            Dim convertedTestExpression As BoundExpression = node.ConvertedTestExpression
            If convertedTestExpression Is Nothing Then
                If Not testExpressionType.IsNullableOfBoolean AndAlso testExpressionType.IsNullableType Then
                    rewrittenWhenTrue = New BoundConversion(rewrittenTestExpression.Syntax,
                                                            rewrittenTestExpression,
                                                            ConversionKind.WideningNullable,
                                                            False,
                                                            False,
                                                            testExpressionType.GetNullableUnderlyingTypeOrSelf)
                End If
            Else
                Debug.Assert(node.TestExpressionPlaceholder Is Nothing OrElse TypeSymbol.Equals(node.TestExpressionPlaceholder.Type, testExpressionType.GetNullableUnderlyingTypeOrSelf, TypeCompareKind.ConsiderEverything))
                rewrittenWhenTrue = VisitExpressionNode(convertedTestExpression,
                                                        node.TestExpressionPlaceholder,
                                                        If(testExpressionType.IsNullableType,
                                                           New BoundConversion(rewrittenTestExpression.Syntax,
                                                               rewrittenTestExpression,
                                                               ConversionKind.WideningNullable,
                                                               False,
                                                               False,
                                                               testExpressionType.GetNullableUnderlyingTypeOrSelf),
                                                           rewrittenTestExpression))
            End If
 
            Dim rewrittenWhenFalse As BoundExpression = VisitExpression(node.ElseExpression)
            Return node.Update(rewrittenTestExpression, rewrittenWhenTrue, Nothing, rewrittenWhenFalse, node.ConstantValueOpt, node.Type)
        End Function
 
        Private Shared Function TransformReferenceOrUnconstrainedRewrittenBinaryConditionalExpression(node As BoundNode) As BoundNode
            Return If(node.Kind <> BoundKind.BinaryConditionalExpression, node,
                      TransformReferenceOrUnconstrainedRewrittenBinaryConditionalExpression(DirectCast(node, BoundBinaryConditionalExpression)))
        End Function
 
        Private Shared Function TransformReferenceOrUnconstrainedRewrittenBinaryConditionalExpression(node As BoundBinaryConditionalExpression) As BoundExpression
            Debug.Assert(node.ConvertedTestExpression Is Nothing)   ' Those should be rewritten by now
            Debug.Assert(node.TestExpressionPlaceholder Is Nothing)
 
            ' NOTE: C# implementation rewrites the coalesce expression to handle the bug 
            '       related to IF(DirectCast(<class1>, I1), DirectCast(<class2>, I1))
            '       VB handles this case in Emitter
 
            If node.HasErrors Then
                Return node
            End If
 
            Dim testExpr = node.TestExpression
            Dim elseExpr = node.ElseExpression
 
            ' Nullable value types should have been handled earlier in lowering, so we can only have reference types and
            ' unconstrained type parameters at this point
            Debug.Assert(testExpr.IsNothingLiteral OrElse Not testExpr.Type.IsValueType)
 
            ' TODO: Checking type equality of test and else is not strictly needed.
            '       Consider removing this requirement.
            If testExpr.IsConstant AndAlso (TypeSymbol.Equals(testExpr.Type, elseExpr.Type, TypeCompareKind.ConsiderEverything)) Then
                '  the only valid IF(...) with the first constant are: IF("abc", <expr>) or IF(Nothing, <expr>)
                If testExpr.ConstantValueOpt.IsNothing Then
                    ' CASE: IF(Nothing, <expr>) 
                    '   Special case: just emit ElseExpression
                    Return node.ElseExpression
                Else
                    ' CASE: IF("abc", <expr>) 
                    '   Dominant type may be different from String, so add conversion
                    Return testExpr
                End If
 
            End If
 
            Debug.Assert(Not testExpr.Type.IsValueType)
            Return node
        End Function
 
        Private Function RewriteNullableBinaryConditionalExpression(node As BoundBinaryConditionalExpression) As BoundNode
            ' == rewrite operands and check for trivial cases
            Dim rewrittenLeft = Me.VisitExpressionNode(node.TestExpression)
            If HasValue(rewrittenLeft) Then
                Return MakeResultFromNonNullLeft(rewrittenLeft, node.ConvertedTestExpression, node.TestExpressionPlaceholder)
            End If
 
            Dim rewrittenRight = Me.VisitExpressionNode(node.ElseExpression)
            If HasNoValue(rewrittenLeft) Then
                Return rewrittenRight
            End If
 
            Dim whenNotNull As BoundExpression = Nothing
            Dim whenNull As BoundExpression = Nothing
            If IsConditionalAccess(rewrittenLeft, whenNotNull, whenNull) Then
                If HasNoValue(whenNull) Then
                    If HasValue(whenNotNull) Then
                        Return UpdateConditionalAccess(rewrittenLeft,
                                                       MakeResultFromNonNullLeft(whenNotNull, node.ConvertedTestExpression, node.TestExpressionPlaceholder),
                                                       rewrittenRight)
 
                    Else
                        Debug.Assert(Not HasNoValue(whenNotNull)) ' Not optimizing for this case
 
                        ' CONSIDER: We could do inlining when rewrittenRight.IsConstant
                    End If
                End If
            End If
 
            If rewrittenLeft.Type.IsNullableType() AndAlso
               rewrittenRight.Type.IsSameTypeIgnoringAll(rewrittenLeft.Type.GetNullableUnderlyingType()) _
            Then
                ' Optimize If(left, right) to left.GetValueOrDefault() when left is T? and right is the default value of T
                If rewrittenRight.IsDefaultValue() Then
                    Dim optimized = NullableValueOrDefault(rewrittenLeft, isOptional:=True)
                    If optimized IsNot Nothing Then
                        Return optimized
                    End If
                End If
 
                ' Optimize If(left, right) to left.GetValueOrDefault(right) when left is T? and right is a side-effectless expression of type T
                If rewrittenRight.ConstantValueOpt IsNot Nothing OrElse
                   Not If(TryCast(rewrittenRight, BoundLocal)?.LocalSymbol.IsRef, True) OrElse
                   Not If(TryCast(rewrittenRight, BoundParameter)?.ParameterSymbol.IsByRef, True) Then
                    Dim optimized = NullableValueOrDefaultOpt(rewrittenLeft, rewrittenRight)
                    If optimized IsNot Nothing Then
                        Return optimized
                    End If
                End If
            End If
 
            '=== Rewrite binary conditional expression using ternary conditional expression
            Dim temp As SynthesizedLocal = Nothing
            Dim tempInit As BoundExpression = Nothing
 
            ' no need to capture locals since we will not 
            ' evaluate anything between HasValue and ValueOrDefault
            Dim capturedleft As BoundExpression = CaptureNullableIfNeeded(rewrittenLeft,
                                                                          temp,
                                                                          tempInit,
                                                                          doNotCaptureLocals:=True)
 
            Dim condition As BoundExpression = NullableHasValue(If(tempInit, capturedleft))
            Dim whenTrue As BoundExpression
 
            If node.ConvertedTestExpression Is Nothing Then
                whenTrue = NullableValueOrDefault(capturedleft)
            Else
                If capturedleft.Type.IsSameTypeIgnoringAll(node.ConvertedTestExpression.Type) Then
                    ' Optimization
                    whenTrue = capturedleft
                Else
                    whenTrue = VisitExpressionNode(node.ConvertedTestExpression,
                                               node.TestExpressionPlaceholder,
                                               NullableValueOrDefault(capturedleft))
                End If
            End If
 
            Dim result As BoundExpression = MakeTernaryConditionalExpression(node.Syntax, condition, whenTrue, rewrittenRight)
 
            ' if we used a temp, arrange a sequence for it
            If temp IsNot Nothing Then
                result = New BoundSequence(node.Syntax,
                                     ImmutableArray.Create(Of LocalSymbol)(temp),
                                     ImmutableArray(Of BoundExpression).Empty,
                                     result,
                                     result.Type)
            End If
 
            Return result
        End Function
 
        Private Function MakeResultFromNonNullLeft(rewrittenLeft As BoundExpression, convertedTestExpression As BoundExpression, testExpressionPlaceholder As BoundRValuePlaceholder) As BoundExpression
            If convertedTestExpression Is Nothing Then
                Return NullableValueOrDefault(rewrittenLeft)
            Else
                If rewrittenLeft.Type.IsSameTypeIgnoringAll(convertedTestExpression.Type) Then
                    ' Optimization
                    Return rewrittenLeft
                End If
 
                Return VisitExpressionNode(convertedTestExpression,
                                           testExpressionPlaceholder,
                                           NullableValueOrDefault(rewrittenLeft))
            End If
        End Function
 
        Private Function VisitExpressionNode(node As BoundExpression,
                                    placeholder As BoundValuePlaceholderBase,
                                    placeholderSubstitute As BoundExpression) As BoundExpression
 
            If placeholder IsNot Nothing Then
                AddPlaceholderReplacement(placeholder, placeholderSubstitute)
            End If
 
            Dim result = VisitExpressionNode(node)
 
            If placeholder IsNot Nothing Then
                RemovePlaceholderReplacement(placeholder)
            End If
 
            Return result
        End Function
 
        Public Overrides Function VisitTernaryConditionalExpression(node As BoundTernaryConditionalExpression) As BoundNode
            Return TransformRewrittenTernaryConditionalExpression(
                        DirectCast(MyBase.VisitTernaryConditionalExpression(node), BoundTernaryConditionalExpression))
        End Function
 
        Private Shared Function TransformRewrittenTernaryConditionalExpression(node As BoundTernaryConditionalExpression) As BoundExpression
            If node.Condition.IsConstant AndAlso node.WhenTrue.IsConstant AndAlso node.WhenFalse.IsConstant Then
                ' This optimization be applies if only *all three* operands are constants!!!
 
                Debug.Assert(node.Condition.ConstantValueOpt.IsBoolean OrElse
                             node.Condition.ConstantValueOpt.IsNothing OrElse
                             node.Condition.ConstantValueOpt.IsString)
 
                Dim value As Boolean = If(node.Condition.ConstantValueOpt.IsBoolean,
                                          node.Condition.ConstantValueOpt.BooleanValue,
                                          node.Condition.ConstantValueOpt.IsString)
 
                Return If(value, node.WhenTrue, node.WhenFalse)
            End If
            Return node
        End Function
 
    End Class
End Namespace