File: Lowering\LocalRewriter\LocalRewriter_AssignmentOperator.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.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