File: Lowering\LocalRewriter\LocalRewriter_AssignmentOperator.vb
Web Access
Project: src\src\roslyn\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.Diagnostics
Imports System.Runtime.InteropServices
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 NotInheritable Class LocalRewriter
        Public Overrides Function VisitAssignmentOperator(node As BoundAssignmentOperator) As BoundNode
            Dim nodeLeft = node.Left

            If nodeLeft.IsLateBound Then
                Return RewriteLateBoundAssignment(node)
            End If

            If node.Right.Kind = BoundKind.MidResult Then
                ' This is a case of Mid assignment where the target was a String, no additional conversion to/from string is needed.
#If DEBUG Then
                Dim midResult = DirectCast(node.Right, BoundMidResult)
                Debug.Assert(midResult.Original Is node.LeftOnTheRightOpt OrElse
                             (midResult.Original.Kind = BoundKind.Parenthesized AndAlso DirectCast(midResult.Original, BoundParenthesized).Expression Is node.LeftOnTheRightOpt))
#End If

                If nodeLeft.IsLValue Then
                    ' Trivial case - a simple call
                    Return RewriteTrivialMidAssignment(node)
                End If
            End If

            Dim setNode = If(IsPropertyAssignment(node), nodeLeft, Nothing)

#If DEBUG Then
            If setNode IsNot Nothing Then
                Dim accessKind = setNode.GetAccessKind()
                Debug.Assert((accessKind And PropertyAccessKind.Set) <> 0)
                Debug.Assert(((accessKind And PropertyAccessKind.Get) = 0) = (node.LeftOnTheRightOpt Is Nothing))
            End If
#End If

            If setNode Is Nothing AndAlso node.LeftOnTheRightOpt Is Nothing Then
                Return Me.VisitAssignmentOperatorSimple(node)
            End If

            Debug.Assert(nodeLeft.Kind <> BoundKind.FieldAccess OrElse
                         Not nodeLeft.IsConstant OrElse
                         Not DirectCast(nodeLeft, BoundFieldAccess).FieldSymbol.IsConstButNotMetadataConstant)

            Dim temps = ImmutableArray(Of SynthesizedLocal).Empty
            Dim assignmentTarget As BoundExpression

            If node.LeftOnTheRightOpt IsNot Nothing Then
                ' Make sure side effects are evaluated only once.
                If setNode IsNot Nothing Then
                    assignmentTarget = setNode.SetAccessKind(PropertyAccessKind.Unknown)
                Else
                    assignmentTarget = nodeLeft
                End If

                Dim temporaries = ArrayBuilder(Of SynthesizedLocal).GetInstance()
                Dim useTwice As UseTwiceRewriter.Result = UseTwiceRewriter.UseTwice(Me._currentMethodOrLambda, assignmentTarget, isForRegularCompoundAssignment:=True, temporaries)
                temps = temporaries.ToImmutableAndFree()

                Dim leftOnTheRight As BoundExpression

                If setNode IsNot Nothing Then
                    setNode = useTwice.First.SetAccessKind(PropertyAccessKind.Set)
                    assignmentTarget = setNode
                    leftOnTheRight = useTwice.Second.SetAccessKind(PropertyAccessKind.Get)
                Else
                    assignmentTarget = useTwice.First
                    Debug.Assert(assignmentTarget.IsLValue)
                    leftOnTheRight = useTwice.Second.MakeRValue()
                End If

                AddPlaceholderReplacement(node.LeftOnTheRightOpt, VisitExpressionNode(leftOnTheRight))
            Else
                assignmentTarget = nodeLeft
            End If

            Dim result As BoundExpression

            If setNode IsNot Nothing Then
                ' Rewrite property assignment into call to setter.
                Debug.Assert(assignmentTarget Is setNode)
                result = RewritePropertyAssignmentAsSetCall(node, setNode)
            Else
                result = node.Update(VisitExpressionNode(assignmentTarget),
                                     Nothing,
                                     VisitAndGenerateObjectCloneIfNeeded(node.Right, node.SuppressObjectClone),
                                     True,
                                     node.Type)
            End If

            If temps.Length > 0 Then
                If result.Type.IsVoidType() Then
                    result = New BoundSequence(node.Syntax,
                                               StaticCast(Of LocalSymbol).From(temps),
                                               ImmutableArray.Create(Of BoundExpression)(result),
                                               Nothing,
                                               result.Type)
                Else
                    result = New BoundSequence(node.Syntax,
                                               StaticCast(Of LocalSymbol).From(temps),
                                               ImmutableArray(Of BoundExpression).Empty,
                                               result,
                                               result.Type)
                End If
            End If

            If node.LeftOnTheRightOpt IsNot Nothing Then
                RemovePlaceholderReplacement(node.LeftOnTheRightOpt)
            End If

            Return result
        End Function

        Private Shared Function IsPropertyAssignment(node As BoundAssignmentOperator) As Boolean
            Select Case node.Left.Kind
                Case BoundKind.PropertyAccess
                    Dim propertyAccess = DirectCast(node.Left, BoundPropertyAccess)
                    Return Not propertyAccess.PropertySymbol.ReturnsByRef
                Case BoundKind.XmlMemberAccess
                    Return True
                Case Else
                    Return False
            End Select
        End Function

        ''' <summary>
        ''' Make sure GetObjectValue calls are injected.
        ''' </summary>
        Private Function VisitAssignmentOperatorSimple(node As BoundAssignmentOperator) As BoundExpression
            Debug.Assert(node.LeftOnTheRightOpt Is Nothing)

            Return node.Update(VisitAssignmentLeftExpression(node), Nothing, VisitAndGenerateObjectCloneIfNeeded(node.Right, node.SuppressObjectClone), True, node.Type)
        End Function

        Private Function VisitAssignmentLeftExpression(node As BoundAssignmentOperator) As BoundExpression
            Dim leftNode = node.Left

            ' If the lhs of this assignment operator is a field access, it should not be 
            ' rewritten even if it's const. If you do that, it will create ObjectCreationExpressions 
            ' for Dates and Decimals which are not allowed there.
            If leftNode.Kind = BoundKind.FieldAccess Then
                Dim leftFieldAccess = DirectCast(leftNode, BoundFieldAccess)
                If leftFieldAccess.IsConstant Then
#If DEBUG Then
                    Debug.Assert(Not _rewrittenNodes.Contains(node), "LocalRewriter: Rewriting the same node several times.")
                    Dim originalNode = node
#End If

                    Dim result = DirectCast(MyBase.VisitFieldAccess(leftFieldAccess), BoundExpression)

#If DEBUG Then
                    If result Is originalNode Then
                        result = result.MemberwiseClone(Of BoundExpression)()
                    End If
                    _rewrittenNodes.Add(result)
#End If
                    Return result
                End If
            End If

            Return Me.VisitExpressionNode(leftNode)
        End Function

        Private Function RewritePropertyAssignmentAsSetCall(node As BoundAssignmentOperator, setNode As BoundExpression) As BoundExpression
            Select Case setNode.Kind
                Case BoundKind.XmlMemberAccess
                    Return RewritePropertyAssignmentAsSetCall(node, DirectCast(setNode, BoundXmlMemberAccess).MemberAccess)

                Case BoundKind.PropertyAccess
                    Return RewritePropertyAssignmentAsSetCall(node, DirectCast(setNode, BoundPropertyAccess))

                Case Else
                    Throw ExceptionUtilities.UnexpectedValue(setNode.Kind)

            End Select
        End Function

        <Conditional("DEBUG")>
        Private Shared Sub AssertIsWriteableFromMember(node As BoundPropertyAccess, fromMember As Symbol)
            Dim receiver = node.ReceiverOpt

            Dim sourceProperty As SourcePropertySymbol = DirectCast(node.PropertySymbol, SourcePropertySymbol)
            Dim propertyIsStatic As Boolean = node.PropertySymbol.IsShared

            Debug.Assert(
                sourceProperty IsNot Nothing AndAlso
                sourceProperty.IsAutoProperty AndAlso
                TypeSymbol.Equals(sourceProperty.ContainingType, fromMember.ContainingType, TypeCompareKind.ConsiderEverything) AndAlso
                propertyIsStatic = fromMember.IsShared AndAlso
                (propertyIsStatic OrElse receiver.Kind = BoundKind.MeReference) AndAlso
                (fromMember.Kind = SymbolKind.Field OrElse (fromMember.Kind = SymbolKind.Method AndAlso
                                                            DirectCast(fromMember, MethodSymbol).IsAnyConstructor)))

        End Sub

        Private Function RewritePropertyAssignmentAsSetCall(node As BoundAssignmentOperator, setNode As BoundPropertyAccess) As BoundExpression
            Debug.Assert(setNode.AccessKind = PropertyAccessKind.Set)

            Dim [property] = setNode.PropertySymbol
            Dim setMethod = [property].GetMostDerivedSetMethod()

            If setMethod Is Nothing Then
                AssertIsWriteableFromMember(setNode, Me._currentMethodOrLambda)

                Dim backingField = [property].AssociatedField
                Debug.Assert(backingField IsNot Nothing, "autoproperty must have a backing field")

                Dim rewrittenReceiver = VisitExpressionNode(setNode.ReceiverOpt)
                Dim field = New BoundFieldAccess(setNode.Syntax,
                                                 rewrittenReceiver,
                                                 backingField,
                                                 isLValue:=True,
                                                 type:=backingField.Type)

                Dim rewrittenValue = VisitExpression(node.Right)

                Return New BoundAssignmentOperator(node.Syntax,
                                                   field,
                                                   rewrittenValue,
                                                   node.SuppressObjectClone,
                                                   node.Type)

            Else
                ' GenerateAccessorCall rewrites the arguments
                Return RewriteReceiverArgumentsAndGenerateAccessorCall(node.Syntax,
                                  setMethod,
                                  setNode.ReceiverOpt,
                                  setNode.Arguments.Concat(node.Right),
                                  node.ConstantValueOpt,
                                  isLValue:=False,
                                  suppressObjectClone:=False,
                                  type:=setMethod.ReturnType)
            End If

        End Function

        Private Function RewriteLateBoundAssignment(node As BoundAssignmentOperator) As BoundNode

            Dim assignmentTarget As BoundExpression = node.Left

#If DEBUG Then
            Dim accessKind As LateBoundAccessKind = node.Left.GetLateBoundAccessKind()
            Debug.Assert((accessKind And LateBoundAccessKind.Set) <> 0)
            Debug.Assert(((accessKind And LateBoundAccessKind.Get) = 0) = (node.LeftOnTheRightOpt Is Nothing))
#End If

            Dim temps = ImmutableArray(Of SynthesizedLocal).Empty

            If node.LeftOnTheRightOpt IsNot Nothing Then
                ' Make sure side effects are evaluated only once.
                assignmentTarget = assignmentTarget.SetLateBoundAccessKind(LateBoundAccessKind.Unknown)

                Dim temporaries = ArrayBuilder(Of SynthesizedLocal).GetInstance()
                Dim useTwice As UseTwiceRewriter.Result = UseTwiceRewriter.UseTwice(Me._currentMethodOrLambda, assignmentTarget, isForRegularCompoundAssignment:=False, temporaries)
                temps = temporaries.ToImmutableAndFree()

                Dim leftOnTheRight As BoundExpression

                assignmentTarget = useTwice.First.SetLateBoundAccessKind(LateBoundAccessKind.Set)
                leftOnTheRight = useTwice.Second.SetLateBoundAccessKind(LateBoundAccessKind.Get)

                AddPlaceholderReplacement(node.LeftOnTheRightOpt, VisitExpressionNode(leftOnTheRight))
            End If

            Dim value As BoundExpression = VisitExpressionNode(node.Right)

            If node.LeftOnTheRightOpt IsNot Nothing Then
                RemovePlaceholderReplacement(node.LeftOnTheRightOpt)
            End If

            Dim result As BoundExpression

            If assignmentTarget.Kind = BoundKind.LateMemberAccess Then
                ' objExpr.goo = bar
                result = LateSet(node.Syntax,
                                 DirectCast(MyBase.VisitLateMemberAccess(DirectCast(assignmentTarget, BoundLateMemberAccess)), BoundLateMemberAccess),
                                 value,
                                 Nothing,
                                 Nothing,
                                 isCopyBack:=False)
            Else
                Dim invocation = DirectCast(assignmentTarget, BoundLateInvocation)

                If invocation.Member.Kind = BoundKind.LateMemberAccess Then
                    ' objExpr.goo(args) = bar
                    result = LateSet(node.Syntax,
                                     DirectCast(MyBase.VisitLateMemberAccess(DirectCast(invocation.Member, BoundLateMemberAccess)), BoundLateMemberAccess),
                                     value,
                                     VisitList(invocation.ArgumentsOpt),
                                     invocation.ArgumentNamesOpt,
                                     isCopyBack:=False)
                Else
                    ' objExpr(args) = bar
                    invocation = invocation.Update(VisitExpressionNode(invocation.Member),
                                                   VisitList(invocation.ArgumentsOpt),
                                                   invocation.ArgumentNamesOpt,
                                                   invocation.AccessKind,
                                                   invocation.MethodOrPropertyGroupOpt,
                                                   invocation.Type)

                    result = LateIndexSet(node.Syntax,
                                          invocation,
                                          value,
                                          isCopyBack:=False)
                End If
            End If

            If temps.Length > 0 Then
                result = New BoundSequence(node.Syntax,
                                           StaticCast(Of LocalSymbol).From(temps),
                                           ImmutableArray.Create(Of BoundExpression)(result),
                                           Nothing,
                                           result.Type)
            End If

            Return result
        End Function

        Private Function VisitAndGenerateObjectCloneIfNeeded(right As BoundExpression, Optional suppressObjectClone As Boolean = False) As BoundExpression
            Return If(suppressObjectClone OrElse right.HasErrors(), VisitExpression(right), GenerateObjectCloneIfNeeded(right, VisitExpression(right)))
        End Function

        ''' <summary>
        ''' Apply GetObjectValue call if needed.
        ''' </summary>
        Private Function GenerateObjectCloneIfNeeded(generatedExpression As BoundExpression) As BoundExpression
            Return GenerateObjectCloneIfNeeded(generatedExpression, generatedExpression)
        End Function

        ''' <summary>
        ''' Apply GetObjectValue call if needed.
        ''' </summary>
        Private Function GenerateObjectCloneIfNeeded(expression As BoundExpression, rewrittenExpression As BoundExpression) As BoundExpression
            If expression.HasErrors OrElse rewrittenExpression.HasErrors OrElse Me._inExpressionLambda Then
                Return rewrittenExpression
            End If

            Dim result As BoundExpression = rewrittenExpression

            If Not result.HasErrors AndAlso result.Type.IsObjectType() AndAlso Not Me.ContainingAssembly.IsVbRuntime Then

                ' There are a series of object operations which we know don't require a call to GetObjectValue.
                ' These operations are math and logic operators.
                Dim nodeToCheck As BoundExpression = expression

                Do
                    If nodeToCheck.IsConstant Then
                        Debug.Assert(nodeToCheck.ConstantValueOpt.IsNothing)
                        Return result
                    End If

                    Select Case nodeToCheck.Kind
                        Case BoundKind.BinaryOperator

                            Dim binaryOperator = DirectCast(nodeToCheck, BoundBinaryOperator)

                            If (binaryOperator.OperatorKind And BinaryOperatorKind.UserDefined) = 0 Then
                                Select Case (binaryOperator.OperatorKind And BinaryOperatorKind.OpMask)
                                    Case BinaryOperatorKind.Power,
                                         BinaryOperatorKind.Divide,
                                         BinaryOperatorKind.Modulo,
                                         BinaryOperatorKind.IntegerDivide,
                                         BinaryOperatorKind.Concatenate,
                                         BinaryOperatorKind.And,
                                         BinaryOperatorKind.AndAlso,
                                         BinaryOperatorKind.Or,
                                         BinaryOperatorKind.OrElse,
                                         BinaryOperatorKind.Xor,
                                         BinaryOperatorKind.Multiply,
                                         BinaryOperatorKind.Add,
                                         BinaryOperatorKind.Subtract,
                                         BinaryOperatorKind.LeftShift,
                                         BinaryOperatorKind.RightShift

                                        Return result
                                End Select
                            End If

                            Exit Do

                        Case BoundKind.UnaryOperator

                            Dim unaryOperator = DirectCast(nodeToCheck, BoundUnaryOperator)

                            If (unaryOperator.OperatorKind And UnaryOperatorKind.UserDefined) = 0 Then
                                Select Case (unaryOperator.OperatorKind And UnaryOperatorKind.IntrinsicOpMask)
                                    Case UnaryOperatorKind.Minus,
                                         UnaryOperatorKind.Plus,
                                         UnaryOperatorKind.Not

                                        Return result
                                End Select
                            End If

                            Exit Do

                        Case BoundKind.DirectCast,
                             BoundKind.TryCast,
                             BoundKind.Conversion

                            Dim conversionKind As ConversionKind

                            If nodeToCheck.Kind = BoundKind.DirectCast Then
                                Dim conversion = DirectCast(nodeToCheck, BoundDirectCast)
                                conversionKind = conversion.ConversionKind
                                nodeToCheck = conversion.Operand
                            ElseIf nodeToCheck.Kind = BoundKind.TryCast Then
                                Dim conversion = DirectCast(nodeToCheck, BoundTryCast)
                                conversionKind = conversion.ConversionKind
                                nodeToCheck = conversion.Operand
                            Else
                                Dim conversion = DirectCast(nodeToCheck, BoundConversion)
                                conversionKind = conversion.ConversionKind
                                nodeToCheck = conversion.Operand
                            End If

                            Debug.Assert((conversionKind And ConversionKind.UserDefined) = 0)

                            ' there are cases where there's an explicit cast in code, that may be an identity conversion and 
                            ' it should still get ignored in order to create a call to the GetObjectValue helper.
                            ' e.g. happens in the conversion of the get method of the current property in a for each loop.
                            If Not Conversions.IsIdentityConversion(conversionKind) Then
                                Return result
                            End If

                        Case BoundKind.Parenthesized
                            nodeToCheck = DirectCast(nodeToCheck, BoundParenthesized).Expression

                        Case BoundKind.XmlEmbeddedExpression
                            nodeToCheck = DirectCast(nodeToCheck, BoundXmlEmbeddedExpression).Expression

                        Case Else
                            Exit Do
                    End Select
                Loop

                Const getObjectValue As WellKnownMember = WellKnownMember.System_Runtime_CompilerServices_RuntimeHelpers__GetObjectValueObject
                Dim getObjectValueMethod = DirectCast(Compilation.GetWellKnownTypeMember(getObjectValue), MethodSymbol)

                If Not ReportMissingOrBadRuntimeHelper(nodeToCheck, getObjectValue, getObjectValueMethod) Then
                    result = New BoundCall(expression.Syntax, getObjectValueMethod, Nothing, Nothing,
                                                      ImmutableArray.Create(result), Nothing, getObjectValueMethod.ReturnType)
                End If
            End If

            Return result
        End Function

        Private Function RewriteTrivialMidAssignment(node As BoundAssignmentOperator) As BoundExpression
            ' This is a case when target is an LValue (not a property, etc.) of type String.
            Dim midResult = DirectCast(node.Right, BoundMidResult)
            Debug.Assert(node.Left.IsLValue AndAlso node.LeftOnTheRightOpt IsNot Nothing AndAlso
                         (node.LeftOnTheRightOpt Is midResult.Original OrElse
                          (midResult.Original.Kind = BoundKind.Parenthesized AndAlso node.LeftOnTheRightOpt Is DirectCast(midResult.Original, BoundParenthesized).Expression)))
            Debug.Assert(midResult.Type.IsStringType())

            Dim memberSymbol As MethodSymbol = Nothing
            Const memberId As WellKnownMember = WellKnownMember.Microsoft_VisualBasic_CompilerServices_StringType__MidStmtStr
            memberSymbol = DirectCast(Compilation.GetWellKnownTypeMember(memberId), MethodSymbol)

            If ReportMissingOrBadRuntimeHelper(node, memberId, memberSymbol) Then
                Return midResult.Update(VisitExpressionNode(node.Left),
                                        VisitExpressionNode(midResult.Start),
                                        VisitExpressionNode(midResult.LengthOpt),
                                        VisitExpressionNode(midResult.Source),
                                        node.Type)
            End If

            Dim temporaries As ImmutableArray(Of SynthesizedLocal) = Nothing
            Dim copyBack As ImmutableArray(Of BoundExpression) = Nothing

            ' If the length is omitted, it is implicitly the full length of the string.
            ' This is encoded as the largest positive long value, which is greater than the
            ' maximum length of any string on a 32-bit platform.
            Dim midCall = New BoundCall(node.Syntax,
                                        memberSymbol,
                                        Nothing,
                                        Nothing,
                                        RewriteCallArguments(ImmutableArray.Create(node.Left, midResult.Start,
                                                              If(midResult.LengthOpt, New BoundLiteral(node.Syntax, ConstantValue.Create(&H7FFFFFFF), midResult.Start.Type)),
                                                              midResult.Source),
                                                             memberSymbol.Parameters,
                                                             temporaries,
                                                             copyBack,
                                                             suppressObjectClone:=False),
                                        Nothing,
                                        memberSymbol.ReturnType)

            Debug.Assert(temporaries.IsDefault)
            Debug.Assert(copyBack.IsDefault)

            Return midCall
        End Function

        Public Overrides Function VisitMidResult(node As BoundMidResult) As BoundNode
            ' This is a non-trivial case, either a conversion is involved or the target is a property, etc.
            ' Need to allocate a temp, store original string in it, pass it by ref to MidStmtStr and return its value after the call.
            ' We will achieve this by synthesizing and rewriting trivial Mid assignment with the temp as the target.

            Dim temp = New SynthesizedLocal(Me._currentMethodOrLambda, node.Type, SynthesizedLocalKind.LoweringTemp)
            Dim localRef = New BoundLocal(node.Syntax, temp, node.Type)
            Dim placeholder = New BoundCompoundAssignmentTargetPlaceholder(node.Syntax, node.Type)

            Return New BoundSequence(node.Syntax,
                                     ImmutableArray.Create(Of LocalSymbol)(temp),
                                     ImmutableArray.Create(Of BoundExpression)(New BoundAssignmentOperator(node.Syntax,
                                                                                                        localRef,
                                                                                                        VisitExpressionNode(node.Original),
                                                                                                        suppressObjectClone:=True),
                                                                            RewriteTrivialMidAssignment(New BoundAssignmentOperator(node.Syntax,
                                                                                                                                    localRef,
                                                                                                                                    placeholder,
                                                                                                                                    node.Update(placeholder,
                                                                                                                                                node.Start,
                                                                                                                                                node.LengthOpt,
                                                                                                                                                node.Source,
                                                                                                                                                node.Type),
                                                                                                                                    suppressObjectClone:=False))),
                                     localRef.MakeRValue(),
                                     node.Type)
        End Function
    End Class
End Namespace