File: Binding\ExecutableCodeBinder.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.Concurrent
Imports System.Collections.Generic
Imports System.Collections.Immutable
Imports System.Runtime.InteropServices
Imports System.Threading
Imports Microsoft.CodeAnalysis.PooledObjects
Imports Microsoft.CodeAnalysis.RuntimeMembers
Imports Microsoft.CodeAnalysis.Text
Imports Microsoft.CodeAnalysis.VisualBasic.Symbols
Imports Microsoft.CodeAnalysis.VisualBasic.Syntax
Imports Roslyn.Utilities
Imports TypeKind = Microsoft.CodeAnalysis.TypeKind
 
Namespace Microsoft.CodeAnalysis.VisualBasic
 
    ''' <summary>
    ''' A ExecutableCodeBinder provides context for looking up labels within a context represented by a syntax node, 
    ''' and also implementation of GetBinder. 
    ''' </summary>
    Friend MustInherit Class ExecutableCodeBinder
        Inherits Binder
 
        Private ReadOnly _syntaxRoot As SyntaxNode
        Private ReadOnly _descendantBinderFactory As DescendantBinderFactory
        Private _labelsMap As MultiDictionary(Of String, SourceLabelSymbol)
        Private _labels As ImmutableArray(Of SourceLabelSymbol) = Nothing
 
        Public Sub New(root As SyntaxNode, containingBinder As Binder)
            MyBase.New(containingBinder)
 
            _syntaxRoot = root
            _descendantBinderFactory = New DescendantBinderFactory(Me, root)
        End Sub
 
        Friend ReadOnly Property Labels As ImmutableArray(Of SourceLabelSymbol)
            Get
                If _labels.IsDefault Then
                    ImmutableInterlocked.InterlockedCompareExchange(_labels, BuildLabels(), Nothing)
                End If
 
                Return _labels
            End Get
        End Property
 
        ' Build a read only array of all the local variables declared in this statement list.
        Private Function BuildLabels() As ImmutableArray(Of SourceLabelSymbol)
            Dim labels = ArrayBuilder(Of SourceLabelSymbol).GetInstance()
 
            Dim syntaxVisitor = New LabelVisitor(labels, DirectCast(ContainingMember, MethodSymbol), Me)
 
            Select Case _syntaxRoot.Kind
                Case SyntaxKind.SingleLineFunctionLambdaExpression,
                     SyntaxKind.SingleLineSubLambdaExpression
                    syntaxVisitor.Visit(DirectCast(_syntaxRoot, SingleLineLambdaExpressionSyntax).Body)
 
                Case SyntaxKind.MultiLineFunctionLambdaExpression,
                     SyntaxKind.MultiLineSubLambdaExpression
                    syntaxVisitor.VisitList(DirectCast(_syntaxRoot, MultiLineLambdaExpressionSyntax).Statements)
 
                Case Else
                    syntaxVisitor.Visit(_syntaxRoot)
            End Select
 
            If labels.Count > 0 Then
                Return labels.ToImmutableAndFree()
            Else
                labels.Free()
                Return ImmutableArray(Of SourceLabelSymbol).Empty
            End If
        End Function
 
        Private ReadOnly Property LabelsMap As MultiDictionary(Of String, SourceLabelSymbol)
            Get
                If Me._labelsMap Is Nothing Then
                    Interlocked.CompareExchange(Me._labelsMap, BuildLabelsMap(Me.Labels), Nothing)
                End If
                Return Me._labelsMap
            End Get
        End Property
 
        Private Shared ReadOnly s_emptyLabelMap As MultiDictionary(Of String, SourceLabelSymbol) = New MultiDictionary(Of String, SourceLabelSymbol)(0, IdentifierComparison.Comparer)
 
        Private Shared Function BuildLabelsMap(labels As ImmutableArray(Of SourceLabelSymbol)) As MultiDictionary(Of String, SourceLabelSymbol)
            If Not labels.IsEmpty Then
                Dim map = New MultiDictionary(Of String, SourceLabelSymbol)(labels.Length, IdentifierComparison.Comparer)
                For Each label In labels
                    map.Add(label.Name, label)
                Next
                Return map
            Else
                ' Return an empty map if there aren't any labels.
                ' LookupLabelByNameToken and other methods assumes a non null map 
                ' is returned from the LabelMap property.
                Return s_emptyLabelMap
            End If
        End Function
 
        Friend Overrides Function LookupLabelByNameToken(labelName As SyntaxToken) As LabelSymbol
            Dim name As String = labelName.ValueText
 
            For Each labelSymbol As LabelSymbol In Me.LabelsMap(name)
                If labelSymbol.LabelName = labelName Then
                    Return labelSymbol
                End If
            Next
 
            Return MyBase.LookupLabelByNameToken(labelName)
        End Function
 
        Friend Overrides Sub LookupInSingleBinder(lookupResult As LookupResult,
                                                      name As String,
                                                      arity As Integer,
                                                      options As LookupOptions,
                                                      originalBinder As Binder,
                                                      <[In], Out> ByRef useSiteInfo As CompoundUseSiteInfo(Of AssemblySymbol))
            Debug.Assert(lookupResult.IsClear)
 
            If (options And LookupOptions.LabelsOnly) = LookupOptions.LabelsOnly AndAlso LabelsMap IsNot Nothing Then
                Dim labels = Me.LabelsMap(name)
 
                Select Case labels.Count
                    Case 0
                        ' Not found
 
                    Case 1
                        lookupResult.SetFrom(SingleLookupResult.Good(labels.Single()))
 
                    Case Else
                        ' There are several labels with the same name, so we are going through the list 
                        ' of labels and pick one with the smallest location to make the choice deterministic
                        Dim bestSymbol As SourceLabelSymbol = Nothing
                        Dim bestLocation As Location = Nothing
                        For Each symbol In labels
                            Debug.Assert(symbol.Locations.Length = 1)
                            Dim sourceLocation As Location = symbol.Locations(0)
                            If bestSymbol Is Nothing OrElse Me.Compilation.CompareSourceLocations(bestLocation, sourceLocation) > 0 Then
                                bestSymbol = symbol
                                bestLocation = sourceLocation
                            End If
                        Next
 
                        lookupResult.SetFrom(SingleLookupResult.Good(bestSymbol))
                End Select
            End If
        End Sub
 
        Friend Overrides Sub AddLookupSymbolsInfoInSingleBinder(nameSet As LookupSymbolsInfo,
                                                                    options As LookupOptions,
                                                                    originalBinder As Binder)
            ' UNDONE: additional filtering based on options?
            If Not Labels.IsEmpty AndAlso (options And LookupOptions.LabelsOnly) = LookupOptions.LabelsOnly Then
                Dim labels = Me.Labels
                For Each labelSymbol In labels
                    nameSet.AddSymbol(labelSymbol, labelSymbol.Name, 0)
                Next
            End If
        End Sub
 
        Public Overrides Function GetBinder(stmtList As SyntaxList(Of StatementSyntax)) As Binder
            Return _descendantBinderFactory.GetBinder(stmtList)
        End Function
 
        Public Overrides Function GetBinder(node As SyntaxNode) As Binder
            Return _descendantBinderFactory.GetBinder(node)
        End Function
 
        Public ReadOnly Property Root As SyntaxNode
            Get
                Return _descendantBinderFactory.Root
            End Get
        End Property
 
        ' Get the map that maps from syntax nodes to binders.
        Public ReadOnly Property NodeToBinderMap As ImmutableDictionary(Of SyntaxNode, BlockBaseBinder)
            Get
                Return _descendantBinderFactory.NodeToBinderMap
            End Get
        End Property
 
        ' Get the map that maps from statement lists to binders.
        Friend ReadOnly Property StmtListToBinderMap As ImmutableDictionary(Of SyntaxList(Of StatementSyntax), BlockBaseBinder)
            Get
                Return _descendantBinderFactory.StmtListToBinderMap
            End Get
        End Property
 
#If DEBUG Then
        ' Implicit variable declaration (Option Explicit Off) relies on identifiers
        ' being bound in order. Also, most of our tests run with Option Explicit On. To test that 
        ' we bind identifiers in order even with Option Explicit On, in DEBUG we check the order or
        ' binding of simple names when compiling a whole method body (i.e., during batch compilation, 
        ' not SemanticModel services). 
        '
        ' We check lambda separately from method bodies (even though in theory they should be checked
        ' together) because there are cases where lambda are bound out of order.
        '
        ' See SourceMethodSymbol.GetBoundMethodBody for where this is enabled. 
        '
        ' See BindSimpleName for where CheckSimpleNameBinderOrder is called.
        ' We just store the positions of simple names that have been checked. 
 
        Private _checkSimpleNameBindingOrder As Boolean = False
 
        ' The set of offsets of simple names that have already been bound.
        Private _boundSimpleNames As HashSet(Of Integer)
 
        ' The largest position that has been bound.
        Private _lastBoundSimpleName As Integer = -1
 
        Public Overrides Sub CheckSimpleNameBindingOrder(node As SimpleNameSyntax)
            If _checkSimpleNameBindingOrder Then
                Dim position = node.SpanStart
 
                ' There are cases where we bind the same name multiple times -- for example, For loop
                ' variables, and debug checks with local type inference. Hence we only check the first time we 
                ' see a simple name.
                If Not _boundSimpleNames.Contains(position) Then
                    ' If this assert fires, it indicates that simple names are not being bound in order.
                    ' This indicates that binding with Option Explicit Off likely will exhibit a bug.
                    Debug.Assert(position >= _lastBoundSimpleName, "Did not bind simple names in order. Option Explicit Off probably will not behave correctly.")
 
                    _boundSimpleNames.Add(position)
                    _lastBoundSimpleName = Math.Max(_lastBoundSimpleName, position)
                End If
            End If
        End Sub
 
        ' We require simple name binding order checks to be enabled, because the SemanticModel APIs
        ' will bind things not in order. We only enable them during binding of a full method body or lambda body.
        Public Overrides Sub EnableSimpleNameBindingOrderChecks(enable As Boolean)
            If enable Then
                Debug.Assert(Not _checkSimpleNameBindingOrder)
                Debug.Assert(_boundSimpleNames Is Nothing)
                _boundSimpleNames = New HashSet(Of Integer)
                _checkSimpleNameBindingOrder = True
            Else
                _boundSimpleNames = Nothing
                _checkSimpleNameBindingOrder = False
            End If
        End Sub
#End If
 
        Public Class LabelVisitor
            Inherits StatementSyntaxWalker
 
            Private ReadOnly _labels As ArrayBuilder(Of SourceLabelSymbol)
            Private ReadOnly _containingMethod As MethodSymbol
            Private ReadOnly _binder As Binder
 
            Public Sub New(labels As ArrayBuilder(Of SourceLabelSymbol), containingMethod As MethodSymbol, binder As Binder)
                Me._labels = labels
                Me._containingMethod = containingMethod
                Me._binder = binder
            End Sub
 
            Public Overrides Sub VisitLabelStatement(node As LabelStatementSyntax)
                _labels.Add(New SourceLabelSymbol(node.LabelToken, _containingMethod, _binder))
            End Sub
        End Class
    End Class
 
End Namespace