File: Lowering\LocalRewriter\LocalRewriter_NullableHelpers.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 System.Runtime.InteropServices
Imports Microsoft.CodeAnalysis.PooledObjects
Imports Microsoft.CodeAnalysis.VisualBasic.Symbols
 
Namespace Microsoft.CodeAnalysis.VisualBasic
    Partial Friend NotInheritable Class LocalRewriter
 
        Private Function WrapInNullable(expr As BoundExpression, nullableType As TypeSymbol) As BoundExpression
            Debug.Assert(nullableType.GetNullableUnderlyingType.IsSameTypeIgnoringAll(expr.Type))
 
            Dim ctor = GetNullableMethod(expr.Syntax, nullableType, SpecialMember.System_Nullable_T__ctor)
 
            If ctor IsNot Nothing Then
                Return New BoundObjectCreationExpression(expr.Syntax,
                                                     ctor,
                                                     ImmutableArray.Create(expr),
                                                     Nothing,
                                                     nullableType)
            End If
 
            Return New BoundBadExpression(expr.Syntax, LookupResultKind.NotReferencable, ImmutableArray(Of Symbol).Empty, ImmutableArray.Create(expr), nullableType, hasErrors:=True)
        End Function
 
        ''' <summary>
        ''' Splits nullable operand into a hasValueExpression and an expression that represents underlying value (returned).
        ''' 
        ''' Underlying value can be called after calling hasValueExpr without duplicated side-effects.
        ''' Note that hasValueExpr is guaranteed to have NO SIDE-EFFECTS, while result value is 
        ''' expected to be called exactly ONCE. That is the normal pattern in operator lifting.
        ''' 
        ''' All necessary temps and side-effecting initializations are appended to temps and inits
        ''' </summary>
        Private Function ProcessNullableOperand(operand As BoundExpression,
                                                <Out> ByRef hasValueExpr As BoundExpression,
                                                ByRef temps As ArrayBuilder(Of LocalSymbol),
                                                ByRef inits As ArrayBuilder(Of BoundExpression),
                                                doNotCaptureLocals As Boolean) As BoundExpression
 
            Return ProcessNullableOperand(operand, hasValueExpr, temps, inits, doNotCaptureLocals, HasValue(operand))
        End Function
 
        Private Function ProcessNullableOperand(operand As BoundExpression,
                                        <Out> ByRef hasValueExpr As BoundExpression,
                                        ByRef temps As ArrayBuilder(Of LocalSymbol),
                                        ByRef inits As ArrayBuilder(Of BoundExpression),
                                        doNotCaptureLocals As Boolean,
                                        operandHasValue As Boolean) As BoundExpression
 
            Debug.Assert(Not HasNoValue(operand), "processing nullable operand when it is known to be null")
 
            If operandHasValue Then
                operand = NullableValueOrDefault(operand)
            End If
 
            Dim captured = CaptureNullableIfNeeded(operand, temps, inits, doNotCaptureLocals)
 
            If operandHasValue Then
                hasValueExpr = New BoundLiteral(operand.Syntax, ConstantValue.True, Me.GetSpecialType(SpecialType.System_Boolean))
                Return captured
            End If
 
            hasValueExpr = NullableHasValue(captured)
            Return NullableValueOrDefault(captured)
        End Function
 
        ' Right operand could be a method that takes Left operand byref. Ex: " local And TakesArgByref(local) "
        ' So in general we must capture Left even if it is a local.
        ' however in many case we do not need that.
        Private Shared Function RightCantChangeLeftLocal(left As BoundExpression, right As BoundExpression) As Boolean
            ' TODO: in most cases right operand does not change value of the left one
            '       we could be smarter than this.
            Return right.Kind = BoundKind.Local OrElse
                   right.Kind = BoundKind.Parameter
 
        End Function
 
        ''' <summary>
        ''' Returns a NOT-SIDE-EFFECTING expression that represents results of the operand
        ''' If such transformation requires a temp, the temp and its initializing expression
        ''' are returned in temp/init
        ''' </summary>
        Private Function CaptureNullableIfNeeded(operand As BoundExpression,
                                                        <Out> ByRef temp As SynthesizedLocal,
                                                        <Out> ByRef init As BoundExpression,
                                                        doNotCaptureLocals As Boolean) As BoundExpression
 
            temp = Nothing
            init = Nothing
 
            If operand.IsConstant Then
                Return operand
            End If
 
            If doNotCaptureLocals Then
                If operand.Kind = BoundKind.Local AndAlso Not DirectCast(operand, BoundLocal).LocalSymbol.IsByRef Then
                    Return operand
                End If
 
                If operand.Kind = BoundKind.Parameter AndAlso Not DirectCast(operand, BoundParameter).ParameterSymbol.IsByRef Then
                    Return operand
                End If
            End If
 
            ' capture into local.
            Return CaptureOperand(operand, temp, init)
        End Function
 
        Private Function CaptureOperand(operand As BoundExpression, <Out> ByRef temp As SynthesizedLocal, <Out> ByRef init As BoundExpression) As BoundExpression
            temp = New SynthesizedLocal(Me._currentMethodOrLambda, operand.Type, SynthesizedLocalKind.LoweringTemp)
            Dim localAccess = New BoundLocal(operand.Syntax, temp, True, temp.Type)
            init = New BoundAssignmentOperator(operand.Syntax, localAccess, operand, True, operand.Type)
            Return localAccess.MakeRValue
        End Function
 
        Private Function CaptureNullableIfNeeded(
            operand As BoundExpression,
            <[In], Out> ByRef temps As ArrayBuilder(Of LocalSymbol),
            <[In], Out> ByRef inits As ArrayBuilder(Of BoundExpression),
            doNotCaptureLocals As Boolean
        ) As BoundExpression
 
            Dim temp As SynthesizedLocal = Nothing
            Dim init As BoundExpression = Nothing
            Dim captured = CaptureNullableIfNeeded(operand, temp, init, doNotCaptureLocals)
 
            If temp IsNot Nothing Then
                temps = If(temps, ArrayBuilder(Of LocalSymbol).GetInstance)
                temps.Add(temp)
 
                Debug.Assert(init IsNot Nothing)
                inits = If(inits, ArrayBuilder(Of BoundExpression).GetInstance)
                inits.Add(init)
            Else
                Debug.Assert(captured Is operand)
            End If
 
            Return captured
        End Function
 
        ''' <summary>
        ''' Returns expression that -
        ''' a) evaluates the operand if needed
        ''' b) produces it's ValueOrDefault.
        ''' The helper is familiar with wrapping expressions and will go directly after the value 
        ''' skipping wrap/unwrap steps.
        ''' </summary>
        Private Function NullableValueOrDefault(expr As BoundExpression, Optional isOptional As Boolean = False) As BoundExpression
            Debug.Assert(expr.Type.IsNullableType)
 
            ' check if we are not getting value from freshly constructed nullable
            ' no need to wrap/unwrap it then.
            Select Case expr.Kind
                Case BoundKind.ObjectCreationExpression
                    Dim objectCreation = DirectCast(expr, BoundObjectCreationExpression)
 
                    ' passing one argument means we are calling New Nullable<T>(arg)
                    If objectCreation.Arguments.Length = 1 Then
                        Return objectCreation.Arguments(0)
                    End If
                Case BoundKind.Conversion
                    Dim conversion = DirectCast(expr, BoundConversion)
                    If IsConversionFromUnderlyingToNullable(conversion) Then
                        Return conversion.Operand
                    End If
 
                Case Else
                    Debug.Assert(Not HasValue(expr))
 
                    If Not _inExpressionLambda AndAlso expr.Type.IsNullableOfBoolean() Then
 
                        Dim whenNotNull As BoundExpression = Nothing
                        Dim whenNull As BoundExpression = Nothing
                        If IsConditionalAccess(expr, whenNotNull, whenNull) AndAlso HasNoValue(whenNull) Then
                            Debug.Assert(Not HasNoValue(whenNotNull))
                            Dim valueOrDefault = NullableValueOrDefault(whenNotNull, isOptional)
                            If valueOrDefault Is Nothing Then
                                Debug.Assert(isOptional)
                                Return Nothing
                            End If
                            Return UpdateConditionalAccess(expr,
                                                           valueOrDefault,
                                                           New BoundLiteral(expr.Syntax, ConstantValue.False, expr.Type.GetNullableUnderlyingType()))
                        End If
                    End If
            End Select
 
            Dim getValueOrDefaultMethod = GetNullableMethod(expr.Syntax, expr.Type, SpecialMember.System_Nullable_T_GetValueOrDefault, isOptional)
 
            If getValueOrDefaultMethod IsNot Nothing Then
                Return New BoundCall(expr.Syntax,
                                 getValueOrDefaultMethod,
                                 Nothing,
                                 expr,
                                 ImmutableArray(Of BoundExpression).Empty,
                                 Nothing,
                                 isLValue:=False,
                                 suppressObjectClone:=True,
                                 type:=getValueOrDefaultMethod.ReturnType)
            End If
 
            Return If(isOptional, Nothing, New BoundBadExpression(expr.Syntax, LookupResultKind.NotReferencable, ImmutableArray(Of Symbol).Empty, ImmutableArray.Create(expr), expr.Type.GetNullableUnderlyingType(), hasErrors:=True))
        End Function
 
        Private Function NullableValueOrDefaultOpt(expr As BoundExpression, defaultValue As BoundExpression) As BoundExpression
            Debug.Assert(expr.Type.IsNullableType)
 
            Dim getValueOrDefaultWithDefaultValueMethod = GetNullableMethod(expr.Syntax, expr.Type, SpecialMember.System_Nullable_T_GetValueOrDefaultDefaultValue, isOptional:=True)
 
            If getValueOrDefaultWithDefaultValueMethod IsNot Nothing Then
                Return New BoundCall(expr.Syntax,
                                 getValueOrDefaultWithDefaultValueMethod,
                                 Nothing,
                                 expr,
                                 ImmutableArray.Create(defaultValue),
                                 Nothing,
                                 isLValue:=False,
                                 suppressObjectClone:=True,
                                 type:=getValueOrDefaultWithDefaultValueMethod.ReturnType)
            End If
 
            Return Nothing
        End Function
 
        Private Shared Function IsConversionFromUnderlyingToNullable(conversion As BoundConversion) As Boolean
            Return (conversion.ConversionKind And (ConversionKind.Widening Or ConversionKind.Nullable Or ConversionKind.UserDefined)) = (ConversionKind.Widening Or ConversionKind.Nullable) AndAlso
                   conversion.Type.GetNullableUnderlyingType().Equals(conversion.Operand.Type, TypeCompareKind.AllIgnoreOptionsForVB)
        End Function
 
        Private Function NullableValue(expr As BoundExpression) As BoundExpression
            Debug.Assert(expr.Type.IsNullableType)
 
            If HasValue(expr) Then
                Return NullableValueOrDefault(expr)
            End If
 
            Dim getValueMethod As MethodSymbol = GetNullableMethod(expr.Syntax, expr.Type, SpecialMember.System_Nullable_T_get_Value)
 
            If getValueMethod IsNot Nothing Then
                Return New BoundCall(expr.Syntax,
                                 getValueMethod,
                                 Nothing,
                                 expr,
                                 ImmutableArray(Of BoundExpression).Empty,
                                 Nothing,
                                 isLValue:=False,
                                 suppressObjectClone:=True,
                                 type:=getValueMethod.ReturnType)
            End If
 
            Return New BoundBadExpression(expr.Syntax, LookupResultKind.NotReferencable, ImmutableArray(Of Symbol).Empty, ImmutableArray.Create(expr), expr.Type.GetNullableUnderlyingType(), hasErrors:=True)
        End Function
 
        ''' <summary>
        ''' Evaluates expr and calls HasValue on it.
        ''' </summary>
        Private Function NullableHasValue(expr As BoundExpression) As BoundExpression
            Debug.Assert(expr.Type.IsNullableType)
 
            ' when we statically know if expr HasValue we may skip 
            ' evaluation depending on context.
            Debug.Assert(Not HasValue(expr))
            Debug.Assert(Not HasNoValue(expr))
 
            Dim hasValueMethod As MethodSymbol = GetNullableMethod(expr.Syntax, expr.Type, SpecialMember.System_Nullable_T_get_HasValue)
 
            If hasValueMethod IsNot Nothing Then
                Return New BoundCall(expr.Syntax,
                                 hasValueMethod,
                                 Nothing,
                                 expr,
                                 ImmutableArray(Of BoundExpression).Empty,
                                 Nothing,
                                 isLValue:=False,
                                 suppressObjectClone:=True,
                                 type:=hasValueMethod.ReturnType)
            End If
 
            Return New BoundBadExpression(expr.Syntax, LookupResultKind.NotReferencable, ImmutableArray(Of Symbol).Empty, ImmutableArray.Create(expr),
                                          Me.Compilation.GetSpecialType(SpecialType.System_Boolean), hasErrors:=True)
        End Function
 
        Private Shared Function NullableNull(syntax As SyntaxNode, nullableType As TypeSymbol) As BoundExpression
            Debug.Assert(nullableType.IsNullableType)
 
            Return New BoundObjectCreationExpression(syntax,
                                Nothing,
                                ImmutableArray(Of BoundExpression).Empty,
                                Nothing,
                                nullableType)
        End Function
 
        ''' <summary>
        ''' Checks that candidate Null expression is a simple expression that produces Null of the desired type
        ''' (not a conversion or anything like that) and returns it.
        ''' Otherwise creates "New T?()" expression.
        ''' </summary>
        Private Shared Function NullableNull(candidateNullExpression As BoundExpression,
                                             type As TypeSymbol) As BoundExpression
            Debug.Assert(HasNoValue(candidateNullExpression))
 
            ' in case if the expression is any more complicated than just creating a Null
            ' simplify it. This may happen if HasNoValue gets smarter and can
            ' detect situations other than "New T?()"
            If (Not type.IsSameTypeIgnoringAll(candidateNullExpression.Type)) OrElse
                candidateNullExpression.Kind <> BoundKind.ObjectCreationExpression Then
 
                Return NullableNull(candidateNullExpression.Syntax, type)
            End If
 
            Return candidateNullExpression
        End Function
 
        Private Function NullableFalse(syntax As SyntaxNode, nullableOfBoolean As TypeSymbol) As BoundExpression
            Debug.Assert(nullableOfBoolean.IsNullableOfBoolean)
            Dim booleanType = nullableOfBoolean.GetNullableUnderlyingType
            Return WrapInNullable(New BoundLiteral(syntax, ConstantValue.False, booleanType), nullableOfBoolean)
        End Function
 
        Private Function NullableTrue(syntax As SyntaxNode, nullableOfBoolean As TypeSymbol) As BoundExpression
            Debug.Assert(nullableOfBoolean.IsNullableOfBoolean)
            Dim booleanType = nullableOfBoolean.GetNullableUnderlyingType
            Return WrapInNullable(New BoundLiteral(syntax, ConstantValue.True, booleanType), nullableOfBoolean)
        End Function
 
        Private Function GetNullableMethod(syntax As SyntaxNode, nullableType As TypeSymbol, member As SpecialMember, Optional isOptional As Boolean = False) As MethodSymbol
            Dim method As MethodSymbol = Nothing
 
            If TryGetSpecialMember(method, member, syntax, isOptional) Then
                Dim substitutedType = DirectCast(nullableType, SubstitutedNamedType)
                Return DirectCast(substitutedType.GetMemberForDefinition(method), MethodSymbol)
            End If
 
            Return Nothing
        End Function
 
        Private Function NullableOfBooleanValue(syntax As SyntaxNode, isTrue As Boolean, nullableOfBoolean As TypeSymbol) As BoundExpression
            If isTrue Then
                Return NullableTrue(syntax, nullableOfBoolean)
            Else
                Return NullableFalse(syntax, nullableOfBoolean)
            End If
        End Function
 
        ''' <summary>
        ''' returns true when expression has NO SIDE-EFFECTS and is known to produce nullable NULL
        ''' </summary>
        Private Shared Function HasNoValue(expr As BoundExpression) As Boolean
            Debug.Assert(expr.Type.IsNullableType)
 
            If expr.Kind = BoundKind.ObjectCreationExpression Then
                Dim objCreation = DirectCast(expr, BoundObjectCreationExpression)
                ' Nullable<T> has only one ctor with parameters and only that one sets hasValue = true
                Return objCreation.Arguments.Length = 0
            End If
 
            ' by default we do not know
            Return False
        End Function
 
        ''' <summary>
        ''' Returns true when expression is known to produce nullable NOT-NULL
        ''' NOTE: unlike HasNoValue case, HasValue expressions may have side-effects.
        ''' </summary>
        Private Shared Function HasValue(expr As BoundExpression) As Boolean
            Debug.Assert(expr.Type.IsNullableType)
 
            Select Case expr.Kind
                Case BoundKind.ObjectCreationExpression
                    Dim objCreation = DirectCast(expr, BoundObjectCreationExpression)
                    ' Nullable<T> has only one ctor with parameters and only that one sets hasValue = true
                    Return objCreation.Arguments.Length <> 0
                Case BoundKind.Conversion
                    If IsConversionFromUnderlyingToNullable(DirectCast(expr, BoundConversion)) Then
                        Return True
                    End If
            End Select
 
            ' by default we do not know
            Return False
        End Function
 
        ''' <summary>
        ''' Helper to generate binary expressions.
        ''' Performs some trivial constant folding.
        ''' TODO: Perhaps belong to a different file
        ''' </summary>
        Private Function MakeBinaryExpression(syntax As SyntaxNode,
                                            binaryOpKind As BinaryOperatorKind,
                                            left As BoundExpression,
                                            right As BoundExpression,
                                            isChecked As Boolean,
                                            resultType As TypeSymbol) As BoundExpression
 
            Debug.Assert(Not left.Type.IsNullableType)
            Debug.Assert(Not right.Type.IsNullableType)
 
            Dim intOverflow As Boolean = False
            Dim divideByZero As Boolean = False
            Dim lengthOutOfLimit As Boolean = False
 
            Dim constant = OverloadResolution.TryFoldConstantBinaryOperator(binaryOpKind, left, right, resultType, intOverflow, divideByZero, lengthOutOfLimit)
            If constant IsNot Nothing AndAlso
                Not divideByZero AndAlso
                Not (intOverflow And isChecked) AndAlso
                Not lengthOutOfLimit Then
 
                Debug.Assert(Not constant.IsBad)
                Return New BoundLiteral(syntax, constant, resultType)
            End If
 
            Select Case binaryOpKind
                Case BinaryOperatorKind.Subtract
                    If right.IsDefaultValueConstant Then
                        Return left
                    End If
 
                Case BinaryOperatorKind.Add,
                     BinaryOperatorKind.Or,
                     BinaryOperatorKind.OrElse
 
                    ' if one of operands is trivial, return the other one
                    If left.IsDefaultValueConstant Then
                        Return right
                    End If
 
                    If right.IsDefaultValueConstant Then
                        Return left
                    End If
 
                    ' if one of operands is True, evaluate the other and return the True one
                    If left.IsTrueConstant Then
                        Return MakeSequence(right, left)
                    End If
 
                    If right.IsTrueConstant Then
                        Return MakeSequence(left, right)
                    End If
 
                Case BinaryOperatorKind.And,
                    BinaryOperatorKind.AndAlso,
                    BinaryOperatorKind.Multiply
 
                    ' if one of operands is trivial, evaluate the other and return the trivial one
                    If left.IsDefaultValueConstant Then
                        Return MakeSequence(right, left)
                    End If
 
                    If right.IsDefaultValueConstant Then
                        Return MakeSequence(left, right)
                    End If
 
                    ' if one of operands is True, return the other one
                    If left.IsTrueConstant Then
                        Return right
                    End If
 
                    If right.IsTrueConstant Then
                        Return left
                    End If
 
                Case BinaryOperatorKind.Equals
                    If left.IsTrueConstant Then
                        Return right
                    End If
 
                    If right.IsTrueConstant Then
                        Return left
                    End If
 
                Case BinaryOperatorKind.NotEquals
                    If left.IsFalseConstant Then
                        Return right
                    End If
 
                    If right.IsFalseConstant Then
                        Return left
                    End If
            End Select
 
            Return TransformRewrittenBinaryOperator(New BoundBinaryOperator(syntax, binaryOpKind, left, right, isChecked, resultType))
        End Function
 
        ''' <summary>
        ''' Simpler helper for binary expressions.
        ''' When operand are boolean, the result type is same as operand's and is never checked 
        ''' so do not need to pass that in.
        ''' </summary>
        Private Function MakeBooleanBinaryExpression(syntax As SyntaxNode,
                                    binaryOpKind As BinaryOperatorKind,
                                    left As BoundExpression,
                                    right As BoundExpression) As BoundExpression
 
            Debug.Assert(TypeSymbol.Equals(left.Type, right.Type, TypeCompareKind.ConsiderEverything))
            Debug.Assert(left.Type.IsBooleanType)
 
            Return MakeBinaryExpression(syntax, binaryOpKind, left, right, False, left.Type)
        End Function
 
        Private Shared Function MakeNullLiteral(syntax As SyntaxNode, type As TypeSymbol) As BoundLiteral
            Return New BoundLiteral(syntax, ConstantValue.Nothing, type)
        End Function
 
        ''' <summary>
        ''' Takes two expressions and makes sequence.
        ''' </summary>
        Private Shared Function MakeSequence(first As BoundExpression, second As BoundExpression) As BoundExpression
            Return MakeSequence(second.Syntax, first, second)
        End Function
 
        ''' <summary>
        ''' Takes two expressions and makes sequence.
        ''' </summary>
        Private Shared Function MakeSequence(syntax As SyntaxNode,
                                             first As BoundExpression,
                                             second As BoundExpression) As BoundExpression
 
            Dim sideeffects = GetSideeffects(first)
            If sideeffects Is Nothing Then
                Return second
            End If
 
            Return New BoundSequence(syntax,
                                     ImmutableArray(Of LocalSymbol).Empty,
                                     ImmutableArray.Create(sideeffects),
                                     second,
                                     second.Type)
        End Function
 
        ''' <summary>
        ''' Takes two expressions and makes sequence.
        ''' </summary>
        Private Function MakeTernaryConditionalExpression(syntax As SyntaxNode,
                                                          condition As BoundExpression,
                                                          whenTrue As BoundExpression,
                                                          whenFalse As BoundExpression) As BoundExpression
 
            Debug.Assert(condition.Type.IsBooleanType, "ternary condition must be boolean")
            Debug.Assert(whenTrue.Type.IsSameTypeIgnoringAll(whenFalse.Type), "ternary branches must have same types")
 
            Dim ifConditionConst = condition.ConstantValueOpt
            If ifConditionConst IsNot Nothing Then
                Return MakeSequence(syntax, condition, If(ifConditionConst Is ConstantValue.True, whenTrue, whenFalse))
            End If
 
            Return TransformRewrittenTernaryConditionalExpression(New BoundTernaryConditionalExpression(syntax, condition, whenTrue, whenFalse, Nothing, whenTrue.Type))
        End Function
 
        ''' <summary>
        ''' Returns an expression that can be used instead of the original one when
        ''' we want to run the expression for side-effects only (i.e. we intend to ignore result).
        ''' </summary>
        Private Shared Function GetSideeffects(operand As BoundExpression) As BoundExpression
            If operand.IsConstant Then
                Return Nothing
            End If
 
            Select Case operand.Kind
                Case BoundKind.Local,
                    BoundKind.Parameter
 
                    Return Nothing
 
                Case BoundKind.ObjectCreationExpression
                    If operand.Type.IsNullableType Then
                        Dim objCreation = DirectCast(operand, BoundObjectCreationExpression)
                        Dim args = objCreation.Arguments
                        If args.Length = 0 Then
                            Return Nothing
                        Else
                            Return GetSideeffects(args(0))
                        End If
                    End If
            End Select
 
            Return operand
        End Function
    End Class
End Namespace