' Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license 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 ''' ''' 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 ''' ''' #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 ''' ''' 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. ''' 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 DiagnosticBag) 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 DiagnosticBag) As BoundExpression ' Need this for ImplicitVariableBinder created by SpeculativeBinder. Return Me.ContainingBinder.BindFunctionAggregationExpression([function], diagnostics) End Function ''' ''' Disallow additional local variable declaration (make binder frozen) ''' and report delayed shadowing diagnostics. ''' ''' Public Overrides Sub DisallowFurtherImplicitVariableDeclaration(diagnostics As DiagnosticBag) #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 ''' ''' True if implicit variable declaration is done (binder is frozen and doesn't ''' allow additional implicit variable declaration) ''' Public Overrides ReadOnly Property AllImplicitVariableDeclarationsAreHandled As Boolean Get Return _frozen End Get End Property ''' ''' True if we are in a place that allows implicit variable declaration. This binder ''' implies that. ''' Public Overrides ReadOnly Property ImplicitVariableDeclarationAllowed As Boolean Get Return True End Get End Property ''' ''' Get all implicitly declared variables that were declared in this method body. The binder ''' must be frozen before this can be obtained. ''' 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 ''' ''' Declare an implicit local variable. The type of the local is determined ''' by the type character (if any) on the variable. ''' Public Overrides Function DeclareImplicitLocalVariable(nameSyntax As IdentifierNameSyntax, diagnostics As DiagnosticBag) 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 ''' ''' 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. ''' 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 useSiteDiagnostics As HashSet(Of DiagnosticInfo)) #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, useSiteDiagnostics)) 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