File: Parser\BlockContexts\CompilationUnitContext.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 Microsoft.CodeAnalysis.PooledObjects
Imports Microsoft.CodeAnalysis.Syntax.InternalSyntax
Imports Microsoft.CodeAnalysis.Text
Imports Microsoft.CodeAnalysis.VisualBasic.Symbols
Imports Microsoft.CodeAnalysis.VisualBasic.Syntax
'-----------------------------------------------------------------------------
' Contains the definition of the DeclarationContext
'-----------------------------------------------------------------------------

Namespace Microsoft.CodeAnalysis.VisualBasic.Syntax.InternalSyntax

    Friend NotInheritable Class CompilationUnitContext
        Inherits NamespaceBlockContext

        Private _optionStmts As CodeAnalysis.Syntax.InternalSyntax.SyntaxList(Of OptionStatementSyntax)
        Private _importsStmts As CodeAnalysis.Syntax.InternalSyntax.SyntaxList(Of ImportsStatementSyntax)
        Private _attributeStmts As CodeAnalysis.Syntax.InternalSyntax.SyntaxList(Of AttributesStatementSyntax)
        Private _state As SyntaxKind

        Friend Sub New(parser As Parser)
            MyBase.New(SyntaxKind.CompilationUnit, Nothing, Nothing)

            Me.Parser = parser
            _statements = _parser._pool.Allocate(Of StatementSyntax)()
            _state = SyntaxKind.OptionStatement
        End Sub

        Friend Overrides ReadOnly Property IsWithinAsyncMethodOrLambda As Boolean
            Get
                Return Parser.IsScript
            End Get
        End Property

        Friend Overrides Function ProcessSyntax(node As VisualBasicSyntaxNode) As BlockContext
            Do
                Select Case _state
                    Case SyntaxKind.OptionStatement
                        If node.Kind = SyntaxKind.OptionStatement Then
                            Add(node)
                            Return Me
                        End If
                        _optionStmts = New CodeAnalysis.Syntax.InternalSyntax.SyntaxList(Of OptionStatementSyntax)(Body.Node)
                        _state = SyntaxKind.ImportsStatement

                    Case SyntaxKind.ImportsStatement
                        If node.Kind = SyntaxKind.ImportsStatement Then
                            Add(node)
                            Return Me
                        End If
                        _importsStmts = New CodeAnalysis.Syntax.InternalSyntax.SyntaxList(Of ImportsStatementSyntax)(Body.Node)
                        _state = SyntaxKind.AttributesStatement

                    Case SyntaxKind.AttributesStatement
                        If node.Kind = SyntaxKind.AttributesStatement Then
                            Add(node)
                            Return Me
                        End If
                        _attributeStmts = New CodeAnalysis.Syntax.InternalSyntax.SyntaxList(Of AttributesStatementSyntax)(Body.Node)
                        _state = SyntaxKind.None

                    Case Else
                        ' only allow executable statements in top-level script code
                        If _parser.IsScript Then
                            Dim newContext = TryProcessExecutableStatement(node)
                            If newContext IsNot Nothing Then
                                Return newContext
                            End If
                        End If

                        Return MyBase.ProcessSyntax(node)
                End Select
            Loop
        End Function

        Friend Overrides Function TryLinkSyntax(node As VisualBasicSyntaxNode, ByRef newContext As BlockContext) As LinkResult
            If _parser.IsScript Then
                Return TryLinkStatement(node, newContext)
            Else
                Return MyBase.TryLinkSyntax(node, newContext)
            End If
        End Function

        Friend Overrides Function CreateBlockSyntax(endStmt As StatementSyntax) As VisualBasicSyntaxNode
            Throw ExceptionUtilities.Unreachable
        End Function

        Friend Function CreateCompilationUnit(optionalTerminator As PunctuationSyntax,
                                              notClosedIfDirectives As ArrayBuilder(Of IfDirectiveTriviaSyntax),
                                              notClosedRegionDirectives As ArrayBuilder(Of RegionDirectiveTriviaSyntax),
                                              haveRegionDirectives As Boolean,
                                              notClosedExternalSourceDirective As ExternalSourceDirectiveTriviaSyntax) As CompilationUnitSyntax

            Debug.Assert(optionalTerminator Is Nothing OrElse optionalTerminator.Kind = SyntaxKind.EndOfFileToken)

            If _state <> SyntaxKind.None Then
                Select Case _state
                    Case SyntaxKind.OptionStatement
                        _optionStmts = New CodeAnalysis.Syntax.InternalSyntax.SyntaxList(Of OptionStatementSyntax)(Body.Node)

                    Case SyntaxKind.ImportsStatement
                        _importsStmts = New CodeAnalysis.Syntax.InternalSyntax.SyntaxList(Of ImportsStatementSyntax)(Body.Node)

                    Case SyntaxKind.AttributesStatement
                        _attributeStmts = New CodeAnalysis.Syntax.InternalSyntax.SyntaxList(Of AttributesStatementSyntax)(Body.Node)
                End Select
                _state = SyntaxKind.None
            End If

            Dim declarations = Body()

            Dim result = SyntaxFactory.CompilationUnit(_optionStmts,
                                                       _importsStmts,
                                                       _attributeStmts,
                                                       declarations,
                                                       optionalTerminator)

            Dim regionsAreAllowedEverywhere = Not haveRegionDirectives OrElse Parser.CheckFeatureAvailability(Feature.RegionsEverywhere)

            If notClosedIfDirectives IsNot Nothing OrElse notClosedRegionDirectives IsNot Nothing OrElse notClosedExternalSourceDirective IsNot Nothing OrElse
               Not regionsAreAllowedEverywhere Then
                result = DiagnosticRewriter.Rewrite(result, notClosedIfDirectives, notClosedRegionDirectives, regionsAreAllowedEverywhere, notClosedExternalSourceDirective, Parser)

                If notClosedIfDirectives IsNot Nothing Then
                    notClosedIfDirectives.Free()
                End If
                If notClosedRegionDirectives IsNot Nothing Then
                    notClosedRegionDirectives.Free()
                End If
            End If

            FreeStatements()
            Return result
        End Function

        Private Class DiagnosticRewriter
            Inherits VisualBasicSyntaxRewriter

            Private _notClosedIfDirectives As HashSet(Of IfDirectiveTriviaSyntax) = Nothing
            Private _notClosedRegionDirectives As HashSet(Of RegionDirectiveTriviaSyntax) = Nothing
            Private _notClosedExternalSourceDirective As ExternalSourceDirectiveTriviaSyntax = Nothing
            Private _regionsAreAllowedEverywhere As Boolean

            Private _parser As Parser
            Private _declarationBlocksBeingVisited As ArrayBuilder(Of VisualBasicSyntaxNode) ' CompilationUnitSyntax is treated as a declaration block for our purposes
            Private _parentsOfRegionDirectivesAwaitingClosure As ArrayBuilder(Of VisualBasicSyntaxNode) ' Nodes are coming from _declarationBlocksBeingVisited
            Private _tokenWithDirectivesBeingVisited As SyntaxToken

            Private Sub New()
                MyBase.New()
            End Sub

            Public Shared Function Rewrite(compilationUnit As CompilationUnitSyntax,
                                           notClosedIfDirectives As ArrayBuilder(Of IfDirectiveTriviaSyntax),
                                           notClosedRegionDirectives As ArrayBuilder(Of RegionDirectiveTriviaSyntax),
                                           regionsAreAllowedEverywhere As Boolean,
                                           notClosedExternalSourceDirective As ExternalSourceDirectiveTriviaSyntax,
                                           parser As Parser) As CompilationUnitSyntax

                Dim rewriter As New DiagnosticRewriter()

                If notClosedIfDirectives IsNot Nothing Then
                    rewriter._notClosedIfDirectives =
                        New HashSet(Of IfDirectiveTriviaSyntax)(ReferenceEqualityComparer.Instance)

                    For Each node In notClosedIfDirectives
                        rewriter._notClosedIfDirectives.Add(node)
                    Next
                End If

                If notClosedRegionDirectives IsNot Nothing Then
                    rewriter._notClosedRegionDirectives =
                        New HashSet(Of RegionDirectiveTriviaSyntax)(ReferenceEqualityComparer.Instance)

                    For Each node In notClosedRegionDirectives
                        rewriter._notClosedRegionDirectives.Add(node)
                    Next
                End If

                rewriter._parser = parser
                rewriter._regionsAreAllowedEverywhere = regionsAreAllowedEverywhere

                If Not regionsAreAllowedEverywhere Then
                    rewriter._declarationBlocksBeingVisited = ArrayBuilder(Of VisualBasicSyntaxNode).GetInstance()
                    rewriter._parentsOfRegionDirectivesAwaitingClosure = ArrayBuilder(Of VisualBasicSyntaxNode).GetInstance()
                End If

                rewriter._notClosedExternalSourceDirective = notClosedExternalSourceDirective

                Dim result = DirectCast(rewriter.Visit(compilationUnit), CompilationUnitSyntax)

                If Not regionsAreAllowedEverywhere Then
                    Debug.Assert(rewriter._declarationBlocksBeingVisited.Count = 0)
                    Debug.Assert(rewriter._parentsOfRegionDirectivesAwaitingClosure.Count = 0) ' We never add parents of not closed #Region directives into this stack.
                    rewriter._declarationBlocksBeingVisited.Free()
                    rewriter._parentsOfRegionDirectivesAwaitingClosure.Free()
                End If

                Return result
            End Function

#If DEBUG Then
            ' NOTE: the logic is heavily relying on the fact that green nodes in
            ' NOTE: one single tree are not reused, the following code assert this
            Private ReadOnly _processedNodesWithoutDuplication As HashSet(Of VisualBasicSyntaxNode) = New HashSet(Of VisualBasicSyntaxNode)(ReferenceEqualityComparer.Instance)
#End If

            Public Overrides Function VisitCompilationUnit(node As CompilationUnitSyntax) As VisualBasicSyntaxNode
                If _declarationBlocksBeingVisited IsNot Nothing Then
                    _declarationBlocksBeingVisited.Push(node)
                End If

                Dim result = MyBase.VisitCompilationUnit(node)

                If _declarationBlocksBeingVisited IsNot Nothing Then
                    Dim n = _declarationBlocksBeingVisited.Pop()
                    Debug.Assert(n Is node)
                End If

                Return result
            End Function

            Public Overrides Function VisitMethodBlock(node As MethodBlockSyntax) As VisualBasicSyntaxNode
                If _declarationBlocksBeingVisited IsNot Nothing Then
                    _declarationBlocksBeingVisited.Push(node)
                End If

                Dim result = MyBase.VisitMethodBlock(node)

                If _declarationBlocksBeingVisited IsNot Nothing Then
                    Dim n = _declarationBlocksBeingVisited.Pop()
                    Debug.Assert(n Is node)
                End If

                Return result
            End Function

            Public Overrides Function VisitConstructorBlock(node As ConstructorBlockSyntax) As VisualBasicSyntaxNode
                If _declarationBlocksBeingVisited IsNot Nothing Then
                    _declarationBlocksBeingVisited.Push(node)
                End If

                Dim result = MyBase.VisitConstructorBlock(node)

                If _declarationBlocksBeingVisited IsNot Nothing Then
                    Dim n = _declarationBlocksBeingVisited.Pop()
                    Debug.Assert(n Is node)
                End If

                Return result
            End Function

            Public Overrides Function VisitOperatorBlock(node As OperatorBlockSyntax) As VisualBasicSyntaxNode
                If _declarationBlocksBeingVisited IsNot Nothing Then
                    _declarationBlocksBeingVisited.Push(node)
                End If

                Dim result = MyBase.VisitOperatorBlock(node)

                If _declarationBlocksBeingVisited IsNot Nothing Then
                    Dim n = _declarationBlocksBeingVisited.Pop()
                    Debug.Assert(n Is node)
                End If

                Return result
            End Function

            Public Overrides Function VisitAccessorBlock(node As AccessorBlockSyntax) As VisualBasicSyntaxNode
                If _declarationBlocksBeingVisited IsNot Nothing Then
                    _declarationBlocksBeingVisited.Push(node)
                End If

                Dim result = MyBase.VisitAccessorBlock(node)

                If _declarationBlocksBeingVisited IsNot Nothing Then
                    Dim n = _declarationBlocksBeingVisited.Pop()
                    Debug.Assert(n Is node)
                End If

                Return result
            End Function

            Public Overrides Function VisitNamespaceBlock(node As NamespaceBlockSyntax) As VisualBasicSyntaxNode
                If _declarationBlocksBeingVisited IsNot Nothing Then
                    _declarationBlocksBeingVisited.Push(node)
                End If

                Dim result = MyBase.VisitNamespaceBlock(node)

                If _declarationBlocksBeingVisited IsNot Nothing Then
                    Dim n = _declarationBlocksBeingVisited.Pop()
                    Debug.Assert(n Is node)
                End If

                Return result
            End Function

            Public Overrides Function VisitClassBlock(node As ClassBlockSyntax) As VisualBasicSyntaxNode
                If _declarationBlocksBeingVisited IsNot Nothing Then
                    _declarationBlocksBeingVisited.Push(node)
                End If

                Dim result = MyBase.VisitClassBlock(node)

                If _declarationBlocksBeingVisited IsNot Nothing Then
                    Dim n = _declarationBlocksBeingVisited.Pop()
                    Debug.Assert(n Is node)
                End If

                Return result
            End Function

            Public Overrides Function VisitStructureBlock(node As StructureBlockSyntax) As VisualBasicSyntaxNode
                If _declarationBlocksBeingVisited IsNot Nothing Then
                    _declarationBlocksBeingVisited.Push(node)
                End If

                Dim result = MyBase.VisitStructureBlock(node)

                If _declarationBlocksBeingVisited IsNot Nothing Then
                    Dim n = _declarationBlocksBeingVisited.Pop()
                    Debug.Assert(n Is node)
                End If

                Return result
            End Function

            Public Overrides Function VisitModuleBlock(node As ModuleBlockSyntax) As VisualBasicSyntaxNode
                If _declarationBlocksBeingVisited IsNot Nothing Then
                    _declarationBlocksBeingVisited.Push(node)
                End If

                Dim result = MyBase.VisitModuleBlock(node)

                If _declarationBlocksBeingVisited IsNot Nothing Then
                    Dim n = _declarationBlocksBeingVisited.Pop()
                    Debug.Assert(n Is node)
                End If

                Return result
            End Function

            Public Overrides Function VisitInterfaceBlock(node As InterfaceBlockSyntax) As VisualBasicSyntaxNode
                If _declarationBlocksBeingVisited IsNot Nothing Then
                    _declarationBlocksBeingVisited.Push(node)
                End If

                Dim result = MyBase.VisitInterfaceBlock(node)

                If _declarationBlocksBeingVisited IsNot Nothing Then
                    Dim n = _declarationBlocksBeingVisited.Pop()
                    Debug.Assert(n Is node)
                End If

                Return result
            End Function

            Public Overrides Function VisitEnumBlock(node As EnumBlockSyntax) As VisualBasicSyntaxNode
                If _declarationBlocksBeingVisited IsNot Nothing Then
                    _declarationBlocksBeingVisited.Push(node)
                End If

                Dim result = MyBase.VisitEnumBlock(node)

                If _declarationBlocksBeingVisited IsNot Nothing Then
                    Dim n = _declarationBlocksBeingVisited.Pop()
                    Debug.Assert(n Is node)
                End If

                Return result
            End Function

            Public Overrides Function VisitPropertyBlock(node As PropertyBlockSyntax) As VisualBasicSyntaxNode
                If _declarationBlocksBeingVisited IsNot Nothing Then
                    _declarationBlocksBeingVisited.Push(node)
                End If

                Dim result = MyBase.VisitPropertyBlock(node)

                If _declarationBlocksBeingVisited IsNot Nothing Then
                    Dim n = _declarationBlocksBeingVisited.Pop()
                    Debug.Assert(n Is node)
                End If

                Return result
            End Function

            Public Overrides Function VisitEventBlock(node As EventBlockSyntax) As VisualBasicSyntaxNode
                If _declarationBlocksBeingVisited IsNot Nothing Then
                    _declarationBlocksBeingVisited.Push(node)
                End If

                Dim result = MyBase.VisitEventBlock(node)

                If _declarationBlocksBeingVisited IsNot Nothing Then
                    Dim n = _declarationBlocksBeingVisited.Pop()
                    Debug.Assert(n Is node)
                End If

                Return result
            End Function

            Public Overrides Function VisitIfDirectiveTrivia(node As IfDirectiveTriviaSyntax) As VisualBasicSyntaxNode
#If DEBUG Then
                Debug.Assert(_processedNodesWithoutDuplication.Add(node))
#End If
                Dim rewritten = MyBase.VisitIfDirectiveTrivia(node)
                If Me._notClosedIfDirectives IsNot Nothing AndAlso Me._notClosedIfDirectives.Contains(node) Then
                    rewritten = Parser.ReportSyntaxError(rewritten, ERRID.ERR_LbExpectedEndIf)
                End If

                Return rewritten
            End Function

            Public Overrides Function VisitRegionDirectiveTrivia(node As RegionDirectiveTriviaSyntax) As VisualBasicSyntaxNode
#If DEBUG Then
                Debug.Assert(_processedNodesWithoutDuplication.Add(node))
#End If
                Dim rewritten = MyBase.VisitRegionDirectiveTrivia(node)
                If Me._notClosedRegionDirectives IsNot Nothing AndAlso Me._notClosedRegionDirectives.Contains(node) Then
                    rewritten = Parser.ReportSyntaxError(rewritten, ERRID.ERR_ExpectedEndRegion)

                ElseIf Not _regionsAreAllowedEverywhere
                    rewritten = VerifyRegionPlacement(node, rewritten)
                End If

                Return rewritten
            End Function

            Private Function VerifyRegionPlacement(original As VisualBasicSyntaxNode, rewritten As VisualBasicSyntaxNode) As VisualBasicSyntaxNode
                Dim containingBlock = _declarationBlocksBeingVisited.Peek()

                ' Ensure that the directive is inside the block, rather than is attached to it as a leading/trailing trivia
                Debug.Assert(_declarationBlocksBeingVisited.Count > 1 OrElse containingBlock.Kind = SyntaxKind.CompilationUnit)

                If _declarationBlocksBeingVisited.Count > 1 Then

                    If _tokenWithDirectivesBeingVisited Is containingBlock.GetFirstToken() Then
                        Dim leadingTrivia = _tokenWithDirectivesBeingVisited.GetLeadingTrivia()

                        If leadingTrivia IsNot Nothing AndAlso New CodeAnalysis.Syntax.InternalSyntax.SyntaxList(Of VisualBasicSyntaxNode)(leadingTrivia).Nodes.Contains(original) Then
                            containingBlock = _declarationBlocksBeingVisited(_declarationBlocksBeingVisited.Count - 2)
                        End If

                    ElseIf _tokenWithDirectivesBeingVisited Is containingBlock.GetLastToken() Then
                        Dim trailingTrivia = _tokenWithDirectivesBeingVisited.GetTrailingTrivia()

                        If trailingTrivia IsNot Nothing AndAlso New CodeAnalysis.Syntax.InternalSyntax.SyntaxList(Of VisualBasicSyntaxNode)(trailingTrivia).Nodes.Contains(original) Then
                            containingBlock = _declarationBlocksBeingVisited(_declarationBlocksBeingVisited.Count - 2)
                        End If
                    End If
                End If

                Dim reportAnError = Not IsValidContainingBlockForRegionInVB12(containingBlock)

                If original.Kind = SyntaxKind.RegionDirectiveTrivia Then
                    _parentsOfRegionDirectivesAwaitingClosure.Push(containingBlock)
                Else
                    Debug.Assert(original.Kind = SyntaxKind.EndRegionDirectiveTrivia)

                    If _parentsOfRegionDirectivesAwaitingClosure.Count > 0 Then
                        Dim regionBeginContainingBlock = _parentsOfRegionDirectivesAwaitingClosure.Pop()

                        If regionBeginContainingBlock IsNot containingBlock AndAlso IsValidContainingBlockForRegionInVB12(regionBeginContainingBlock) Then
                            reportAnError = True
                        End If
                    End If
                End If

                If reportAnError Then
                    rewritten = _parser.ReportFeatureUnavailable(Feature.RegionsEverywhere, rewritten)
                End If

                Return rewritten
            End Function

            Private Shared Function IsValidContainingBlockForRegionInVB12(containingBlock As VisualBasicSyntaxNode) As Boolean
                Select Case containingBlock.Kind
                    Case SyntaxKind.FunctionBlock,
                         SyntaxKind.SubBlock,
                         SyntaxKind.ConstructorBlock,
                         SyntaxKind.OperatorBlock,
                         SyntaxKind.SetAccessorBlock,
                         SyntaxKind.GetAccessorBlock,
                         SyntaxKind.AddHandlerAccessorBlock,
                         SyntaxKind.RemoveHandlerAccessorBlock,
                         SyntaxKind.RaiseEventAccessorBlock

                        Return False
                End Select

                Return True
            End Function

            Public Overrides Function VisitEndRegionDirectiveTrivia(node As EndRegionDirectiveTriviaSyntax) As VisualBasicSyntaxNode
#If DEBUG Then
                Debug.Assert(_processedNodesWithoutDuplication.Add(node))
#End If
                Dim rewritten = MyBase.VisitEndRegionDirectiveTrivia(node)

                If Not _regionsAreAllowedEverywhere Then
                    rewritten = VerifyRegionPlacement(node, rewritten)
                End If

                Return rewritten
            End Function

            Public Overrides Function VisitExternalSourceDirectiveTrivia(node As ExternalSourceDirectiveTriviaSyntax) As VisualBasicSyntaxNode
#If DEBUG Then
                Debug.Assert(_processedNodesWithoutDuplication.Add(node))
#End If
                Dim rewritten = MyBase.VisitExternalSourceDirectiveTrivia(node)
                If Me._notClosedExternalSourceDirective Is node Then
                    rewritten = Parser.ReportSyntaxError(rewritten, ERRID.ERR_ExpectedEndExternalSource)
                End If
                Return rewritten
            End Function

            Public Overrides Function Visit(node As VisualBasicSyntaxNode) As VisualBasicSyntaxNode
                If node Is Nothing OrElse Not node.ContainsDirectives Then
                    Return node
                End If

                Return node.Accept(Me)
            End Function

            Public Overrides Function VisitSyntaxToken(token As SyntaxToken) As SyntaxToken
                If token Is Nothing OrElse Not token.ContainsDirectives Then
                    Return token
                End If

                Debug.Assert(_tokenWithDirectivesBeingVisited Is Nothing)
                _tokenWithDirectivesBeingVisited = token

                Dim leadingTrivia = token.GetLeadingTrivia()
                Dim trailingTrivia = token.GetTrailingTrivia()

                If leadingTrivia IsNot Nothing Then
                    Dim rewritten = VisitList(New CodeAnalysis.Syntax.InternalSyntax.SyntaxList(Of VisualBasicSyntaxNode)(leadingTrivia)).Node
                    If leadingTrivia IsNot rewritten Then
                        token = DirectCast(token.WithLeadingTrivia(rewritten), SyntaxToken)
                    End If
                End If

                If trailingTrivia IsNot Nothing Then
                    Dim rewritten = VisitList(New CodeAnalysis.Syntax.InternalSyntax.SyntaxList(Of VisualBasicSyntaxNode)(trailingTrivia)).Node
                    If trailingTrivia IsNot rewritten Then
                        token = DirectCast(token.WithTrailingTrivia(rewritten), SyntaxToken)
                    End If
                End If

                _tokenWithDirectivesBeingVisited = Nothing
                Return token
            End Function

        End Class
    End Class

End Namespace