File: Lowering\WithExpressionRewriter.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.Collections
Imports Microsoft.CodeAnalysis.PooledObjects
Imports Microsoft.CodeAnalysis.Text
Imports Microsoft.CodeAnalysis.VisualBasic.Symbols
Imports Microsoft.CodeAnalysis.VisualBasic.Syntax
Imports TypeKind = Microsoft.CodeAnalysis.TypeKind
 
Namespace Microsoft.CodeAnalysis.VisualBasic
 
    Friend NotInheritable Class WithExpressionRewriter
        Private ReadOnly _withSyntax As WithStatementSyntax
 
        Public Structure Result
            Public Sub New(expression As BoundExpression, locals As ImmutableArray(Of LocalSymbol), initializers As ImmutableArray(Of BoundExpression), capturedLvalueByRefCallOrProperty As BoundExpression)
                Me.Expression = expression
                Me.Locals = locals
                Me.Initializers = initializers
                Me.CapturedLvalueByRefCallOrProperty = capturedLvalueByRefCallOrProperty
            End Sub
 
            ''' <summary> Expression to be used instead of With statement expression placeholder </summary>
            Public ReadOnly Expression As BoundExpression
 
            ''' <summary> Locals being used </summary>
            Public ReadOnly Locals As ImmutableArray(Of LocalSymbol)
 
            ''' <summary> Locals initialization expressions </summary>
            Public ReadOnly Initializers As ImmutableArray(Of BoundExpression)
 
            Public ReadOnly CapturedLvalueByRefCallOrProperty As BoundExpression
        End Structure
 
        Friend Sub New(withSyntax As WithStatementSyntax)
            Me._withSyntax = withSyntax
        End Sub
 
#Region "State"
 
        Private Class State
            Public ReadOnly ContainingMember As Symbol
            Public ReadOnly DoNotUseByRefLocal As Boolean
            Public ReadOnly Binder As Binder
            Public ReadOnly PreserveIdentityOfLValues As Boolean
            Public ReadOnly IsDraftRewrite As Boolean
 
            Private _locals As ArrayBuilder(Of LocalSymbol) = Nothing
            Private _initializers As ArrayBuilder(Of BoundExpression) = Nothing
            Public _capturedLvalueByRefCallOrProperty As BoundExpression = Nothing
 
            Public Sub New(containingMember As Symbol, doNotUseByRefLocal As Boolean, binder As Binder, preserveIdentityOfLValues As Boolean, isDraftRewrite As Boolean)
                Me.ContainingMember = containingMember
                Me.DoNotUseByRefLocal = doNotUseByRefLocal
                Me.Binder = binder
                Me.PreserveIdentityOfLValues = preserveIdentityOfLValues
                Me.IsDraftRewrite = isDraftRewrite
            End Sub
 
            Public Sub AddLocal(local As LocalSymbol, initializer As BoundExpression)
                Debug.Assert(local IsNot Nothing)
                Debug.Assert(initializer IsNot Nothing)
 
                If Me._locals Is Nothing Then
                    Debug.Assert(Me._initializers Is Nothing)
 
                    Me._locals = ArrayBuilder(Of LocalSymbol).GetInstance()
                    Me._initializers = ArrayBuilder(Of BoundExpression).GetInstance()
                End If
 
                Me._locals.Add(local)
                Me._initializers.Add(initializer)
            End Sub
 
            Public Function CreateResult(expression As BoundExpression) As Result
                Return New Result(expression,
                                  If(Me._locals Is Nothing, ImmutableArray(Of LocalSymbol).Empty, Me._locals.ToImmutableAndFree()),
                                  If(Me._initializers Is Nothing, ImmutableArray(Of BoundExpression).Empty, Me._initializers.ToImmutableAndFree()),
                                  Me._capturedLvalueByRefCallOrProperty)
 
            End Function
        End Class
 
#End Region
 
#Region "Implementation"
 
        Private Function CaptureInATemp(value As BoundExpression, state As State) As BoundLocal
            Dim type As TypeSymbol = value.Type
            Debug.Assert(type IsNot Nothing AndAlso Not type.IsVoidType())
 
            Dim local As New SynthesizedLocal(state.ContainingMember, type, SynthesizedLocalKind.With, _withSyntax)
 
            Dim boundLocal = New BoundLocal(value.Syntax, local, isLValue:=True, type:=type).MakeCompilerGenerated()
 
            If value.IsLValue Then
                ' Region analysis depends on node identity, let's preserve it by using a wrapper.
                If state.PreserveIdentityOfLValues Then
                    value = New BoundLValueToRValueWrapper(value.Syntax, value, value.Type).MakeCompilerGenerated()
                Else
                    value = value.MakeRValue()
                End If
            End If
            state.AddLocal(local,
                            New BoundAssignmentOperator(
                                value.Syntax, boundLocal, value, suppressObjectClone:=True, type:=type).MakeCompilerGenerated())
 
            Return boundLocal
        End Function
 
        Private Function CaptureInAByRefTemp(value As BoundExpression, state As State) As BoundExpression
            Debug.Assert(value.IsLValue())
 
            Dim type As TypeSymbol = value.Type
            Debug.Assert(type IsNot Nothing AndAlso Not type.IsVoidType())
 
            Dim local As New SynthesizedLocal(state.ContainingMember, type, SynthesizedLocalKind.With, _withSyntax, isByRef:=True)
 
            Dim boundLocal = New BoundLocal(value.Syntax, local, isLValue:=True, type:=type).MakeCompilerGenerated()
 
            state.AddLocal(local,
                            New BoundReferenceAssignment(
                                value.Syntax, boundLocal, value, True, type:=type).MakeCompilerGenerated())
 
            Return boundLocal
        End Function
 
        Private Function CaptureArrayAccess(value As BoundArrayAccess, state As State) As BoundExpression
            Debug.Assert(value.IsLValue)
 
            Dim boundArrayTemp As BoundExpression = CaptureInATemp(value.Expression, state).MakeRValue()
 
            Dim n = value.Indices.Length
            Dim indices(n - 1) As BoundExpression
            For i = 0 To n - 1
                indices(i) = CaptureRValue(value.Indices(i), state)
            Next
 
            Return value.Update(boundArrayTemp, indices.AsImmutableOrNull(), value.IsLValue, value.Type)
        End Function
 
        Private Function CaptureRValue(value As BoundExpression, state As State) As BoundExpression
            Dim kind As BoundKind = value.Kind
 
            Select Case kind
                Case BoundKind.BadVariable,
                     BoundKind.Literal,
                     BoundKind.MeReference,
                     BoundKind.MyClassReference,
                     BoundKind.MyBaseReference
 
                    Return value
            End Select
 
            If value.IsValue AndAlso value.Type IsNot Nothing AndAlso Not value.Type.IsVoidType() Then
 
                Debug.Assert(Not value.IsLValue)
 
                Dim constantValue As ConstantValue = value.ConstantValueOpt
 
                If constantValue IsNot Nothing Then
                    Debug.Assert(value.Kind <> BoundKind.Literal)
                    Return value
                End If
 
                ' TODO: Might need to do some optimization for compiler generated locals.
                '       For example, no reason to recapture a local that is already a capture.
                Return CaptureInATemp(value, state).MakeRValue()
            End If
 
            Throw ExceptionUtilities.Unreachable
        End Function
 
        Private Function CaptureFieldAccess(value As BoundFieldAccess, state As State) As BoundExpression
            Debug.Assert(value.IsLValue)
 
            Dim fieldSymbol = value.FieldSymbol
            If fieldSymbol.IsShared AndAlso value.ReceiverOpt IsNot Nothing Then
                Return value.Update(Nothing, fieldSymbol, value.IsLValue, value.SuppressVirtualCalls, value.ConstantsInProgressOpt, value.Type)
 
            ElseIf value.ReceiverOpt Is Nothing Then
                Return value
 
            Else
                Dim receiver As BoundExpression = CaptureReceiver(value.ReceiverOpt, state)
                Return value.Update(receiver, fieldSymbol, value.IsLValue, value.SuppressVirtualCalls, value.ConstantsInProgressOpt, value.Type)
            End If
 
        End Function
 
        Private Function CaptureReceiver(value As BoundExpression, state As State) As BoundExpression
            Debug.Assert(value IsNot Nothing)
 
            If value.IsLValue AndAlso value.Type.IsReferenceType Then
                Return CaptureInATemp(value, state)
            Else
                Return CaptureExpression(value, state)
            End If
        End Function
 
        Private Function CaptureExpression(value As BoundExpression, state As State) As BoundExpression
            If Not value.IsLValue Then
                Return CaptureRValue(value, state)
            End If
 
            Select Case value.Kind
                Case BoundKind.ArrayAccess
                    Return CaptureArrayAccess(DirectCast(value, BoundArrayAccess), state)
 
                Case BoundKind.FieldAccess
                    Return CaptureFieldAccess(DirectCast(value, BoundFieldAccess), state)
 
                Case BoundKind.Local,
                     BoundKind.Parameter
                    Return value
 
                Case BoundKind.WithLValueExpressionPlaceholder
                    ' NOTE: this may only happen in case of calling this rewriter from initial 
                    '       binding, in lowering phase all placeholders must already be substituted
 
                    ' We need to replace the placeholder with 'draft substitute' to make sure
                    ' we properly analyze it in flow analysis. This is important, for example, 
                    ' if we pass the original value typed local to substitute like in the following
                    ' example:
                    '
                    '       Dim s As New StructureType(...)
                    '       With s
                    '           .Field1 = 1
                    '           ...
                    '
                    ' Note that we cannot just replace the placeholder with the 'original' expression
                    ' because we should predict and use the expression which will actually be used 
                    ' in lowering, for example in the following scenario:
                    '
                    '       With {expr}
                    '           With .Field
                    '               ...
                    '
                    ' {expr} is of reference type we don't want to replace the placeholder with the 
                    ' outer expression, because otherwise it will get to initializers of the nested 
                    ' With statement and will take part in flow analysis
                    '
                    ' Try and get the substitute from the binder, or leave placeholder 'as-is' to 
                    ' be replaced with proper substitute by flow analysis code when needed
                    Dim substitute As BoundExpression =
                        state.Binder.GetWithStatementPlaceholderSubstitute(DirectCast(value, BoundValuePlaceholderBase))
                    Return If(substitute IsNot Nothing, CaptureExpression(substitute, state), value)
 
                Case Else
                    Return CaptureRValue(value, state)
            End Select
        End Function
 
#End Region
 
        ''' <summary>
        ''' Given an expression specified for With statement produces:
        '''   1) Expression - an expression to be used instead of expression placeholder
        '''   2) Locals - a set of locals used to capture parts of Expression
        '''   3) Initializers - initializers for Locals
        ''' 
        ''' To be used in With statement only!
        ''' </summary>
        Public Function AnalyzeWithExpression(
            containingMember As Symbol,
            value As BoundExpression,
            doNotUseByRefLocal As Boolean,
            isDraftRewrite As Boolean,
            binder As Binder,
            Optional preserveIdentityOfLValues As Boolean = False
        ) As Result
            Dim state As New State(containingMember, doNotUseByRefLocal, binder, preserveIdentityOfLValues, isDraftRewrite)
            Return state.CreateResult(CaptureWithExpression(value, state))
        End Function
 
        Private Function CaptureWithExpression(value As BoundExpression, state As State) As BoundExpression
            Dim type As TypeSymbol = value.Type
            Debug.Assert(type IsNot Nothing)
 
            Dim kind = value.Kind
 
            If kind = BoundKind.MeReference OrElse kind = BoundKind.MyClassReference OrElse kind = BoundKind.MyBaseReference Then
                ' Me reference can simply be reused instead of placeholder
                ' NOTE: MyClass & MyBase references may only get here in erroneous scenarios
                Return value
            End If
 
            If type.IsReferenceType Then
                ' Expressions of reference type are to be captured using a simple local
                Dim result As BoundLocal = CaptureInATemp(value, state)
 
                If Not value.IsLValue Then
                    result = result.MakeRValue()
                End If
 
                Return result
            End If
 
            ' NOTE: Only structures and type parameters should reach this point
            Debug.Assert(value.Type.IsStructureType OrElse value.Type.IsTypeParameter)
 
            If Not value.IsLValue() Then
                ' All R-value value typed and type parameter typed 
                ' expressions should be captured in simple locals
                Return CaptureInATemp(value, state).MakeRValue()
            End If
 
            ' NOTE: Only L-value expressions of value type or type parameter type
 
            If kind = BoundKind.Local OrElse kind = BoundKind.Parameter Then
                ' Locals and parameters of value or type parameter type can 
                ' simply be reused instead of placeholder
                Debug.Assert(type.IsValueType OrElse type.IsTypeParameter)
                Return value
            End If
 
            ' If the expression is to be captured in lambda do not capture in a ref local.
            ' If the expression is a generic array element, getting a writable reference may fail
            ' and readonly reference cannot be stored in a temp, so do not capture in a ref.
            If Not (state.DoNotUseByRefLocal OrElse (value.Kind = BoundKind.ArrayAccess AndAlso value.Type.Kind = SymbolKind.TypeParameter)) Then
                Return CaptureInAByRefTemp(value, state)
            End If
 
            ' Otherwise, we need to capture parts of the expression in a set of non-ByRef locals 
            Dim expression As BoundExpression = Nothing
            Select Case value.Kind
                Case BoundKind.ArrayAccess
                    expression = CaptureArrayAccess(DirectCast(value, BoundArrayAccess), state)
 
                Case BoundKind.FieldAccess
                    expression = CaptureFieldAccess(DirectCast(value, BoundFieldAccess), state)
 
                Case BoundKind.PropertyAccess
                    If Not state.IsDraftRewrite OrElse Not DirectCast(value, BoundPropertyAccess).PropertySymbol.ReturnsByRef Then
                        Throw ExceptionUtilities.UnexpectedValue(value.Kind)
                    End If
 
                    Debug.Assert(state.DoNotUseByRefLocal)
                    If state._capturedLvalueByRefCallOrProperty Is Nothing Then
                        state._capturedLvalueByRefCallOrProperty = value
                    End If
 
                    expression = CaptureInATemp(value, state) ' Capture by value for the purpose of draft rewrite
 
                Case BoundKind.Call
                    If Not state.IsDraftRewrite OrElse Not DirectCast(value, BoundCall).Method.ReturnsByRef Then
                        Throw ExceptionUtilities.UnexpectedValue(value.Kind)
                    End If
 
                    Debug.Assert(state.DoNotUseByRefLocal)
                    If state._capturedLvalueByRefCallOrProperty Is Nothing Then
                        state._capturedLvalueByRefCallOrProperty = value
                    End If
 
                    expression = CaptureInATemp(value, state) ' Capture by value for the purpose of draft rewrite
 
                Case Else
                    Throw ExceptionUtilities.UnexpectedValue(value.Kind)
            End Select
 
            ' LValue-ness of expressions must be preserved
            Debug.Assert(expression IsNot Nothing)
            Debug.Assert(expression.IsLValue)
            Return expression
        End Function
 
    End Class
 
End Namespace