File: Binding\ImplicitVariableBinder.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
 
Namespace Microsoft.CodeAnalysis.VisualBasic
#If DEBUG Then
    ''' <summary>
    ''' The ImplicitVariableBinder manages implicitly declared local variables in VB.
    ''' Unlike other Binders, the ImplicitVariableBinder is observably 
    ''' mutable -- as new implicit local variables are declared, those variables will be
    ''' found by lookup operations on the binder. This mutability requires that use of the 
    ''' ImplicitVariableBinder be treated with somewhat more care than other binders.
    ''' 
    ''' The implicit variable binder is placed immediately outside the method body binder
    ''' when the method body binder is created.
    ''' 
    ''' Furthermore, the semantics of binding of implicitly declared local variables in VB
    ''' are order dependent because of type characters:
    '''     x$ = "hello": x = "hi"   ' OK
    '''     x = "hi": X$ = "hello"   ' error: x is of type Object.
    ''' 
    ''' An ImplicitVariableBinder can be frozen, at which point additional variables cannot
    ''' be declared. This should be done once an entire method body is bound.
    ''' 
    ''' Thus, it is important that only one thread at a time be allowed to access the implicit variable binder for
    ''' declaration (once frozen, it is OK for multiple threads to do lookups.)
    ''' 
    ''' In Debug, Asserts validate these rules.
    ''' 
    ''' Additional assert to make sure that declarations are handled in order is handled by 
    ''' <see cref="ExecutableCodeBinder.CheckSimpleNameBindingOrder"/>
    ''' </summary>
#End If
    Friend Class ImplicitVariableBinder
        Inherits Binder
 
        Private ReadOnly _containerOfLocals As Symbol
 
        Private _frozen As Boolean
        Private _implicitLocals As Dictionary(Of String, LocalSymbol)
        Private _possiblyShadowingVariables As MultiDictionary(Of String, ShadowedVariableInfo)
 
#If DEBUG Then
        ' All declarations for a ImplicitVariableBinder should occur on the same thread.
        ' It is OK for lookup to occur on a different thread, if the binder has
        ' been frozen. -1 when not yet initialized.
        Private _threadIdForDeclaration As Integer = -1
#End If
 
        ''' <summary>
        ''' If Option Explicit is Off for this source file, then implicit variable declaration will be allowed
        ''' in this binder. "containerOfLocals" is the container for implicitly declared variables.
        ''' </summary>
        Public Sub New(containingBinder As Binder, containerOfLocals As Symbol)
            MyBase.New(containingBinder)
            Debug.Assert(containingBinder.OptionExplicit = False)
 
            _containerOfLocals = containerOfLocals
            _frozen = False
        End Sub
 
        Friend Overrides Function BindGroupAggregationExpression(group As GroupAggregationSyntax, diagnostics As BindingDiagnosticBag) As BoundExpression
            ' Need this for ImplicitVariableBinder created by SpeculativeBinder.
            Return Me.ContainingBinder.BindGroupAggregationExpression(group, diagnostics)
        End Function
 
        Friend Overrides Function BindFunctionAggregationExpression([function] As FunctionAggregationSyntax, diagnostics As BindingDiagnosticBag) As BoundExpression
            ' Need this for ImplicitVariableBinder created by SpeculativeBinder.
            Return Me.ContainingBinder.BindFunctionAggregationExpression([function], diagnostics)
        End Function
 
        ''' <summary>
        ''' Disallow additional local variable declaration (make binder frozen)
        ''' and report delayed shadowing diagnostics.
        ''' </summary>
        ''' <remarks></remarks>
        Public Overrides Sub DisallowFurtherImplicitVariableDeclaration(diagnostics As BindingDiagnosticBag)
#If DEBUG Then
            CheckVariableDeclarationOnSingleThread()
#End If
 
            If Not _frozen Then
                _frozen = True
 
                If _implicitLocals IsNot Nothing AndAlso _possiblyShadowingVariables IsNot Nothing Then
                    For Each localName As String In _implicitLocals.Keys
                        For Each shadowingVariableInfo In _possiblyShadowingVariables(localName)
                            ' An already declared variable in an enclosed block is shadowing this new implicit variable. 
                            ' Report an error at THAT declaration's location.
                            ReportDiagnostic(diagnostics, shadowingVariableInfo.Location, shadowingVariableInfo.ErrorId, shadowingVariableInfo.Name)
                        Next
                    Next
                End If
            End If
        End Sub
 
        ''' <summary>
        ''' True if implicit variable declaration is done (binder is frozen and doesn't
        ''' allow additional implicit variable declaration)
        ''' </summary>
        Public Overrides ReadOnly Property AllImplicitVariableDeclarationsAreHandled As Boolean
            Get
                Return _frozen
            End Get
        End Property
 
        ''' <summary>
        ''' True if we are in a place that allows implicit variable declaration. This binder
        ''' implies that.
        ''' </summary>
        Public Overrides ReadOnly Property ImplicitVariableDeclarationAllowed As Boolean
            Get
                Return True
            End Get
        End Property
 
        ''' <summary>
        ''' Get all implicitly declared variables that were declared in this method body. The binder
        ''' must be frozen before this can be obtained.
        ''' </summary>
        Public Overrides ReadOnly Property ImplicitlyDeclaredVariables As ImmutableArray(Of LocalSymbol)
            Get
                Debug.Assert(_frozen)
 
                If _implicitLocals Is Nothing Then
                    Return ImmutableArray(Of LocalSymbol).Empty
                Else
                    Dim builder As ArrayBuilder(Of LocalSymbol) = ArrayBuilder(Of LocalSymbol).GetInstance()
                    builder.AddRange(_implicitLocals.Values)
                    Return builder.ToImmutableAndFree()
                End If
            End Get
        End Property
 
        ''' <summary>
        ''' Declare an implicit local variable. The type of the local is determined
        ''' by the type character (if any) on the variable.
        ''' </summary>
        Public Overrides Function DeclareImplicitLocalVariable(nameSyntax As IdentifierNameSyntax, diagnostics As BindingDiagnosticBag) As LocalSymbol
#If DEBUG Then
            Debug.Assert(Not _frozen)
            CheckVariableDeclarationOnSingleThread()
#End If
 
            ' Type is always Object, unless type character specified.
            Dim localSpecialType As SpecialType = SpecialType.System_Object
            If nameSyntax.Identifier.GetTypeCharacter() <> TypeCharacter.None Then
                Dim unused As String = Nothing
                localSpecialType = GetSpecialTypeForTypeCharacter(nameSyntax.Identifier.GetTypeCharacter(), unused)
            End If
 
            Dim localVar = LocalSymbol.Create(_containerOfLocals,
                                           Me,
                                           nameSyntax.Identifier,
                                           LocalDeclarationKind.ImplicitVariable,
                                           GetSpecialType(localSpecialType, nameSyntax, diagnostics))
 
            If _implicitLocals Is Nothing Then
                _implicitLocals = New Dictionary(Of String, LocalSymbol)(IdentifierComparison.Comparer)
            End If
 
            _implicitLocals.Add(nameSyntax.Identifier.ValueText, localVar)
            Return localVar
        End Function
 
        ''' <summary>
        ''' A tricky problem is reporting the "Variable 'x' hides a variable in an enclosing block" message if the variable in
        ''' an enclosing block is an implicit variable that hasn't been declared yet. We handle this by remembering any variable
        ''' declarations in enclosed blocks, and then report the error when the implicit variable is declared.
        ''' </summary>
        Public Sub RememberPossibleShadowingVariable(name As String, syntax As SyntaxNodeOrToken, errorId As ERRID)
#If DEBUG Then
            Debug.Assert(Not _frozen)
            CheckVariableDeclarationOnSingleThread()
#End If
 
            If _possiblyShadowingVariables Is Nothing Then
                _possiblyShadowingVariables = New MultiDictionary(Of String, ShadowedVariableInfo)(IdentifierComparison.Comparer)
            End If
 
            _possiblyShadowingVariables.Add(name, New ShadowedVariableInfo(name, syntax.GetLocation(), errorId))
        End Sub
 
        ' Structure for saving information about possible shadowing variables.
        Private Structure ShadowedVariableInfo
            Public ReadOnly Name As String
            Public ReadOnly Location As Location
            Public ReadOnly ErrorId As ERRID
            Public Sub New(name As String, location As Location, errorId As ERRID)
                Me.Name = name
                Me.Location = location
                Me.ErrorId = errorId
            End Sub
        End Structure
 
        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))
#If DEBUG Then
            CheckVariableDeclarationOnSingleThread()
#End If
            ' locals are always arity 0, and never types and namespaces.
            Dim localSymbol As LocalSymbol = Nothing
            If _implicitLocals IsNot Nothing AndAlso (options And (LookupOptions.NamespacesOrTypesOnly Or LookupOptions.LabelsOnly)) = 0 Then
                If _implicitLocals.TryGetValue(name, localSymbol) Then
                    lookupResult.SetFrom(CheckViability(localSymbol, arity, options, Nothing, useSiteInfo))
                End If
            End If
 
            Return
        End Sub
 
        Friend Overrides Sub AddLookupSymbolsInfoInSingleBinder(nameSet As LookupSymbolsInfo,
                                                                   options As LookupOptions,
                                                                   originalBinder As Binder)
            Debug.Assert(_frozen)
 
            If _implicitLocals IsNot Nothing AndAlso
               (options And (LookupOptions.NamespacesOrTypesOnly Or LookupOptions.LabelsOnly)) = 0 Then
 
                For Each localSymbol In _implicitLocals.Values
                    If originalBinder.CanAddLookupSymbolInfo(localSymbol, options, nameSet, Nothing) Then
                        nameSet.AddSymbol(localSymbol, localSymbol.Name, 0)
                    End If
                Next
            End If
        End Sub
 
#If DEBUG Then
        ' Check that variable declarations all occur in a single thread.
        Private Sub CheckVariableDeclarationOnSingleThread()
            If Not _frozen Then
                ' First time we're called, get the thread id. Subsequent times, check it hasn't changed.
                If _threadIdForDeclaration = -1 Then
                    Interlocked.CompareExchange(_threadIdForDeclaration, Environment.CurrentManagedThreadId, -1)
                End If
 
                Debug.Assert(_threadIdForDeclaration = Environment.CurrentManagedThreadId)
            End If
        End Sub
#End If
 
    End Class
End Namespace