File: Lowering\LocalRewriter\LocalRewriter_LocalDeclaration.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 Microsoft.CodeAnalysis.PooledObjects
Imports Microsoft.CodeAnalysis.VisualBasic.Symbols
Imports Microsoft.CodeAnalysis.VisualBasic.Syntax
Imports ReferenceEqualityComparer = Roslyn.Utilities.ReferenceEqualityComparer
 
Namespace Microsoft.CodeAnalysis.VisualBasic
    Partial Friend NotInheritable Class LocalRewriter
 
        Public Overrides Function VisitLocalDeclaration(node As BoundLocalDeclaration) As BoundNode
 
            Dim localSymbol = node.LocalSymbol
            Dim staticLocalBackingFields As KeyValuePair(Of SynthesizedStaticLocalBackingField, SynthesizedStaticLocalBackingField) = Nothing
            Dim initializerOpt As BoundExpression = node.InitializerOpt
            Dim hasInitializer As Boolean = (initializerOpt IsNot Nothing)
 
            ' only if we have initializer we will produce something
            Dim result As BoundStatement = Nothing
 
            If localSymbol.IsStatic Then
                staticLocalBackingFields = CreateBackingFieldsForStaticLocal(localSymbol, hasInitializer)
            End If
 
            If hasInitializer Then
                ' Note: A variable declaration with "AsNew" and just one variable gets bound to a BoundLocalDeclaration instead of a
                ' BoundAsNewLocalDeclaration to simplify things.
                '
                ' We need to fill the replacement map in case the initializer is a object member initializer and does not need a
                ' temporary.
                Dim placeholder As BoundWithLValueExpressionPlaceholder = Nothing
 
                If initializerOpt.Kind = BoundKind.ObjectCreationExpression OrElse initializerOpt.Kind = BoundKind.NewT Then
                    Dim objectCreationExpression = DirectCast(initializerOpt, BoundObjectCreationExpressionBase)
 
                    If objectCreationExpression.InitializerOpt IsNot Nothing AndAlso
                        objectCreationExpression.InitializerOpt.Kind = BoundKind.ObjectInitializerExpression Then
 
                        Dim objectInitializer = DirectCast(objectCreationExpression.InitializerOpt, BoundObjectInitializerExpression)
 
                        If Not objectInitializer.CreateTemporaryLocalForInitialization Then
                            Debug.Assert(objectInitializer.PlaceholderOpt IsNot Nothing)
 
                            placeholder = objectInitializer.PlaceholderOpt
                            AddPlaceholderReplacement(placeholder, VisitExpressionNode(New BoundLocal(node.Syntax, localSymbol, localSymbol.Type)))
                        End If
                    End If
                End If
 
                ' Create an initializer for the local if the local is not a constant.
                If Not localSymbol.IsConst Then
                    Dim rewrittenInitializer As BoundExpression = VisitAndGenerateObjectCloneIfNeeded(initializerOpt)
                    result = RewriteLocalDeclarationAsInitializer(node, rewrittenInitializer, staticLocalBackingFields, placeholder Is Nothing)
                End If
 
                If placeholder IsNot Nothing Then
                    RemovePlaceholderReplacement(placeholder)
                End If
            End If
 
            Return result
        End Function
 
        ''' <summary>
        ''' Replaces local declaration with its initializer
        ''' Also marks resulting statement with seq point that matches original declaration.
        ''' </summary>
        Private Function RewriteLocalDeclarationAsInitializer(
            node As BoundLocalDeclaration,
            rewrittenInitializer As BoundExpression,
            staticLocalBackingFields As KeyValuePair(Of SynthesizedStaticLocalBackingField, SynthesizedStaticLocalBackingField),
            Optional objectInitializerNeedsTemporary As Boolean = True
        ) As BoundStatement
            Debug.Assert(rewrittenInitializer IsNot Nothing)
 
            Dim saveState As UnstructuredExceptionHandlingContext = LeaveUnstructuredExceptionHandlingContext(node)
 
            Dim result As BoundStatement
 
            ' If the rewritten initializer only rewrites into assignment operators, then just make it a statement and return it.
            ' This should only be the case for a AsNew declaration with a object member declaration
            If Not objectInitializerNeedsTemporary Then
 
                Debug.Assert((node.InitializerOpt Is Nothing AndAlso node.InitializedByAsNew) OrElse
                             (DirectCast(node.InitializerOpt, BoundObjectCreationExpressionBase).InitializerOpt.Kind = BoundKind.ObjectInitializerExpression AndAlso
                             Not DirectCast(DirectCast(node.InitializerOpt, BoundObjectCreationExpressionBase).InitializerOpt,
                                            BoundObjectInitializerExpression).CreateTemporaryLocalForInitialization))
 
                result = New BoundExpressionStatement(rewrittenInitializer.Syntax, rewrittenInitializer)
 
            Else
                result = New BoundExpressionStatement(
                    rewrittenInitializer.Syntax,
                    New BoundAssignmentOperator(
                        rewrittenInitializer.Syntax,
                        VisitExpressionNode(
                            New BoundLocal(
                                node.Syntax,
                                node.LocalSymbol,
                                node.LocalSymbol.Type
                            )
                        ),
                        rewrittenInitializer,
                        suppressObjectClone:=True,
                        type:=node.LocalSymbol.Type
                    )
                )
            End If
 
            If node.LocalSymbol.IsStatic Then
                result = EnforceStaticLocalInitializationSemantics(staticLocalBackingFields, result)
            End If
 
            RestoreUnstructuredExceptionHandlingContext(node, saveState)
 
            If ShouldGenerateUnstructuredExceptionHandlingResumeCode(node) Then
                result = RegisterUnstructuredExceptionHandlingResumeTarget(node.Syntax, result, canThrow:=True)
            End If
 
            If Instrument(node) Then
                result = _instrumenterOpt.InstrumentLocalInitialization(node, result)
            End If
 
            Return result
        End Function
 
        Private Function CreateBackingFieldsForStaticLocal(localSymbol As LocalSymbol, hasInitializer As Boolean) As KeyValuePair(Of SynthesizedStaticLocalBackingField, SynthesizedStaticLocalBackingField)
            Debug.Assert(localSymbol.IsStatic)
 
            If _staticLocalMap Is Nothing Then
                _staticLocalMap = New Dictionary(Of LocalSymbol, KeyValuePair(Of SynthesizedStaticLocalBackingField, SynthesizedStaticLocalBackingField))(ReferenceEqualityComparer.Instance)
            End If
 
            Dim result As New KeyValuePair(Of SynthesizedStaticLocalBackingField, SynthesizedStaticLocalBackingField)(
                                                New SynthesizedStaticLocalBackingField(localSymbol, isValueField:=True, reportErrorForLongNames:=Not hasInitializer),
                                                If(hasInitializer, New SynthesizedStaticLocalBackingField(localSymbol, isValueField:=False, reportErrorForLongNames:=True), Nothing))
 
            If _emitModule IsNot Nothing Then
                _emitModule.AddSynthesizedDefinition(Me._topMethod.ContainingType, result.Key.GetCciAdapter())
 
                If result.Value IsNot Nothing Then
                    _emitModule.AddSynthesizedDefinition(Me._topMethod.ContainingType, result.Value.GetCciAdapter())
                End If
            End If
 
            _staticLocalMap.Add(localSymbol, result)
 
            Return result
        End Function
 
        Public Overrides Function VisitLocal(node As BoundLocal) As BoundNode
            If node.LocalSymbol.IsStatic Then
                Dim backingValueField As SynthesizedStaticLocalBackingField = _staticLocalMap(node.LocalSymbol).Key
 
                Return New BoundFieldAccess(node.Syntax,
                                            If(_topMethod.IsShared,
                                               Nothing,
                                               New BoundMeReference(node.Syntax, _topMethod.ContainingType)),
                                               backingValueField, isLValue:=node.IsLValue, type:=backingValueField.Type)
            Else
                Return MyBase.VisitLocal(node)
            End If
        End Function
 
        Private Function EnforceStaticLocalInitializationSemantics(
            staticLocalBackingFields As KeyValuePair(Of SynthesizedStaticLocalBackingField, SynthesizedStaticLocalBackingField),
            rewrittenInitialization As BoundStatement
        ) As BoundStatement
            Dim syntax = rewrittenInitialization.Syntax
            Dim objectType = GetSpecialTypeWithUseSiteDiagnostics(SpecialType.System_Object, syntax)
            Dim booleanType = GetSpecialTypeWithUseSiteDiagnostics(SpecialType.System_Boolean, syntax)
 
            Dim staticLocalInitFlag__ctor As MethodSymbol = Nothing
            Dim compareExchange As MethodSymbol = Nothing
            Dim state As FieldSymbol = Nothing
            Dim ctorIncompleteInitialization As MethodSymbol = Nothing
 
            ' Note, use of 'Or' rather than 'OrElse' in the following 'If' is intentional.
            ' The goal as to report as many errors as possible, simplifies testing as well.
            If (objectType.IsErrorType() OrElse booleanType.IsErrorType()) Or
               Not TryGetWellknownMember(staticLocalInitFlag__ctor, WellKnownMember.Microsoft_VisualBasic_CompilerServices_StaticLocalInitFlag__ctor, syntax) Or
               Not TryGetWellknownMember(compareExchange, WellKnownMember.System_Threading_Interlocked__CompareExchange_T, syntax) Or
               Not TryGetWellknownMember(state, WellKnownMember.Microsoft_VisualBasic_CompilerServices_StaticLocalInitFlag__State, syntax) Or
               Not TryGetWellknownMember(ctorIncompleteInitialization, WellKnownMember.Microsoft_VisualBasic_CompilerServices_IncompleteInitialization__ctor, syntax) Then
                Return rewrittenInitialization
            End If
 
            Dim statements = ArrayBuilder(Of BoundStatement).GetInstance()
 
            Dim flag = New BoundFieldAccess(syntax,
                                            If(_topMethod.IsShared,
                                               Nothing,
                                               New BoundMeReference(syntax, _topMethod.ContainingType)),
                                            staticLocalBackingFields.Value, isLValue:=True, type:=staticLocalBackingFields.Value.Type)
 
            Dim useSiteInfo = GetNewCompoundUseSiteInfo()
            Dim flagAsObject = New BoundDirectCast(syntax,
                                                   flag.MakeRValue(),
                                                   Conversions.ClassifyDirectCastConversion(flag.Type, objectType, useSiteInfo),
                                                   objectType)
            _diagnostics.Add(syntax, useSiteInfo)
 
            ' If flag Is Nothing
            '    Interlocked.CompareExchange(flag, New StaticLocalInitFlag, Nothing)
            ' End If
 
            Dim flagIsNothing = New BoundBinaryOperator(syntax,
                                                        BinaryOperatorKind.Is, flagAsObject,
                                                        New BoundLiteral(syntax, ConstantValue.Nothing, objectType),
                                                        False,
                                                        booleanType)
 
            Dim newFlagInstance = New BoundObjectCreationExpression(syntax,
                                                                    staticLocalInitFlag__ctor,
                                                                    ImmutableArray(Of BoundExpression).Empty,
                                                                    Nothing,
                                                                    flag.Type)
 
            Dim interlockedCompareExchangeFlagWithNewInstance =
                    New BoundCall(syntax,
                                  compareExchange.Construct(flag.Type),
                                  Nothing, Nothing,
                                  ImmutableArray.Create(Of BoundExpression)(flag, newFlagInstance, New BoundLiteral(syntax, ConstantValue.Nothing, flag.Type)),
                                  Nothing,
                                  flag.Type)
 
            Dim conditionalFlagInit = RewriteIfStatement(syntax, flagIsNothing, interlockedCompareExchangeFlagWithNewInstance.ToStatement(), Nothing, instrumentationTargetOpt:=Nothing)
 
            statements.Add(conditionalFlagInit)
 
            ' Initialization of a static local occurs the first time
            ' control reaches the declaration. To guarantee this,
            ' some code to guard the execution of the initializer
            ' is necessary. For a static local named "Var", this
            ' guard code utilizes a flag named Var$Init of type
            '
            ' Class Microsoft.VisualBasic.CompilerServices.StaticLocalInitFlag
            '     Public State As Short
            ' End Class
            '
            ' and is of the form:
            '
            ' SyncLock Var$Init
            '     Try
            '         If (Var$Init.State = 0) Then
            '             Var$Init.State = 2
            '             Var = Initialization
            '         Else If (Var$Init.State = 2) Then
            '             ' Recursion in initialization
            '             Throw New Microsoft.VisualBasic.CompilerServices.IncompleteInitialization()
            '         End If
            '     Finally
            '         Var$Init.State = 1
            '     End Try
            ' End SyncLock
 
            Dim boundLockTakenLocal As BoundLocal = Nothing
            Dim tempLockTakenAssignment As BoundStatement = Nothing
 
            Dim boundMonitorEnterCall As BoundStatement = GenerateMonitorEnter(syntax, flagAsObject, boundLockTakenLocal, tempLockTakenAssignment)
            Dim flagState = New BoundFieldAccess(syntax, flag, state, isLValue:=True, type:=state.Type)
 
            Dim two = New BoundLiteral(syntax, ConstantValue.Create(2S), flagState.Type)
            Dim flagStateIsZero = New BoundBinaryOperator(syntax,
                                                          BinaryOperatorKind.Equals,
                                                          flagState.MakeRValue(),
                                                          New BoundLiteral(syntax, ConstantValue.Default(ConstantValueTypeDiscriminator.Int16), flagState.Type),
                                                          False,
                                                          booleanType)
 
            Dim flagStateAssignTwo = New BoundAssignmentOperator(syntax, flagState, two, suppressObjectClone:=True).ToStatement()
 
            Dim flagStateIsTwo = New BoundBinaryOperator(syntax,
                                                         BinaryOperatorKind.Equals,
                                                         flagState.MakeRValue(),
                                                         two,
                                                         False,
                                                         booleanType)
 
            Dim throwIncompleteInitialization = New BoundThrowStatement(syntax,
                                                                        New BoundObjectCreationExpression(syntax,
                                                                                                          ctorIncompleteInitialization,
                                                                                                          ImmutableArray(Of BoundExpression).Empty,
                                                                                                          Nothing,
                                                                                                          ctorIncompleteInitialization.ContainingType))
 
            Dim conditionalValueInit =
                RewriteIfStatement(syntax,
                                   flagStateIsZero,
                                   New BoundStatementList(syntax, ImmutableArray.Create(flagStateAssignTwo, rewrittenInitialization)),
                                   RewriteIfStatement(syntax,
                                                      flagStateIsTwo,
                                                      throwIncompleteInitialization,
                                                      Nothing,
                                                      instrumentationTargetOpt:=Nothing),
                                   instrumentationTargetOpt:=Nothing)
 
            Dim locals As ImmutableArray(Of LocalSymbol)
            Dim statementsInTry As ImmutableArray(Of BoundStatement)
 
            If boundLockTakenLocal IsNot Nothing Then
                locals = ImmutableArray.Create(boundLockTakenLocal.LocalSymbol)
                statements.Add(tempLockTakenAssignment)
                statementsInTry = ImmutableArray.Create(boundMonitorEnterCall, conditionalValueInit)
            Else
                locals = ImmutableArray(Of LocalSymbol).Empty
                statements.Add(boundMonitorEnterCall)
                statementsInTry = ImmutableArray.Create(Of BoundStatement)(conditionalValueInit)
            End If
 
            Dim tryBody As BoundBlock = New BoundBlock(syntax,
                                                       Nothing,
                                                       ImmutableArray(Of LocalSymbol).Empty,
                                                       statementsInTry)
 
            Dim flagStateAssignOne = New BoundAssignmentOperator(syntax, flagState,
                                                                 New BoundLiteral(syntax, ConstantValue.Create(1S), flagState.Type),
                                                                 suppressObjectClone:=True).ToStatement()
 
            Dim monitorExit As BoundStatement = GenerateMonitorExit(syntax, flagAsObject, boundLockTakenLocal)
 
            Dim finallyBody As BoundBlock = New BoundBlock(syntax,
                                                           Nothing,
                                                           ImmutableArray(Of LocalSymbol).Empty,
                                                           ImmutableArray.Create(flagStateAssignOne, monitorExit))
 
            Dim tryFinally = New BoundTryStatement(syntax, tryBody, ImmutableArray(Of BoundCatchBlock).Empty, finallyBody, Nothing)
 
            statements.Add(tryFinally)
 
            Return New BoundBlock(syntax,
                                  Nothing,
                                  locals,
                                  statements.ToImmutableAndFree)
        End Function
 
    End Class
End Namespace