File: Lowering\MethodToClassRewriter\MethodToClassRewriter.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.CodeGen
Imports Microsoft.CodeAnalysis.PooledObjects
Imports Microsoft.CodeAnalysis.VisualBasic.Symbols
Imports ReferenceEqualityComparer = Roslyn.Utilities.ReferenceEqualityComparer
 
Namespace Microsoft.CodeAnalysis.VisualBasic
 
    ''' <summary>
    ''' A bound node rewriter that rewrites types properly (which in some cases the automatically-generated).
    ''' This is used in the lambda rewriter, the iterator rewriter, and the async rewriter.
    ''' </summary>
    Partial Friend MustInherit Class MethodToClassRewriter(Of TProxy)
        Inherits BoundTreeRewriterWithStackGuard
 
        ''' <summary>
        ''' For each captured variable, the corresponding field of its frame
        ''' </summary>
        Protected ReadOnly Proxies As Dictionary(Of Symbol, TProxy) = New Dictionary(Of Symbol, TProxy)()
 
        ''' <summary>
        ''' A mapping from every local variable to its replacement local variable. Local variables
        ''' are replaced when their types change due to being inside of a lambda within a generic method.
        ''' </summary>
        Protected ReadOnly LocalMap As Dictionary(Of LocalSymbol, LocalSymbol) = New Dictionary(Of LocalSymbol, LocalSymbol)(ReferenceEqualityComparer.Instance)
 
        ''' <summary>
        ''' A mapping from every parameter to its replacement parameter. Local variables
        ''' are replaced when their types change due to being inside of a lambda.
        ''' </summary>
        Protected ReadOnly ParameterMap As Dictionary(Of ParameterSymbol, ParameterSymbol) = New Dictionary(Of ParameterSymbol, ParameterSymbol)(ReferenceEqualityComparer.Instance)
 
        Protected ReadOnly PlaceholderReplacementMap As New Dictionary(Of BoundValuePlaceholderBase, BoundExpression)
 
        ''' <summary>
        ''' The mapping of type parameters for the current lambda body
        ''' </summary>
        Protected MustOverride ReadOnly Property TypeMap As TypeSubstitution
 
        Friend MustOverride Function FramePointer(syntax As SyntaxNode, frameClass As NamedTypeSymbol) As BoundExpression
 
        ''' <summary>
        ''' The method (e.g. lambda) which is currently being rewritten. If we are
        ''' rewriting a lambda, currentMethod is the new generated method.
        ''' </summary>
        Protected MustOverride ReadOnly Property CurrentMethod As MethodSymbol
 
        Protected MustOverride ReadOnly Property TopLevelMethod As MethodSymbol
 
        Protected MustOverride ReadOnly Property IsInExpressionLambda As Boolean
 
        ''' <summary>
        ''' A not-null collection of synthesized methods generated for the current source type.
        ''' </summary>
        Protected ReadOnly CompilationState As TypeCompilationState
 
        Protected ReadOnly Diagnostics As BindingDiagnosticBag
        Protected ReadOnly SlotAllocatorOpt As VariableSlotAllocator
 
        ''' <summary>
        ''' During rewriting, we ignore locals that have already been rewritten to a proxy (a field on a closure class).
        ''' However, in the EE, we need to preserve the original slots for all locals (slots for any new locals must be
        ''' appended after the originals).  The <see cref="PreserveOriginalLocals"/> field is intended to suppress any
        ''' rewriter logic that would result in original locals being omitted.
        ''' </summary>
        Protected ReadOnly PreserveOriginalLocals As Boolean
 
        Protected Sub New(slotAllocatorOpt As VariableSlotAllocator, compilationState As TypeCompilationState, diagnostics As BindingDiagnosticBag, preserveOriginalLocals As Boolean)
            Debug.Assert(compilationState IsNot Nothing)
            Debug.Assert(diagnostics.AccumulatesDiagnostics)
            Me.CompilationState = compilationState
            Me.Diagnostics = diagnostics
            Me.SlotAllocatorOpt = slotAllocatorOpt
            Me.PreserveOriginalLocals = preserveOriginalLocals
        End Sub
 
#Region "Visitors"
 
        Public Overrides Function VisitLocalDeclaration(node As BoundLocalDeclaration) As BoundNode
            Dim localSymbol = node.LocalSymbol
 
            Dim proxy As TProxy = Nothing
 
            If Proxies.TryGetValue(localSymbol, proxy) Then
                ' Constant locals are never captured.
                Debug.Assert(Not localSymbol.IsConst)
                Return Nothing
            End If
 
            Return node
        End Function
 
        Public NotOverridable Overrides Function VisitType(type As TypeSymbol) As TypeSymbol
            If type Is Nothing Then
                Return type
            End If
 
            Return type.InternalSubstituteTypeParameters(Me.TypeMap).Type
        End Function
 
        Public NotOverridable Overrides Function VisitMethodInfo(node As BoundMethodInfo) As BoundNode
            Return node.Update(VisitMethodSymbol(node.Method), VisitMethodSymbol(node.GetMethodFromHandle), VisitType(node.Type))
        End Function
 
        Public Overrides Function VisitPropertyAccess(node As BoundPropertyAccess) As BoundNode
            ' NOTE: this is only reachable from Lambda rewriter
            ' NOTE: in case property access is inside expression tree
            Dim rewrittenPropertySymbol = VisitPropertySymbol(node.PropertySymbol)
            Dim rewrittenReceiver = DirectCast(Visit(node.ReceiverOpt), BoundExpression)
 
            Dim arguments As ImmutableArray(Of BoundExpression) = node.Arguments
            Dim newArguments(arguments.Length - 1) As BoundExpression
            For i = 0 To arguments.Length - 1
                newArguments(i) = DirectCast(Visit(arguments(i)), BoundExpression)
            Next
 
            Return node.Update(rewrittenPropertySymbol,
                               Nothing,
                               node.AccessKind,
                               isWriteable:=node.IsWriteable,
                               isLValue:=node.IsLValue,
                               receiverOpt:=rewrittenReceiver,
                               arguments:=newArguments.AsImmutableOrNull,
                               defaultArguments:=node.DefaultArguments,
                               type:=VisitType(node.Type))
        End Function
 
        Public Overrides Function VisitCall(node As BoundCall) As BoundNode
            Dim receiverOpt As BoundExpression = node.ReceiverOpt
            Dim rewrittenReceiverOpt As BoundExpression = DirectCast(Visit(receiverOpt), BoundExpression)
            Dim newMethod As MethodSymbol = node.Method
            Dim arguments As ImmutableArray(Of BoundExpression) = VisitList(node.Arguments)
            Dim type As TypeSymbol = VisitType(node.Type)
 
            If ShouldRewriteMethodSymbol(receiverOpt, rewrittenReceiverOpt, newMethod) Then
                Dim methodBeingCalled As MethodSymbol = SubstituteMethodForMyBaseOrMyClassCall(receiverOpt, node.Method)
                newMethod = VisitMethodSymbol(methodBeingCalled)
            End If
 
            Return node.Update(newMethod,
                               Nothing,
                               rewrittenReceiverOpt,
                               arguments,
                               node.DefaultArguments,
                               node.ConstantValueOpt,
                               isLValue:=node.IsLValue,
                               suppressObjectClone:=node.SuppressObjectClone,
                               type:=type)
        End Function
 
        Private Function ShouldRewriteMethodSymbol(originalReceiver As BoundExpression, rewrittenReceiverOpt As BoundExpression, newMethod As MethodSymbol) As Boolean
            Return originalReceiver IsNot rewrittenReceiverOpt OrElse
                   Not newMethod.IsDefinition OrElse
                   (Me.TypeMap IsNot Nothing AndAlso Me.TypeMap.TargetGenericDefinition.Equals(newMethod)) OrElse
                   (Me.IsInExpressionLambda AndAlso rewrittenReceiverOpt IsNot Nothing AndAlso
                        (rewrittenReceiverOpt.IsMyClassReference OrElse rewrittenReceiverOpt.IsMyBaseReference))
        End Function
 
        Public NotOverridable Overrides Function VisitParameter(node As BoundParameter) As BoundNode
            Dim proxy As TProxy = Nothing
            If Proxies.TryGetValue(node.ParameterSymbol, proxy) Then
                Return Me.MaterializeProxy(node, proxy)
            End If
 
            Dim replacementParameter As ParameterSymbol = Nothing
            If Me.ParameterMap.TryGetValue(node.ParameterSymbol, replacementParameter) Then
                Return New BoundParameter(node.Syntax, replacementParameter, node.IsLValue, replacementParameter.Type, node.HasErrors)
            End If
 
            Return MyBase.VisitParameter(node)
        End Function
 
        Protected MustOverride Function MaterializeProxy(origExpression As BoundExpression, proxy As TProxy) As BoundNode
 
        Public NotOverridable Overrides Function VisitLocal(node As BoundLocal) As BoundNode
            Dim local As LocalSymbol = node.LocalSymbol
 
            If local.IsConst Then
                ' Local constants are never captured
                Return MyBase.VisitLocal(node)
            End If
 
            Dim proxy As TProxy = Nothing
            If Proxies.TryGetValue(local, proxy) Then
                Return Me.MaterializeProxy(node, proxy)
            End If
 
            Dim replacementLocal As LocalSymbol = Nothing
            If Me.LocalMap.TryGetValue(local, replacementLocal) Then
                Return New BoundLocal(node.Syntax, replacementLocal, node.IsLValue, replacementLocal.Type, node.HasErrors)
            End If
 
            Return MyBase.VisitLocal(node)
        End Function
 
        Public Overrides Function VisitFieldInfo(node As BoundFieldInfo) As BoundNode
            Return node.Update(VisitFieldSymbol(node.Field), VisitType(node.Type))
        End Function
 
        Public Overrides Function VisitFieldAccess(node As BoundFieldAccess) As BoundNode
            Return node.Update(DirectCast(Visit(node.ReceiverOpt), BoundExpression),
                               VisitFieldSymbol(node.FieldSymbol),
                               node.IsLValue,
                               node.SuppressVirtualCalls,
                               constantsInProgressOpt:=Nothing,
                               VisitType(node.Type))
        End Function
 
        Public Overrides Function VisitDelegateCreationExpression(node As BoundDelegateCreationExpression) As BoundNode
            Debug.Assert(node.RelaxationLambdaOpt Is Nothing AndAlso node.RelaxationReceiverPlaceholderOpt Is Nothing)
 
            Dim rewritten = DirectCast(MyBase.VisitDelegateCreationExpression(node), BoundDelegateCreationExpression)
            Dim newMethod As MethodSymbol = rewritten.Method
            Dim rewrittenReceiver As BoundExpression = rewritten.ReceiverOpt
 
            If ShouldRewriteMethodSymbol(node.ReceiverOpt, rewrittenReceiver, newMethod) Then
                Dim methodBeingCalled As MethodSymbol = SubstituteMethodForMyBaseOrMyClassCall(node.ReceiverOpt, node.Method)
                newMethod = VisitMethodSymbol(methodBeingCalled)
            End If
 
            Return node.Update(
                rewrittenReceiver,
                newMethod,
                rewritten.RelaxationLambdaOpt,
                rewritten.RelaxationReceiverPlaceholderOpt,
                methodGroupOpt:=Nothing,
                type:=rewritten.Type)
        End Function
 
        Public Overrides Function VisitObjectCreationExpression(node As BoundObjectCreationExpression) As BoundNode
            Dim rewritten = DirectCast(MyBase.VisitObjectCreationExpression(node), BoundObjectCreationExpression)
 
            Dim constructor = rewritten.ConstructorOpt
            If constructor IsNot Nothing Then
                If node.Type IsNot rewritten.Type OrElse Not constructor.IsDefinition Then
                    Dim newConstructor = VisitMethodSymbol(constructor)
                    rewritten = node.Update(
                        newConstructor,
                        rewritten.Arguments,
                        rewritten.DefaultArguments,
                        rewritten.InitializerOpt,
                        rewritten.Type)
                End If
            End If
 
            Return rewritten
        End Function
 
        ''' <summary>
        ''' Rewrites method.
        ''' </summary>
        Private Function VisitMethodSymbol(method As MethodSymbol) As MethodSymbol
            Dim substitution As TypeSubstitution = Me.TypeMap
 
            If substitution IsNot Nothing Then
                Dim newMethod As MethodSymbol = method.OriginalDefinition
 
                Dim newContainer As TypeSymbol = method.ContainingType.InternalSubstituteTypeParameters(substitution).AsTypeSymbolOnly()
                Dim substitutedContainer = TryCast(newContainer, SubstitutedNamedType)
                If substitutedContainer IsNot Nothing Then
                    newMethod = DirectCast(substitutedContainer.GetMemberForDefinition(newMethod), MethodSymbol)
                Else
                    Dim anonymousContainer = TryCast(newContainer, AnonymousTypeManager.AnonymousTypeOrDelegatePublicSymbol)
                    If anonymousContainer IsNot Nothing Then
                        newMethod = anonymousContainer.FindSubstitutedMethodSymbol(newMethod)
                    End If
                End If
 
                If newMethod.IsGenericMethod Then
                    Dim typeArgs = method.TypeArguments
                    Dim visitedTypeArgs(typeArgs.Length - 1) As TypeSymbol
                    For i = 0 To typeArgs.Length - 1
                        visitedTypeArgs(i) = VisitType(typeArgs(i))
                    Next
                    newMethod = newMethod.Construct(visitedTypeArgs)
                End If
 
                Return newMethod
            End If
 
            Return method
        End Function
 
        ''' <summary>
        ''' Rewrites property.
        ''' </summary>
        Private Function VisitPropertySymbol([property] As PropertySymbol) As PropertySymbol
            Dim substitution As TypeSubstitution = Me.TypeMap
 
            If substitution IsNot Nothing Then
                Dim newProperty As PropertySymbol = [property].OriginalDefinition
 
                Dim newContainer As TypeSymbol = [property].ContainingType.InternalSubstituteTypeParameters(substitution).AsTypeSymbolOnly()
                Dim substitutedContainer = TryCast(newContainer, SubstitutedNamedType)
                If substitutedContainer IsNot Nothing Then
                    newProperty = DirectCast(substitutedContainer.GetMemberForDefinition(newProperty), PropertySymbol)
                Else
                    Dim anonymousContainer = TryCast(newContainer, AnonymousTypeManager.AnonymousTypePublicSymbol)
                    If anonymousContainer IsNot Nothing Then
                        Dim anonProperty = TryCast(newProperty, AnonymousTypeManager.AnonymousTypePropertyPublicSymbol)
                        newProperty = anonymousContainer.Properties(anonProperty.PropertyIndex)
                    End If
                End If
 
                Return newProperty
            End If
 
            Return [property]
        End Function
 
        ''' <summary>
        ''' Rewrites field.
        ''' </summary>
        Friend Function VisitFieldSymbol(field As FieldSymbol) As FieldSymbol
            Dim substitution As TypeSubstitution = Me.TypeMap
 
            If substitution IsNot Nothing Then
                Dim newField As FieldSymbol = field.OriginalDefinition
 
                Dim newContainer As TypeSymbol = field.ContainingType.InternalSubstituteTypeParameters(substitution).AsTypeSymbolOnly()
                Dim substitutedContainer = TryCast(newContainer, SubstitutedNamedType)
                If substitutedContainer IsNot Nothing Then
                    newField = DirectCast(substitutedContainer.GetMemberForDefinition(newField), FieldSymbol)
                End If
 
                Return newField
            End If
 
            Return field
        End Function
 
        Public Overrides Function VisitBlock(node As BoundBlock) As BoundNode
            Return RewriteBlock(node)
        End Function
 
        Public Overrides Function VisitSequence(node As BoundSequence) As BoundNode
            Return RewriteSequence(node)
        End Function
 
        Public MustOverride Overrides Function VisitCatchBlock(node As BoundCatchBlock) As BoundNode
 
        Protected Function RewriteBlock(node As BoundBlock,
                                        prologue As ArrayBuilder(Of BoundExpression),
                                        newLocals As ArrayBuilder(Of LocalSymbol)) As BoundBlock
 
            For Each v In node.Locals
                If Me.PreserveOriginalLocals OrElse Not Me.Proxies.ContainsKey(v) Then
                    Dim vType = VisitType(v.Type)
                    If TypeSymbol.Equals(vType, v.Type, TypeCompareKind.ConsiderEverything) Then
 
                        Dim replacement As LocalSymbol = Nothing
                        Dim wasReplaced As Boolean = False
                        If Not LocalMap.TryGetValue(v, replacement) Then
                            replacement = CreateReplacementLocalOrReturnSelf(v, vType, onlyReplaceIfFunctionValue:=True, wasReplaced:=wasReplaced)
                        End If
 
                        If wasReplaced Then
                            LocalMap.Add(v, replacement)
                        End If
 
                        newLocals.Add(replacement)
                    Else
                        Dim replacement As LocalSymbol = CreateReplacementLocalOrReturnSelf(v, vType)
                        newLocals.Add(replacement)
                        LocalMap.Add(v, replacement)
                    End If
                End If
            Next
 
            Dim newStatements = ArrayBuilder(Of BoundStatement).GetInstance
            Dim start As Integer = 0
            Dim nodeStatements = node.Statements
 
            If prologue.Count > 0 Then
                ' Add hidden sequence point, prologue doesn't map to source, but if
                ' the first statement in the block is a non-hidden sequence point
                ' (for example, sequence point for a method block), keep it first.
                If nodeStatements.Length > 0 AndAlso nodeStatements(0).Syntax IsNot Nothing Then
                    Dim keepSequencePointFirst As Boolean = False
 
                    Select Case nodeStatements(0).Kind
                        Case BoundKind.SequencePoint
                            Dim sp = DirectCast(nodeStatements(0), BoundSequencePoint)
                            keepSequencePointFirst = sp.StatementOpt Is Nothing
                        Case BoundKind.SequencePointWithSpan
                            Dim sp = DirectCast(nodeStatements(0), BoundSequencePointWithSpan)
                            keepSequencePointFirst = sp.StatementOpt Is Nothing
                    End Select
 
                    If keepSequencePointFirst Then
                        Dim replacement = DirectCast(Me.Visit(nodeStatements(0)), BoundStatement)
                        If replacement IsNot Nothing Then
                            newStatements.Add(replacement)
                        End If
 
                        start = 1
                    End If
                End If
 
                newStatements.Add(New BoundSequencePoint(Nothing, Nothing).MakeCompilerGenerated)
            End If
 
            For Each expr In prologue
                newStatements.Add(New BoundExpressionStatement(expr.Syntax, expr))
            Next
 
            ' done with this
            prologue.Free()
 
            For i As Integer = start To nodeStatements.Length - 1
                Dim replacement = DirectCast(Me.Visit(nodeStatements(i)), BoundStatement)
                If replacement IsNot Nothing Then
                    newStatements.Add(replacement)
                End If
            Next
 
            'TODO: we may not need to update if there was nothing to rewrite.
            Return node.Update(node.StatementListSyntax, newLocals.ToImmutableAndFree(), newStatements.ToImmutableAndFree())
        End Function
 
        Protected Function RewriteBlock(node As BoundBlock) As BoundBlock
            Dim prologue = ArrayBuilder(Of BoundExpression).GetInstance
            Dim newLocals = ArrayBuilder(Of LocalSymbol).GetInstance
 
            Return RewriteBlock(node, prologue, newLocals)
        End Function
 
        Protected Shared Function CreateReplacementLocalOrReturnSelf(
            originalLocal As LocalSymbol,
            newType As TypeSymbol,
            Optional onlyReplaceIfFunctionValue As Boolean = False,
            <Out()> Optional ByRef wasReplaced As Boolean = False
        ) As LocalSymbol
 
            If Not onlyReplaceIfFunctionValue OrElse originalLocal.IsFunctionValue Then
 
                wasReplaced = True
                Return LocalSymbol.Create(originalLocal, newType)
            Else
                wasReplaced = False
                Return originalLocal
            End If
        End Function
 
        Protected Function RewriteSequence(node As BoundSequence) As BoundSequence
            Dim prologue = ArrayBuilder(Of BoundExpression).GetInstance
            Dim newLocals = ArrayBuilder(Of LocalSymbol).GetInstance
 
            Return RewriteSequence(node, prologue, newLocals)
        End Function
 
        Protected Function RewriteSequence(node As BoundSequence,
                                           prologue As ArrayBuilder(Of BoundExpression),
                                           newLocals As ArrayBuilder(Of LocalSymbol)) As BoundSequence
 
            Dim origLocals = node.Locals
 
            ' merge locals new and rewritten original
            For Each v In origLocals
                If Not Me.Proxies.ContainsKey(v) Then
                    Dim vType = VisitType(v.Type)
                    If TypeSymbol.Equals(vType, v.Type, TypeCompareKind.ConsiderEverything) Then
                        newLocals.Add(v)
                    Else
                        Dim replacement = CreateReplacementLocalOrReturnSelf(v, vType)
                        newLocals.Add(replacement)
                        LocalMap.Add(v, replacement)
                    End If
                End If
            Next
 
            ' merge side-effect - prologue followed by rewritten original side-effect
            For Each s In node.SideEffects
                Dim replacement = DirectCast(Me.Visit(s), BoundExpression)
                If replacement IsNot Nothing Then
                    prologue.Add(replacement)
                End If
            Next
 
            Debug.Assert(node.ValueOpt IsNot Nothing OrElse node.HasErrors OrElse node.Type.SpecialType = SpecialType.System_Void)
            Dim newValue = DirectCast(Me.Visit(node.ValueOpt), BoundExpression)
 
            Return node.Update(newLocals.ToImmutableAndFree(), prologue.ToImmutableAndFree(), newValue, If(newValue Is Nothing, node.Type, newValue.Type))
        End Function
 
        Public Overrides Function VisitRValuePlaceholder(node As BoundRValuePlaceholder) As BoundNode
            Return PlaceholderReplacementMap(node)
        End Function
 
        Public Overrides Function VisitLValuePlaceholder(node As BoundLValuePlaceholder) As BoundNode
            Return PlaceholderReplacementMap(node)
        End Function
 
        Public Overrides Function VisitAwaitOperator(node As BoundAwaitOperator) As BoundNode
            Dim awaitablePlaceholder As BoundRValuePlaceholder = node.AwaitableInstancePlaceholder
            PlaceholderReplacementMap.Add(awaitablePlaceholder,
                                          awaitablePlaceholder.Update(VisitType(awaitablePlaceholder.Type)))
 
            Dim awaiterPlaceholder As BoundLValuePlaceholder = node.AwaiterInstancePlaceholder
            PlaceholderReplacementMap.Add(awaiterPlaceholder,
                                          awaiterPlaceholder.Update(VisitType(awaiterPlaceholder.Type)))
 
            Dim result As BoundNode = MyBase.VisitAwaitOperator(node)
 
            PlaceholderReplacementMap.Remove(awaitablePlaceholder)
            PlaceholderReplacementMap.Remove(awaiterPlaceholder)
 
            Return result
        End Function
 
        Public Overrides Function VisitSelectStatement(node As BoundSelectStatement) As BoundNode
            Dim placeholder As BoundRValuePlaceholder = node.ExprPlaceholderOpt
            If placeholder IsNot Nothing Then
                PlaceholderReplacementMap.Add(placeholder,
                                              placeholder.Update(VisitType(placeholder.Type)))
            End If
 
            Dim result As BoundNode = MyBase.VisitSelectStatement(node)
 
            If placeholder IsNot Nothing Then
                PlaceholderReplacementMap.Remove(placeholder)
            End If
            Return result
        End Function
 
        Public Overrides Function VisitUserDefinedShortCircuitingOperator(node As BoundUserDefinedShortCircuitingOperator) As BoundNode
            Dim leftOperandPlaceholder As BoundRValuePlaceholder = node.LeftOperandPlaceholder
            PlaceholderReplacementMap.Add(leftOperandPlaceholder,
                                          leftOperandPlaceholder.Update(VisitType(leftOperandPlaceholder.Type)))
 
            Dim result As BoundNode = MyBase.VisitUserDefinedShortCircuitingOperator(node)
 
            PlaceholderReplacementMap.Remove(leftOperandPlaceholder)
            Return result
        End Function
 
#End Region
 
    End Class
End Namespace