/
githubmirror
/
roslyn
Обзор
Документация
Войти
/
githubmirror
/
roslyn
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/ExpressionEvaluator/VisualBasic/Source/ExpressionCompiler/CompilationContext.vb
1 497 строк
77 KB
Cyrus Najmabadi
Organize VB imports
27 июн 2025, 21:53
27 июн 2025, 21:53
e9c47fd
Код
Авторство
О чём код?
' 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 System.Runtime.InteropServices Imports System.Threading Imports Microsoft.CodeAnalysis.Collections Imports Microsoft.CodeAnalysis.Debugging Imports Microsoft.CodeAnalysis.Emit Imports Microsoft.CodeAnalysis.ExpressionEvaluator Imports Microsoft.CodeAnalysis.PooledObjects Imports Microsoft.CodeAnalysis.VisualBasic.Symbols Imports Microsoft.CodeAnalysis.VisualBasic.Symbols.Metadata.PE Imports Microsoft.CodeAnalysis.VisualBasic.Syntax Imports Microsoft.VisualStudio.Debugger.Clr Imports Microsoft.VisualStudio.Debugger.Evaluation.ClrCompilation Imports Roslyn.Utilities Namespace Microsoft.CodeAnalysis.VisualBasic.ExpressionEvaluator Friend NotInheritable Class CompilationContext Private Shared ReadOnly s_fullNameFormat As New SymbolDisplayFormat( globalNamespaceStyle:=SymbolDisplayGlobalNamespaceStyle.Omitted, typeQualificationStyle:=SymbolDisplayTypeQualificationStyle.NameAndContainingTypesAndNamespaces, genericsOptions:=SymbolDisplayGenericsOptions.IncludeTypeParameters, miscellaneousOptions:=SymbolDisplayMiscellaneousOptions.EscapeKeywordIdentifiers Or SymbolDisplayMiscellaneousOptions.UseSpecialTypes) Friend Shared ReadOnly BackstopBinder As Binder = New BackstopBinder() Friend ReadOnly Compilation As VisualBasicCompilation Friend ReadOnly NamespaceBinder As Binder ' Internal for test purposes. Private ReadOnly _currentFrame As MethodSymbol Private ReadOnly _locals As ImmutableArray(Of LocalSymbol) Private ReadOnly _displayClassVariables As ImmutableDictionary(Of String, DisplayClassVariable) Private ReadOnly _sourceMethodParametersInOrder As ImmutableArray(Of String) Private ReadOnly _localsForBinding As ImmutableArray(Of LocalSymbol) Private ReadOnly _methodNotType As Boolean Private ReadOnly _voidType As NamedTypeSymbol ''' <summary> ''' Create a context to compile expressions within a method scope. ''' </summary> Friend Sub New( compilation As VisualBasicCompilation, currentFrame As MethodSymbol, currentSourceMethod As MethodSymbol, locals As ImmutableArray(Of LocalSymbol), inScopeHoistedLocalSlots As ImmutableSortedSet(Of Integer), methodDebugInfo As MethodDebugInfo(Of TypeSymbol, LocalSymbol), withSyntax As Boolean) _currentFrame = currentFrame Debug.Assert(compilation.Options.RootNamespace = "") ' Default value. Debug.Assert(methodDebugInfo.ExternAliasRecords.IsDefaultOrEmpty) Dim originalCompilation = compilation If withSyntax AndAlso compilation.Options.SuppressEmbeddedDeclarations AndAlso compilation.SyntaxTrees.IsEmpty Then ' We need to add an empty tree to the compilation as ' a workaround for https://github.com/dotnet/roslyn/issues/16885. compilation = compilation.AddSyntaxTrees(VisualBasicSyntaxTree.Dummy) End If Dim defaultNamespaceName As String = methodDebugInfo.DefaultNamespaceName ' Note We don't need to try to bind this string because this is analogous to passing ' a command-line argument - as long as the syntax is valid, an appropriate symbol will ' be created for us. If defaultNamespaceName IsNot Nothing AndAlso TryParseDottedName(defaultNamespaceName, Nothing) Then compilation = compilation.WithOptions(compilation.Options.WithRootNamespace(defaultNamespaceName)) End If If compilation Is originalCompilation Then compilation = compilation.Clone() End If Me.Compilation = compilation ' Each expression compile should use a unique compilation ' to ensure expression-specific synthesized members can be ' added (anonymous types, for instance). Debug.Assert(Me.Compilation IsNot originalCompilation) NamespaceBinder = CreateBinderChain( Me.Compilation, currentFrame.ContainingNamespace, methodDebugInfo.ImportRecordGroups) _voidType = Me.Compilation.GetSpecialType(SpecialType.System_Void) _methodNotType = Not locals.IsDefault If _methodNotType Then _locals = locals _sourceMethodParametersInOrder = GetSourceMethodParametersInOrder(currentFrame, currentSourceMethod) Dim displayClassVariableNamesInOrder As ImmutableArray(Of String) = Nothing GetDisplayClassVariables( currentFrame, currentSourceMethod, locals, inScopeHoistedLocalSlots, _sourceMethodParametersInOrder, displayClassVariableNamesInOrder, _displayClassVariables) Debug.Assert(displayClassVariableNamesInOrder.Length = _displayClassVariables.Count) _localsForBinding = GetLocalsForBinding(locals, displayClassVariableNamesInOrder, _displayClassVariables) Else _locals = ImmutableArray(Of LocalSymbol).Empty _displayClassVariables = ImmutableDictionary(Of String, DisplayClassVariable).Empty _localsForBinding = ImmutableArray(Of LocalSymbol).Empty End If ' Assert that the cheap check for "Me" is equivalent to the expensive check for "Me". Debug.Assert( _displayClassVariables.ContainsKey(GeneratedNameConstants.HoistedMeName) = _displayClassVariables.Values.Any(Function(v) v.Kind = DisplayClassVariableKind.Me)) End Sub Friend Function Compile( syntax As ExecutableStatementSyntax, typeName As String, methodName As String, aliases As ImmutableArray(Of [Alias]), testData As Microsoft.CodeAnalysis.CodeGen.CompilationTestData, diagnostics As DiagnosticBag, <Out> ByRef synthesizedMethod As EEMethodSymbol) As CommonPEModuleBuilder Dim objectType = Me.Compilation.GetSpecialType(SpecialType.System_Object) Dim synthesizedType = New EENamedTypeSymbol( Me.Compilation.SourceAssembly.GlobalNamespace, objectType, syntax, _currentFrame, typeName, methodName, Me, Function(method As EEMethodSymbol, diags As DiagnosticBag, ByRef properties As ResultProperties) Dim hasDisplayClassMe = _displayClassVariables.ContainsKey(GeneratedNameConstants.HoistedMeName) Dim bindAsExpression = syntax.Kind = SyntaxKind.PrintStatement Dim binder = ExtendBinderChain( aliases, method, NamespaceBinder, hasDisplayClassMe, _methodNotType, allowImplicitDeclarations:=Not bindAsExpression) Return If(bindAsExpression, BindExpression(binder, DirectCast(syntax, PrintStatementSyntax).Expression, diags, properties), BindStatement(binder, syntax, diags, properties)) End Function) Dim moduleBuilder = CreateModuleBuilder( Me.Compilation, synthesizedType.Methods, additionalTypes:=ImmutableArray.Create(DirectCast(synthesizedType, NamedTypeSymbol)), testData:=testData, diagnostics:=diagnostics) Debug.Assert(moduleBuilder IsNot Nothing) Me.Compilation.Compile( moduleBuilder, emittingPdb:=False, diagnostics:=diagnostics, filterOpt:=Nothing, cancellationToken:=CancellationToken.None) If diagnostics.HasAnyErrors() Then synthesizedMethod = Nothing Return Nothing End If synthesizedMethod = DirectCast(synthesizedType.Methods(0), EEMethodSymbol) Return moduleBuilder End Function Private Shared Function GetNextMethodName(builder As ArrayBuilder(Of MethodSymbol)) As String ' NOTE: These names are consumed by Concord, so there's no native precedent. Return String.Format("<>m{0}", builder.Count) End Function ''' <summary> ''' Generate a class containing methods that represent ''' the set of arguments and locals at the current scope. ''' </summary> Friend Function CompileGetLocals( typeName As String, localBuilder As ArrayBuilder(Of LocalAndMethod), argumentsOnly As Boolean, aliases As ImmutableArray(Of [Alias]), testData As Microsoft.CodeAnalysis.CodeGen.CompilationTestData, diagnostics As DiagnosticBag) As CommonPEModuleBuilder Dim objectType = Me.Compilation.GetSpecialType(SpecialType.System_Object) Dim allTypeParameters = GetAllTypeParameters(_currentFrame) Dim additionalTypes = ArrayBuilder(Of NamedTypeSymbol).GetInstance() Dim syntax = SyntaxFactory.IdentifierName(SyntaxFactory.MissingToken(SyntaxKind.IdentifierToken)) Dim typeVariablesType As EENamedTypeSymbol = Nothing If Not argumentsOnly AndAlso allTypeParameters.Length > 0 Then ' Generate a generic type with matching type parameters. ' A null instance of this type will be used to represent ' the "Type variables" local. typeVariablesType = New EENamedTypeSymbol( Me.Compilation.SourceModule.GlobalNamespace, objectType, syntax, _currentFrame, ExpressionCompilerConstants.TypeVariablesClassName, Function(m, t) Dim constructor As New EEConstructorSymbol(t) constructor.SetParameters(ImmutableArray(Of ParameterSymbol).Empty) Return ImmutableArray.Create(Of MethodSymbol)(constructor) End Function, allTypeParameters, Function(t1, t2) allTypeParameters.SelectAsArray(Function(tp, i, t) DirectCast(New SimpleTypeParameterSymbol(t, i, tp.GetUnmangledName()), TypeParameterSymbol), t2)) additionalTypes.Add(typeVariablesType) End If Dim synthesizedType As New EENamedTypeSymbol( Me.Compilation.SourceModule.GlobalNamespace, objectType, syntax, _currentFrame, typeName, Function(m, container) Dim methodBuilder = ArrayBuilder(Of MethodSymbol).GetInstance() If Not argumentsOnly Then If aliases.Length > 0 Then ' Pseudo-variables: $exception, $ReturnValue, etc. Dim typeNameDecoder = New EETypeNameDecoder(Me.Compilation, DirectCast(_currentFrame.ContainingModule, PEModuleSymbol)) For Each [alias] As [Alias] In aliases If [alias].IsReturnValueWithoutIndex() Then Debug.Assert(aliases.Count(Function(a) a.Kind = DkmClrAliasKind.ReturnValue) > 1) Continue For End If Dim methodName = GetNextMethodName(methodBuilder) Dim local = PlaceholderLocalSymbol.Create(typeNameDecoder, _currentFrame, [alias]) ' Skip pseudo-variables with errors. If local.GetUseSiteInfo().DiagnosticInfo?.Severity = DiagnosticSeverity.Error Then Continue For End If Dim aliasMethod = Me.CreateMethod( container, methodName, syntax, Function(method As EEMethodSymbol, diags As DiagnosticBag, ByRef properties As ResultProperties) Dim expression = New BoundLocal(syntax, local, isLValue:=False, type:=local.Type) properties = Nothing Return New BoundReturnStatement(syntax, expression, Nothing, Nothing).MakeCompilerGenerated() End Function) localBuilder.Add(MakeLocalAndMethod(local, aliasMethod, If(local.IsReadOnly, DkmClrCompilationResultFlags.ReadOnlyResult, DkmClrCompilationResultFlags.None))) methodBuilder.Add(aliasMethod) Next End If ' "Me" for non-shared methods that are not display class methods ' or display class methods where the display class contains "$VB$Me". If Not m.IsShared AndAlso (Not m.ContainingType.IsClosureOrStateMachineType() OrElse _displayClassVariables.ContainsKey(GeneratedNames.MakeStateMachineCapturedMeName())) Then Dim methodName = GetNextMethodName(methodBuilder) Dim method = Me.GetMeMethod(container, methodName) localBuilder.Add(New VisualBasicLocalAndMethod("Me", "Me", method, DkmClrCompilationResultFlags.None)) ' NOTE: writable in Dev11. methodBuilder.Add(method) End If End If Dim itemsAdded = PooledHashSet(Of String).GetInstance() ' Method parameters Dim parameterIndex = If(m.IsShared, 0, 1) For Each parameter In m.Parameters Dim parameterName As String = parameter.Name If GeneratedNameParser.GetKind(parameterName) = GeneratedNameKind.None Then AppendParameterAndMethod(localBuilder, methodBuilder, parameter, container, parameterIndex) itemsAdded.Add(parameterName) End If parameterIndex += 1 Next ' In case of iterator Or async state machine, the 'm' method has no parameters ' but the source method can have parameters to iterate over. If itemsAdded.Count = 0 AndAlso _sourceMethodParametersInOrder.Length <> 0 Then Dim localsDictionary = PooledDictionary(Of String, (LocalSymbol, Integer)).GetInstance() Dim localIndex = 0 For Each local In _localsForBinding localsDictionary.Add(local.Name, (local, localIndex)) localIndex += 1 Next For Each argumentName In _sourceMethodParametersInOrder Dim localSymbolAndIndex As (LocalSymbol, Integer) = Nothing If localsDictionary.TryGetValue(argumentName, localSymbolAndIndex) Then itemsAdded.Add(argumentName) Dim local = localSymbolAndIndex.Item1 AppendLocalAndMethod(localBuilder, methodBuilder, local, container, localSymbolAndIndex.Item2, GetLocalResultFlags(local)) End If Next localsDictionary.Free() End If If Not argumentsOnly Then ' Locals. Dim localIndex As Integer = 0 For Each local In _localsForBinding If Not itemsAdded.Contains(local.Name) Then AppendLocalAndMethod(localBuilder, methodBuilder, local, container, localIndex, GetLocalResultFlags(local)) End If localIndex += 1 Next ' "Type variables". If typeVariablesType IsNot Nothing Then Dim methodName = GetNextMethodName(methodBuilder) Dim returnType = typeVariablesType.Construct(ImmutableArrayExtensions.Cast(Of TypeParameterSymbol, TypeSymbol)(allTypeParameters)) Dim method = Me.GetTypeVariableMethod(container, methodName, returnType) localBuilder.Add(New VisualBasicLocalAndMethod( ExpressionCompilerConstants.TypeVariablesLocalName, ExpressionCompilerConstants.TypeVariablesLocalName, method, DkmClrCompilationResultFlags.ReadOnlyResult)) methodBuilder.Add(method) End If End If itemsAdded.Free() Return methodBuilder.ToImmutableAndFree() End Function) additionalTypes.Add(synthesizedType) Dim moduleBuilder = CreateModuleBuilder( Me.Compilation, synthesizedType.Methods, additionalTypes:=additionalTypes.ToImmutableAndFree(), testData:=testData, diagnostics:=diagnostics) Debug.Assert(moduleBuilder IsNot Nothing) Me.Compilation.Compile( moduleBuilder, emittingPdb:=False, diagnostics:=diagnostics, filterOpt:=Nothing, cancellationToken:=CancellationToken.None) Return If(diagnostics.HasAnyErrors(), Nothing, moduleBuilder) End Function Private Sub AppendLocalAndMethod( localBuilder As ArrayBuilder(Of LocalAndMethod), methodBuilder As ArrayBuilder(Of MethodSymbol), local As LocalSymbol, container As EENamedTypeSymbol, localIndex As Integer, resultFlags As DkmClrCompilationResultFlags) Dim methodName = GetNextMethodName(methodBuilder) Dim method = Me.GetLocalMethod(container, methodName, local.Name, localIndex) localBuilder.Add(MakeLocalAndMethod(local, method, resultFlags)) methodBuilder.Add(method) End Sub Private Sub AppendParameterAndMethod( localBuilder As ArrayBuilder(Of LocalAndMethod), methodBuilder As ArrayBuilder(Of MethodSymbol), parameter As ParameterSymbol, container As EENamedTypeSymbol, parameterIndex As Integer) ' Note: The native EE doesn't do this, but if we don't escape keyword identifiers, ' the ResultProvider needs to be able to disambiguate cases Like "Me" And "[Me]", ' which it can't do correctly without semantic information. Dim name = SyntaxHelpers.EscapeKeywordIdentifiers(parameter.Name) Dim methodName = GetNextMethodName(methodBuilder) Dim method = Me.GetParameterMethod(container, methodName, parameter.Name, parameterIndex) localBuilder.Add(New VisualBasicLocalAndMethod(name, name, method, DkmClrCompilationResultFlags.None)) methodBuilder.Add(method) End Sub Private Shared Function MakeLocalAndMethod(local As LocalSymbol, method As MethodSymbol, flags As DkmClrCompilationResultFlags) As LocalAndMethod ' Note: The native EE doesn't do this, but if we don't escape keyword identifiers, ' the ResultProvider needs to be able to disambiguate cases Like "Me" And "[Me]", ' which it can't do correctly without semantic information. Dim escapedName = SyntaxHelpers.EscapeKeywordIdentifiers(local.Name) Dim displayName = If(TryCast(local, PlaceholderLocalSymbol)?.DisplayName, escapedName) Return New VisualBasicLocalAndMethod(escapedName, displayName, method, flags) End Function Private Shared Function CreateModuleBuilder( compilation As VisualBasicCompilation, methods As ImmutableArray(Of MethodSymbol), additionalTypes As ImmutableArray(Of NamedTypeSymbol), testData As Microsoft.CodeAnalysis.CodeGen.CompilationTestData, diagnostics As DiagnosticBag) As EEAssemblyBuilder ' Each assembly must have a unique name. Dim emitOptions = New EmitOptions(outputNameOverride:=ExpressionCompilerUtilities.GenerateUniqueName()) Dim runtimeMetadataVersion = compilation.GetRuntimeMetadataVersion() Dim serializationProperties = compilation.ConstructModuleSerializationProperties(emitOptions, runtimeMetadataVersion) Return New EEAssemblyBuilder(compilation.SourceAssembly, emitOptions, methods, serializationProperties, additionalTypes, testData) End Function Friend Function CreateMethod( container As EENamedTypeSymbol, methodName As String, syntax As VisualBasicSyntaxNode, generateMethodBody As GenerateMethodBody) As EEMethodSymbol Return New EEMethodSymbol( Compilation, container, methodName, syntax.GetLocation(), _currentFrame, _locals, _localsForBinding, _displayClassVariables, _voidType, generateMethodBody) End Function Private Function GetLocalMethod(container As EENamedTypeSymbol, methodName As String, localName As String, localIndex As Integer) As EEMethodSymbol Dim syntax = SyntaxFactory.IdentifierName(localName) Return Me.CreateMethod( container, methodName, syntax, Function(method As EEMethodSymbol, diagnostics As DiagnosticBag, ByRef properties As ResultProperties) Dim local = method.LocalsForBinding(localIndex) Dim expression = New BoundLocal(syntax, local, isLValue:=False, type:=local.Type) properties = Nothing Return New BoundReturnStatement(syntax, expression, Nothing, Nothing).MakeCompilerGenerated() End Function) End Function Private Function GetParameterMethod(container As EENamedTypeSymbol, methodName As String, parameterName As String, parameterIndex As Integer) As EEMethodSymbol Dim syntax = SyntaxFactory.IdentifierName(parameterName) Return Me.CreateMethod( container, methodName, syntax, Function(method As EEMethodSymbol, diagnostics As DiagnosticBag, ByRef properties As ResultProperties) Dim parameter = method.Parameters(parameterIndex) Dim expression = New BoundParameter(syntax, parameter, isLValue:=False, type:=parameter.Type) properties = Nothing Return New BoundReturnStatement(syntax, expression, Nothing, Nothing).MakeCompilerGenerated() End Function) End Function Private Function GetMeMethod(container As EENamedTypeSymbol, methodName As String) As EEMethodSymbol Dim syntax = SyntaxFactory.MeExpression() Return Me.CreateMethod( container, methodName, syntax, Function(method As EEMethodSymbol, diagnostics As DiagnosticBag, ByRef properties As ResultProperties) Dim expression = New BoundMeReference(syntax, GetNonClosureOrStateMachineContainer(container.SubstitutedSourceType)) properties = Nothing Return New BoundReturnStatement(syntax, expression, Nothing, Nothing).MakeCompilerGenerated() End Function) End Function Private Function GetTypeVariableMethod(container As EENamedTypeSymbol, methodName As String, typeVariablesType As NamedTypeSymbol) As EEMethodSymbol Dim syntax = SyntaxFactory.IdentifierName(SyntaxFactory.MissingToken(SyntaxKind.IdentifierToken)) Return Me.CreateMethod( container, methodName, syntax, Function(method As EEMethodSymbol, diagnostics As DiagnosticBag, ByRef properties As ResultProperties) Dim type = method.TypeMap.SubstituteNamedType(typeVariablesType) Dim expression = New BoundObjectCreationExpression(syntax, type.InstanceConstructors(0), ImmutableArray(Of BoundExpression).Empty, Nothing, type) properties = Nothing Return New BoundReturnStatement(syntax, expression, Nothing, Nothing).MakeCompilerGenerated() End Function) End Function Private Shared Function BindExpression(binder As Binder, syntax As ExpressionSyntax, diagnostics As DiagnosticBag, <Out> ByRef resultProperties As ResultProperties) As BoundStatement Dim bindingDiagnostics = BindingDiagnosticBag.GetInstance(withDiagnostics:=True, withDependencies:=False) Dim expression = binder.BindExpression(syntax, bindingDiagnostics) Dim flags = DkmClrCompilationResultFlags.None If Not IsAssignableExpression(binder, expression) Then flags = flags Or DkmClrCompilationResultFlags.ReadOnlyResult End If Try If MayHaveSideEffectsVisitor.MayHaveSideEffects(expression) Then flags = flags Or DkmClrCompilationResultFlags.PotentialSideEffect End If Catch ex As BoundTreeVisitor.CancelledByStackGuardException ex.AddAnError(diagnostics) End Try If IsStatement(expression) Then expression = binder.ReclassifyInvocationExpressionAsStatement(expression, bindingDiagnostics) Else expression = binder.MakeRValue(expression, bindingDiagnostics) End If diagnostics.AddRange(bindingDiagnostics.DiagnosticBag) bindingDiagnostics.Free() Select Case expression.Type.SpecialType Case SpecialType.System_Void Debug.Assert(expression.ConstantValueOpt Is Nothing) resultProperties = expression.ExpressionSymbol.GetResultProperties(flags, isConstant:=False) Return New BoundExpressionStatement(syntax, expression).MakeCompilerGenerated() Case SpecialType.System_Boolean flags = flags Or DkmClrCompilationResultFlags.BoolResult End Select resultProperties = expression.ExpressionSymbol.GetResultProperties(flags, expression.ConstantValueOpt IsNot Nothing) Return New BoundReturnStatement(syntax, expression, Nothing, Nothing).MakeCompilerGenerated() End Function Private Shared Function IsAssignableExpression(binder As Binder, expression As BoundExpression) As Boolean Dim diagnostics = BindingDiagnosticBag.GetInstance(withDiagnostics:=True, withDependencies:=False) Dim value = binder.ReclassifyAsValue(expression, diagnostics) Dim result = False If Binder.IsValidAssignmentTarget(value) AndAlso Not diagnostics.HasAnyErrors() Then Dim isError = False binder.AdjustAssignmentTarget(value.Syntax, value, diagnostics, isError) Debug.Assert(isError = diagnostics.HasAnyErrors()) result = Not isError End If diagnostics.Free() Return result End Function Private Shared Function IsStatement(expression As BoundExpression) As Boolean Select Case expression.Kind Case BoundKind.Call Return IsCallStatement(DirectCast(expression, BoundCall)) Case BoundKind.ConditionalAccess Dim [call] = TryCast(DirectCast(expression, BoundConditionalAccess).AccessExpression, BoundCall) Return ([call] IsNot Nothing) AndAlso IsCallStatement([call]) Case Else Return False End Select End Function Private Shared Function IsCallStatement([call] As BoundCall) As Boolean Return [call].Method.IsSub End Function Private Shared Function BindStatement(binder As Binder, syntax As StatementSyntax, diagnostics As DiagnosticBag, <Out> ByRef resultProperties As ResultProperties) As BoundStatement resultProperties = New ResultProperties(DkmClrCompilationResultFlags.PotentialSideEffect Or DkmClrCompilationResultFlags.ReadOnlyResult) Dim builder = BindingDiagnosticBag.GetInstance(withDiagnostics:=True, withDependencies:=False) Dim result = binder.BindStatement(syntax, builder).MakeCompilerGenerated() diagnostics.AddRange(builder.DiagnosticBag) builder.Free() Return result End Function Private Shared Function CreateBinderChain( compilation As VisualBasicCompilation, [namespace] As NamespaceSymbol, importRecordGroups As ImmutableArray(Of ImmutableArray(Of ImportRecord))) As Binder Dim binder = BackstopBinder binder = New SuppressDiagnosticsBinder(binder) binder = New IgnoreAccessibilityBinder(binder) binder = New SourceModuleBinder(binder, DirectCast(compilation.Assembly.Modules(0), SourceModuleSymbol)) If Not importRecordGroups.IsEmpty Then binder = BuildImportedSymbolsBinder(binder, New NamespaceBinder(binder, compilation.GlobalNamespace), importRecordGroups) End If Dim stack = ArrayBuilder(Of String).GetInstance() Dim containingNamespace = [namespace] While containingNamespace IsNot Nothing stack.Push(containingNamespace.Name) containingNamespace = containingNamespace.ContainingNamespace End While ' PERF: we used to call compilation.GetCompilationNamespace on every iteration, ' but that involved walking up to the global namespace, which we have to do ' anyway. Instead, we'll inline the functionality into our own walk of the ' namespace chain. [namespace] = compilation.GlobalNamespace While stack.Count > 0 Dim namespaceName = stack.Pop() If namespaceName.Length > 0 Then ' We're re-getting the namespace, rather than using the one containing ' the current frame method, because we want the merged namespace [namespace] = [namespace].GetNestedNamespace(namespaceName) Debug.Assert([namespace] IsNot Nothing, "We worked backwards from symbols to names, but no symbol exists for name '" + namespaceName + "'") Else Debug.Assert([namespace] Is compilation.GlobalNamespace) End If binder = New NamespaceBinder(binder, [namespace]) End While stack.Free() Return binder End Function Private Shared Function ExtendBinderChain( aliases As ImmutableArray(Of [Alias]), method As EEMethodSymbol, binder As Binder, hasDisplayClassMe As Boolean, methodNotType As Boolean, allowImplicitDeclarations As Boolean) As Binder Dim substitutedSourceMethod = GetSubstitutedSourceMethod(method.SubstitutedSourceMethod, hasDisplayClassMe) Dim substitutedSourceType = substitutedSourceMethod.ContainingType Dim stack = ArrayBuilder(Of NamedTypeSymbol).GetInstance() Dim type = substitutedSourceType While type IsNot Nothing stack.Push(type) type = type.ContainingType End While While stack.Count > 0 substitutedSourceType = stack.Pop() binder = New EENamedTypeBinder(substitutedSourceType, binder) End While stack.Free() If substitutedSourceMethod.Arity > 0 Then binder = New MethodTypeParametersBinder(binder, substitutedSourceMethod.TypeArguments.SelectAsArray(Function(t) DirectCast(t, TypeParameterSymbol))) End If ' The "Type Context" is used when binding DebuggerDisplayAttribute expressions. ' We have chosen to explicitly disallow pseudo variables in that scenario. If methodNotType Then ' Method locals and parameters shadow pseudo-variables. Dim typeNameDecoder = New EETypeNameDecoder(binder.Compilation, DirectCast(substitutedSourceMethod.ContainingModule, PEModuleSymbol)) binder = New PlaceholderLocalBinder(aliases, method, typeNameDecoder, allowImplicitDeclarations, binder) End If ' Even if there are no parameters or locals, this has the effect of setting ' the containing member to the substituted source method. binder = New ParametersAndLocalsBinder(binder, method, substitutedSourceMethod) Return binder End Function Private Shared Function BuildImportedSymbolsBinder( containingBinder As Binder, importBinder As Binder, importRecordGroups As ImmutableArray(Of ImmutableArray(Of ImportRecord))) As Binder Dim projectLevelImportsBuilder As ArrayBuilder(Of NamespaceOrTypeAndImportsClausePosition) = Nothing Dim fileLevelImportsBuilder As ArrayBuilder(Of NamespaceOrTypeAndImportsClausePosition) = Nothing Dim projectLevelAliases As Dictionary(Of String, AliasAndImportsClausePosition) = Nothing Dim fileLevelAliases As Dictionary(Of String, AliasAndImportsClausePosition) = Nothing Dim projectLevelXmlImports As Dictionary(Of String, XmlNamespaceAndImportsClausePosition) = Nothing Dim fileLevelXmlImports As Dictionary(Of String, XmlNamespaceAndImportsClausePosition) = Nothing Debug.Assert(importRecordGroups.Length = 2) ' First file-level, then project-level. Dim fileLevelImportRecords = importRecordGroups(0) Dim projectLevelImportRecords = importRecordGroups(1) ' Use this to give the imports different positions Dim position = 0 For Each importRecord As ImportRecord In projectLevelImportRecords If AddImportForRecord( importRecord, importBinder, position, projectLevelImportsBuilder, projectLevelAliases, projectLevelXmlImports) Then position += 1 End If Next For Each importRecord As ImportRecord In fileLevelImportRecords If AddImportForRecord( importRecord, importBinder, position, fileLevelImportsBuilder, fileLevelAliases, fileLevelXmlImports) Then position += 1 End If Next ' BinderBuilder.CreateBinderForSourceFile creates separate binders for the project- and file-level ' imports. We'd do the same, but we don't have a SourceFileBinder to put in between and that ' violates some (very specific) assertions about the shape of the binder chain. Instead, we will ' manually resolve ties and then create a single set of binders. Dim binder = containingBinder Dim importsBuilder As ArrayBuilder(Of NamespaceOrTypeAndImportsClausePosition) If projectLevelImportsBuilder Is Nothing Then importsBuilder = fileLevelImportsBuilder ElseIf fileLevelImportsBuilder Is Nothing Then importsBuilder = projectLevelImportsBuilder Else importsBuilder = fileLevelImportsBuilder importsBuilder.AddRange(projectLevelImportsBuilder) projectLevelImportsBuilder.Free() End If If importsBuilder IsNot Nothing Then Dim [imports] As ImmutableArray(Of NamespaceOrTypeAndImportsClausePosition) = importsBuilder.ToImmutableAndFree() binder = New TypesOfImportedNamespacesMembersBinder(binder, [imports]) binder = New ImportedTypesAndNamespacesMembersBinder(binder, [imports]) End If Dim aliases = MergeAliases(projectLevelAliases, fileLevelAliases) If aliases IsNot Nothing Then binder = New ImportAliasesBinder(binder, aliases) End If Dim xmlImports = MergeAliases(projectLevelXmlImports, fileLevelXmlImports) If xmlImports IsNot Nothing Then binder = New XmlNamespaceImportsBinder(binder, xmlImports) End If Return binder End Function Private Shared Function AddImportForRecord( importRecord As ImportRecord, importBinder As Binder, position As Integer, ByRef importsBuilder As ArrayBuilder(Of NamespaceOrTypeAndImportsClausePosition), ByRef aliases As Dictionary(Of String, AliasAndImportsClausePosition), ByRef xmlImports As Dictionary(Of String, XmlNamespaceAndImportsClausePosition)) As Boolean Dim targetString = importRecord.TargetString ' NB: It appears that imports of generic types are not included in the PDB, so we never have to worry about parsing them. ' NB: Unlike in C# PDBs, the assembly name will not be present, so we have to just bind the string. Dim targetSyntax As NameSyntax = Nothing If Not String.IsNullOrEmpty(targetString) AndAlso ' CurrentNamespace may be an empty string, new-format types may be null. importRecord.TargetKind <> ImportTargetKind.XmlNamespace AndAlso Not TryParseDottedName(targetString, targetSyntax) Then Debug.WriteLine($"Import record '{importRecord}' has syntactically invalid target '{targetString}'") Return False End If ' Check for syntactically invalid aliases. Dim [alias] = importRecord.Alias If Not String.IsNullOrEmpty([alias]) Then Dim aliasNameSyntax As NameSyntax = Nothing If Not TryParseDottedName([alias], aliasNameSyntax) OrElse aliasNameSyntax.Kind <> SyntaxKind.IdentifierName Then Debug.WriteLine($"Import record '{importRecord}' has syntactically invalid alias '{[alias]}'") Return False End If End If Select Case importRecord.TargetKind Case ImportTargetKind.Type Dim typeSymbol As TypeSymbol If importRecord.TargetType IsNot Nothing Then typeSymbol = DirectCast(importRecord.TargetType, TypeSymbol) Else Debug.Assert(importRecord.Alias Is Nothing) ' Represented as ImportTargetKind.NamespaceOrType in old-format PDBs. typeSymbol = importBinder.BindTypeSyntax(targetSyntax, BindingDiagnosticBag.Discarded) Debug.Assert(typeSymbol IsNot Nothing) If typeSymbol.Kind = SymbolKind.ErrorType Then ' Type is unrecognized. The import may have been ' valid in the original source but unnecessary. Return False ' Don't add anything for this import. End If End If If [alias] IsNot Nothing Then Dim aliasSymbol As New AliasSymbol(importBinder.Compilation, importBinder.ContainingNamespaceOrType, [alias], typeSymbol, NoLocation.Singleton) If aliases Is Nothing Then aliases = New Dictionary(Of String, AliasAndImportsClausePosition)() End If ' There's no real syntax, so there's no real position. We'll give them separate numbers though. aliases([alias]) = New AliasAndImportsClausePosition(aliasSymbol, position, syntaxReference:=Nothing, ImmutableArray(Of AssemblySymbol).Empty) Else If importsBuilder Is Nothing Then importsBuilder = ArrayBuilder(Of NamespaceOrTypeAndImportsClausePosition).GetInstance() End If ' There's no real syntax, so there's no real position. We'll give them separate numbers though. importsBuilder.Add(New NamespaceOrTypeAndImportsClausePosition(typeSymbol, position, syntaxReference:=Nothing, ImmutableArray(Of AssemblySymbol).Empty)) End If ' Dev12 treats the current namespace the same as any other namespace (see ProcedureContext::LoadImportsAndDefaultNamespaceNormal). ' It seems pointless to add an import for the namespace in which we are binding expressions, but the native source gives ' the impression that other namespaces may take the same form in Everett PDBs. Case ImportTargetKind.CurrentNamespace, ImportTargetKind.Namespace ' Unaliased namespace or type If targetString = "" Then Debug.Assert(importRecord.TargetKind = ImportTargetKind.CurrentNamespace) ' The current namespace can be empty. Return False End If Dim namespaceOrTypeSymbol = importBinder.BindNamespaceOrTypeSyntax(targetSyntax, BindingDiagnosticBag.Discarded) Debug.Assert(namespaceOrTypeSymbol IsNot Nothing) If namespaceOrTypeSymbol.Kind = SymbolKind.ErrorType Then ' Namespace is unrecognized. The import may have been ' valid in the original source but unnecessary. Return False ' Don't add anything for this import. End If ' Native PDBs: aliased namespace is stored as NamespaceOrType ' Portable PDBs: aliased namespace is stored as Namespace If [alias] Is Nothing Then If importsBuilder Is Nothing Then importsBuilder = ArrayBuilder(Of NamespaceOrTypeAndImportsClausePosition).GetInstance() End If ' There's no real syntax, so there's no real position. We'll give them separate numbers though. importsBuilder.Add(New NamespaceOrTypeAndImportsClausePosition(namespaceOrTypeSymbol, position, syntaxReference:=Nothing, ImmutableArray(Of AssemblySymbol).Empty)) Else Dim aliasSymbol As New AliasSymbol(importBinder.Compilation, importBinder.ContainingNamespaceOrType, [alias], namespaceOrTypeSymbol, NoLocation.Singleton) If aliases Is Nothing Then aliases = New Dictionary(Of String, AliasAndImportsClausePosition)() End If ' There's no real syntax, so there's no real position. We'll give them separate numbers though. aliases([alias]) = New AliasAndImportsClausePosition(aliasSymbol, position, syntaxReference:=Nothing, ImmutableArray(Of AssemblySymbol).Empty) End If Case ImportTargetKind.NamespaceOrType ' Aliased namespace or type (native PDB only) Dim namespaceOrTypeSymbol = importBinder.BindNamespaceOrTypeSyntax(targetSyntax, BindingDiagnosticBag.Discarded) Debug.Assert(namespaceOrTypeSymbol IsNot Nothing) If namespaceOrTypeSymbol.Kind = SymbolKind.ErrorType Then ' Type is unrecognized. The import may have been ' valid in the original source but unnecessary. Return False ' Don't add anything for this import. End If Debug.Assert([alias] IsNot Nothing) ' Implied by TargetKind Dim aliasSymbol As New AliasSymbol(importBinder.Compilation, importBinder.ContainingNamespaceOrType, [alias], namespaceOrTypeSymbol, NoLocation.Singleton) If aliases Is Nothing Then aliases = New Dictionary(Of String, AliasAndImportsClausePosition)() End If ' There's no real syntax, so there's no real position. We'll give them separate numbers though. aliases([alias]) = New AliasAndImportsClausePosition(aliasSymbol, position, syntaxReference:=Nothing, ImmutableArray(Of AssemblySymbol).Empty) Case ImportTargetKind.XmlNamespace If xmlImports Is Nothing Then xmlImports = New Dictionary(Of String, XmlNamespaceAndImportsClausePosition)() End If ' There's no real syntax, so there's no real position. We'll give them separate numbers though. xmlImports(importRecord.Alias) = New XmlNamespaceAndImportsClausePosition(importRecord.TargetString, position, syntaxReference:=Nothing) Case ImportTargetKind.DefaultNamespace ' Processed ahead of time so that it can be incorporated into the compilation before ' constructing the binder chain. Return False Case ImportTargetKind.MethodToken ' forwarding ' One level of forwarding is pre-processed away, but invalid PDBs might contain ' chains. Just ignore them (as in Dev12). Return False Case ImportTargetKind.Defunct Return False Case ImportTargetKind.Assembly ' VB doesn't have extern aliases. Throw ExceptionUtilities.UnexpectedValue(importRecord.TargetKind) Case Else Throw ExceptionUtilities.UnexpectedValue(importRecord.TargetKind) End Select Return True End Function Private Shared Function MergeAliases(Of T)(projectLevel As Dictionary(Of String, T), fileLevel As Dictionary(Of String, T)) As Dictionary(Of String, T) If projectLevel Is Nothing Then Return fileLevel ElseIf fileLevel Is Nothing Then Return projectLevel End If ' File-level aliases win. For Each pair In projectLevel Dim [alias] As String = pair.Key If Not fileLevel.ContainsKey([alias]) Then fileLevel.Add([alias], pair.Value) End If Next Return fileLevel End Function ''' <summary> ''' We don't want to use the real scanner because we want to treat keywords as identifiers. ''' Since the inputs are so simple, we'll just do the scanning ourselves. ''' </summary> Friend Shared Function TryParseDottedName(input As String, <Out> ByRef output As NameSyntax) As Boolean Dim pooled = PooledStringBuilder.GetInstance() Try Dim builder = pooled.Builder output = Nothing For Each ch In input If builder.Length = 0 Then If Not SyntaxFacts.IsIdentifierStartCharacter(ch) Then output = Nothing Return False End If builder.Append(ch) ElseIf ch = "."c Then Dim identifierName = SyntaxFactory.IdentifierName(builder.ToString()) builder.Clear() output = If(output Is Nothing, DirectCast(identifierName, NameSyntax), SyntaxFactory.QualifiedName(output, identifierName)) ElseIf SyntaxFacts.IsIdentifierPartCharacter(ch) Then builder.Append(ch) Else output = Nothing Return False End If Next ' There must be at least one character in the last identifier. If builder.Length = 0 Then output = Nothing Return False End If Dim finalIdentifierName = SyntaxFactory.IdentifierName(builder.ToString()) output = If(output Is Nothing, DirectCast(finalIdentifierName, NameSyntax), SyntaxFactory.QualifiedName(output, finalIdentifierName)) Return True Finally pooled.Free() End Try End Function Friend ReadOnly Property MessageProvider As CommonMessageProvider Get Return Me.Compilation.MessageProvider End Get End Property Private Shared Function GetLocalsForBinding( locals As ImmutableArray(Of LocalSymbol), displayClassVariableNamesInOrder As ImmutableArray(Of String), displayClassVariables As ImmutableDictionary(Of String, DisplayClassVariable)) As ImmutableArray(Of LocalSymbol) Dim builder = ArrayBuilder(Of LocalSymbol).GetInstance() For Each local In locals Dim name = local.Name If name IsNot Nothing AndAlso Not IsGeneratedLocalName(name) Then builder.Add(local) End If Next For Each variableName In displayClassVariableNamesInOrder Dim variable = displayClassVariables(variableName) Select Case variable.Kind Case DisplayClassVariableKind.Local, DisplayClassVariableKind.Parameter Debug.Assert(Not IsGeneratedLocalName(variable.Name)) ' Established by GetDisplayClassVariables. builder.Add(New EEDisplayClassFieldLocalSymbol(variable)) End Select Next Return builder.ToImmutableAndFree() End Function Private Shared Function GetSourceMethodParametersInOrder( method As MethodSymbol, sourceMethod As MethodSymbol) As ImmutableArray(Of String) Dim containingType = method.ContainingType Dim isIteratorOrAsyncMethod = containingType.IsClosureOrStateMachineType() AndAlso containingType.IsStateMachineType() Dim parameterNamesInOrder = ArrayBuilder(Of String).GetInstance() ' For version before .NET 4.5, we cannot find the sourceMethod properly: ' The source method coincides with the original method in the case. ' Here, we have to get parameters from the containingType. ' This does not guarantee the proper order of parameters. If isIteratorOrAsyncMethod AndAlso method = sourceMethod Then Debug.Assert(containingType.IsClosureOrStateMachineType()) For Each member In containingType.GetMembers ' All iterator and async state machine fields in VB have mangled names. ' The ones beginning with "$VB$Local_" are the hoisted parameters. If member.Kind <> SymbolKind.Field Then Continue For End If Dim field = DirectCast(member, FieldSymbol) Dim fieldName = field.Name Dim parameterName As String = Nothing If GeneratedNameParser.TryParseHoistedUserVariableName(fieldName, parameterName) Then parameterNamesInOrder.Add(parameterName) End If Next Else If sourceMethod <> Nothing Then For Each parameter In sourceMethod.Parameters parameterNamesInOrder.Add(parameter.Name) Next End If End If Return parameterNamesInOrder.ToImmutableAndFree() End Function ''' <summary> ''' Return a mapping of captured variables (parameters, locals, and "Me") to locals. ''' The mapping is needed to expose the original local identifiers (those from source) ''' in the binder. ''' </summary> Private Shared Sub GetDisplayClassVariables( method As MethodSymbol, sourceMethod As MethodSymbol, locals As ImmutableArray(Of LocalSymbol), inScopeHoistedLocalSlots As ImmutableSortedSet(Of Integer), sourceMethodParametersInOrder As ImmutableArray(Of String), <Out> ByRef displayClassVariableNamesInOrder As ImmutableArray(Of String), <Out> ByRef displayClassVariables As ImmutableDictionary(Of String, DisplayClassVariable)) ' Calculate the shortest paths from locals to instances of display classes. ' There should not be two instances of the same display class immediately ' within any particular method. Dim displayClassInstances = ArrayBuilder(Of DisplayClassInstanceAndFields).GetInstance() For Each parameter As ParameterSymbol In method.Parameters If GeneratedNameParser.GetKind(parameter.Name) = GeneratedNameKind.TransparentIdentifier Then Dim instance As New DisplayClassInstanceFromParameter(parameter) displayClassInstances.Add(New DisplayClassInstanceAndFields(instance)) End If Next If method.ContainingType.IsClosureOrStateMachineType() Then If Not method.IsShared Then ' Add "Me" display class instance. Dim instance As New DisplayClassInstanceFromParameter(method.MeParameter) displayClassInstances.Add(New DisplayClassInstanceAndFields(instance)) End If End If Dim displayClassTypes = PooledHashSet(Of TypeSymbol).GetInstance() For Each instance In displayClassInstances displayClassTypes.Add(instance.Instance.Type) Next ' Find any additional display class instances. GetAdditionalDisplayClassInstances(displayClassTypes, displayClassInstances, startIndex:=0) ' Add any display class instances from locals (these will contain any hoisted locals). ' Locals are only added after finding all display class instances reachable from ' parameters because locals may be null (temporary locals in async state machine ' for instance) so we prefer parameters to locals. Dim startIndex = displayClassInstances.Count For Each local As LocalSymbol In locals Dim localName = local.Name If localName IsNot Nothing AndAlso IsDisplayClassInstanceLocalName(localName) Then If displayClassTypes.Add(local.Type) Then Dim instance As New DisplayClassInstanceFromLocal(DirectCast(local, EELocalSymbol)) displayClassInstances.Add(New DisplayClassInstanceAndFields(instance)) End If End If Next GetAdditionalDisplayClassInstances(displayClassTypes, displayClassInstances, startIndex) displayClassTypes.Free() If displayClassInstances.Any() Then ' The locals are the set of all fields from the display classes. Dim displayClassVariableNamesInOrderBuilder = ArrayBuilder(Of String).GetInstance() Dim displayClassVariablesBuilder = PooledDictionary(Of String, DisplayClassVariable).GetInstance() Dim parameterNames = PooledHashSet(Of String).GetInstance() For Each p In sourceMethodParametersInOrder parameterNames.Add(p) Next For Each instance In displayClassInstances GetDisplayClassVariables( displayClassVariableNamesInOrderBuilder, displayClassVariablesBuilder, parameterNames, inScopeHoistedLocalSlots, instance) Next displayClassVariableNamesInOrder = displayClassVariableNamesInOrderBuilder.ToImmutableAndFree() displayClassVariables = displayClassVariablesBuilder.ToImmutableDictionary() displayClassVariablesBuilder.Free() parameterNames.Free() Else displayClassVariableNamesInOrder = ImmutableArray(Of String).Empty displayClassVariables = ImmutableDictionary(Of String, DisplayClassVariable).Empty End If displayClassInstances.Free() End Sub Private Shared Function IsHoistedMeFieldName(fieldName As String) As Boolean Return fieldName.Equals(GeneratedNameConstants.HoistedMeName, StringComparison.Ordinal) End Function Private Shared Function IsLambdaMethodName(methodName As String) As Boolean Return methodName.StartsWith(GeneratedNameConstants.LambdaMethodNamePrefix, StringComparison.Ordinal) End Function ''' <summary> ''' Test whether the name is for a local holding an instance of a display class. ''' </summary> Private Shared Function IsDisplayClassInstanceLocalName(name As String) As Boolean Debug.Assert(name IsNot Nothing) ' Verified by caller. Return name.StartsWith(GeneratedNameConstants.ClosureVariablePrefix, StringComparison.Ordinal) End Function ''' <summary> ''' Test whether the name is for a field holding an instance of a display class ''' (i.e. a hoisted display class instance local). ''' </summary> Private Shared Function IsDisplayClassInstanceFieldName(name As String) As Boolean Debug.Assert(name IsNot Nothing) ' Verified by caller. Return name.StartsWith(GeneratedNameConstants.HoistedSpecialVariablePrefix & GeneratedNameConstants.ClosureVariablePrefix, StringComparison.Ordinal) OrElse name.StartsWith(GeneratedNameConstants.StateMachineHoistedUserVariableOrDisplayClassPrefix & GeneratedNameConstants.ClosureVariablePrefix, StringComparison.Ordinal) OrElse name.StartsWith(GeneratedNameConstants.HoistedSpecialVariablePrefix & GeneratedNameConstants.DisplayClassPrefix, StringComparison.Ordinal) ' Async lambda case End Function Private Shared Function IsTransparentIdentifierField(field As FieldSymbol) As Boolean Dim fieldName = field.Name Dim unmangledName As String = Nothing If GeneratedNameParser.TryParseHoistedUserVariableName(fieldName, unmangledName) Then fieldName = unmangledName ElseIf field.IsAnonymousTypeField(unmangledName) Then fieldName = unmangledName End If Return GeneratedNameParser.GetKind(fieldName) = GeneratedNameKind.TransparentIdentifier End Function Private Shared Function IsGeneratedLocalName(name As String) As Boolean Debug.Assert(name IsNot Nothing) ' Verified by caller. ' If a local's name contains "$", then it is a generated local. Return name.IndexOf("$"c) >= 0 End Function Private Shared Function GetLocalResultFlags(local As LocalSymbol) As DkmClrCompilationResultFlags Debug.Assert(local.IsConst OrElse Not local.IsReadOnly, "Didn't expect user-referenceable read-only local.") Return If( local.IsConst, DkmClrCompilationResultFlags.ReadOnlyResult, DkmClrCompilationResultFlags.None) End Function Private Shared Sub GetAdditionalDisplayClassInstances( displayClassTypes As HashSet(Of TypeSymbol), displayClassInstances As ArrayBuilder(Of DisplayClassInstanceAndFields), startIndex As Integer) ' Find any additional display class instances breadth first. Dim i = startIndex While i < displayClassInstances.Count() GetDisplayClassInstances(displayClassTypes, displayClassInstances, displayClassInstances(i)) i += 1 End While End Sub Private Shared Sub GetDisplayClassInstances( displayClassTypes As HashSet(Of TypeSymbol), displayClassInstances As ArrayBuilder(Of DisplayClassInstanceAndFields), instance As DisplayClassInstanceAndFields) ' Display class instance. The display class fields are variables. For Each member In instance.Type.GetMembers() If member.Kind <> SymbolKind.Field Then Continue For End If Dim field = DirectCast(member, FieldSymbol) Dim fieldType = field.Type Dim fieldName As String = field.Name If IsDisplayClassInstanceFieldName(fieldName) OrElse IsTransparentIdentifierField(field) Then Debug.Assert(Not field.IsShared) ' A local that is itself a display class instance. If displayClassTypes.Add(fieldType) Then Dim other = instance.FromField(field) displayClassInstances.Add(other) End If End If Next End Sub Private Shared Sub GetDisplayClassVariables( displayClassVariableNamesInOrder As ArrayBuilder(Of String), displayClassVariablesBuilder As Dictionary(Of String, DisplayClassVariable), parameterNames As HashSet(Of String), inScopeHoistedLocalSlots As ImmutableSortedSet(Of Integer), instance As DisplayClassInstanceAndFields) ' Display class instance. The display class fields are variables. For Each member In instance.Type.GetMembers() If member.Kind <> SymbolKind.Field Then Continue For End If Dim field = DirectCast(member, FieldSymbol) Dim fieldName = field.Name Dim unmangledName As String = Nothing If field.IsAnonymousTypeField(unmangledName) Then fieldName = unmangledName End If Dim variableKind As DisplayClassVariableKind Dim variableName As String Dim hoistedLocalName As String = Nothing Dim hoistedLocalSlotIndex As Integer = 0 If fieldName.StartsWith(GeneratedNameConstants.HoistedUserVariablePrefix, StringComparison.Ordinal) Then Debug.Assert(Not field.IsShared) variableKind = DisplayClassVariableKind.Local variableName = fieldName.Substring(GeneratedNameConstants.HoistedUserVariablePrefix.Length) ElseIf fieldName.StartsWith(GeneratedNameConstants.HoistedSpecialVariablePrefix, StringComparison.Ordinal) Then Debug.Assert(Not field.IsShared) variableKind = DisplayClassVariableKind.Local variableName = fieldName.Substring(GeneratedNameConstants.HoistedSpecialVariablePrefix.Length) ElseIf GeneratedNameParser.TryParseStateMachineHoistedUserVariableOrDisplayClassName(fieldName, hoistedLocalName, hoistedLocalSlotIndex) Then Debug.Assert(Not field.IsShared) If Not inScopeHoistedLocalSlots.Contains(hoistedLocalSlotIndex) Then Continue For End If variableKind = DisplayClassVariableKind.Local variableName = hoistedLocalName ElseIf IsHoistedMeFieldName(fieldName) Then Debug.Assert(Not field.IsShared) ' A reference to "Me". variableKind = DisplayClassVariableKind.Me variableName = fieldName ' As in C#, we retain the mangled name. It shouldn't be used, other than as a dictionary key. ElseIf fieldName.StartsWith(GeneratedNameConstants.LambdaCacheFieldPrefix, StringComparison.Ordinal) Then Continue For ElseIf GeneratedNameParser.GetKind(fieldName) = GeneratedNameKind.TransparentIdentifier Then ' A transparent identifier (field) in an anonymous type synthesized for a transparent identifier. Debug.Assert(Not field.IsShared) Continue For Else variableKind = DisplayClassVariableKind.Local variableName = fieldName End If If variableKind <> DisplayClassVariableKind.Me AndAlso IsGeneratedLocalName(variableName) Then Continue For End If If variableKind = DisplayClassVariableKind.Local AndAlso parameterNames.Contains(variableName) Then variableKind = DisplayClassVariableKind.Parameter End If Dim displayClassVariable As DisplayClassVariable = Nothing If displayClassVariablesBuilder.TryGetValue(variableName, displayClassVariable) Then ' Only expecting duplicates for async state machine ' fields (that should be at the top-level). Debug.Assert(displayClassVariable.DisplayClassFields.Count() = 1) If Not instance.Fields.Any() Then ' Prefer parameters over locals. Debug.Assert(TypeOf instance.Instance Is DisplayClassInstance) Else Debug.Assert(instance.Fields.Count() >= 1) ' greater depth ' There are two ways names could collide: ' 1) hoisted state machine locals in different scopes ' 2) hoisted state machine parameters that are also captured by lambdas ' The former should be impossible since we dropped out-of-scope hoisted ' locals above. We assert that we are seeing the latter. Debug.Assert((variableKind = DisplayClassVariableKind.Parameter) OrElse (variableKind = DisplayClassVariableKind.Me)) If variableKind = DisplayClassVariableKind.Parameter AndAlso GeneratedNameParser.GetKind(instance.Type.Name) = GeneratedNameKind.LambdaDisplayClass Then displayClassVariablesBuilder(variableName) = instance.ToVariable(variableName, variableKind, field) End If End If Else displayClassVariableNamesInOrder.Add(variableName) displayClassVariablesBuilder.Add(variableName, instance.ToVariable(variableName, variableKind, field)) End If Next End Sub ''' <summary> ''' Identifies the method in which binding should occur. ''' </summary> ''' <param name="candidateSubstitutedSourceMethod"> ''' The symbol of the method that is currently on top of the callstack, with ''' EE type parameters substituted in place of the original type parameters. ''' </param> ''' <param name="sourceMethodMustBeInstance"> ''' True if "Me" is available via a display class in the current context ''' </param> ''' <returns> ''' If <paramref name="candidateSubstitutedSourceMethod"/> is compiler-generated, ''' then we will attempt to determine which user-derived method caused it to be ''' generated. For example, if <paramref name="candidateSubstitutedSourceMethod"/> ''' is a state machine MoveNext method, then we will try to find the iterator or ''' async method for which it was generated. if we are able to find the original ''' method, then we will substitute in the EE type parameters. Otherwise, we will ''' return <paramref name="candidateSubstitutedSourceMethod"/>. ''' </returns> ''' <remarks> ''' In the event that the original method is overloaded, we may not be able to determine ''' which overload actually corresponds to the state machine. In particular, we do not ''' have information about the signature of the original method (i.e. number of parameters, ''' parameter types and ref-kinds, return type). However, we conjecture that this ''' level of uncertainty is acceptable, since parameters are managed by a separate binder ''' in the synthesized binder chain and we have enough information to check the other method ''' properties that are used during binding (e.g. static-ness, generic arity, type parameter ''' constraints). ''' </remarks> Friend Shared Function GetSubstitutedSourceMethod( candidateSubstitutedSourceMethod As MethodSymbol, sourceMethodMustBeInstance As Boolean) As MethodSymbol Dim candidateSubstitutedSourceType = candidateSubstitutedSourceMethod.ContainingType Dim candidateSourceTypeName = candidateSubstitutedSourceType.Name Dim desiredMethodName As String = Nothing If IsLambdaMethodName(candidateSubstitutedSourceMethod.Name) OrElse GeneratedNameParser.TryParseStateMachineTypeName(candidateSourceTypeName, desiredMethodName) Then ' We could be in the MoveNext method of an async lambda. If that is the case, we can't ' figure out desiredMethodName by unmangling the name. If desiredMethodName IsNot Nothing AndAlso IsLambdaMethodName(desiredMethodName) Then desiredMethodName = Nothing Dim containing = candidateSubstitutedSourceType.ContainingType Debug.Assert(containing IsNot Nothing) If containing.IsClosureType() Then candidateSubstitutedSourceType = containing sourceMethodMustBeInstance = candidateSubstitutedSourceType.MemberNames.Contains(GeneratedNameConstants.HoistedMeName, StringComparer.Ordinal) End If End If Dim desiredTypeParameters = candidateSubstitutedSourceType.OriginalDefinition.TypeParameters ' Type containing the original iterator, async, or lambda-containing method. Dim substitutedSourceType = GetNonClosureOrStateMachineContainer(candidateSubstitutedSourceType) For Each candidateMethod In substitutedSourceType.GetMembers().OfType(Of MethodSymbol)() If IsViableSourceMethod(candidateMethod, desiredMethodName, desiredTypeParameters, sourceMethodMustBeInstance) Then Return If(desiredTypeParameters.Length = 0, candidateMethod, candidateMethod.Construct(candidateSubstitutedSourceType.TypeArgumentsNoUseSiteDiagnostics)) End If Next Debug.Assert(False, String.Format("Why didn't we find a substituted source method for {0}?", candidateSubstitutedSourceMethod)) End If Return candidateSubstitutedSourceMethod End Function Private Shared Function GetNonClosureOrStateMachineContainer(type As NamedTypeSymbol) As NamedTypeSymbol ' 1) Display class and state machine types are always nested within the types ' that use them so that they can access private members of those types). ' 2) The native compiler used to produce nested display classes for nested lambdas, ' so we may have to walk out more than one level. While type.IsClosureOrStateMachineType() type = type.ContainingType End While Debug.Assert(type IsNot Nothing) Return type End Function Private Shared Function IsViableSourceMethod( candidateMethod As MethodSymbol, desiredMethodName As String, desiredTypeParameters As ImmutableArray(Of TypeParameterSymbol), desiredMethodMustBeInstance As Boolean) As Boolean Return _ Not candidateMethod.IsMustOverride AndAlso (Not (desiredMethodMustBeInstance AndAlso candidateMethod.IsShared)) AndAlso (desiredMethodName Is Nothing OrElse desiredMethodName = candidateMethod.Name) AndAlso HasDesiredConstraints(candidateMethod, desiredTypeParameters) End Function Private Shared Function HasDesiredConstraints(candidateMethod As MethodSymbol, desiredTypeParameters As ImmutableArray(Of TypeParameterSymbol)) As Boolean Dim arity = candidateMethod.Arity If arity <> desiredTypeParameters.Length Then Return False ElseIf arity = 0 Then Return True End If Dim indexedTypeParameters = IndexedTypeParameterSymbol.Take(arity).As(Of TypeSymbol) ' NOTE: Can't seem to construct a type map for just the method type parameters, ' so we also specify a trivial map for the type parameters of the (immediately) ' containing type. Dim candidateMethodDefinition As MethodSymbol = candidateMethod.OriginalDefinition Dim sourceTypeTypeParameters As ImmutableArray(Of TypeParameterSymbol) = candidateMethodDefinition.ContainingType.TypeParameters Dim candidateTypeMap = TypeSubstitution.Create( candidateMethodDefinition, sourceTypeTypeParameters.Concat(candidateMethodDefinition.TypeParameters), sourceTypeTypeParameters.As(Of TypeSymbol).Concat(indexedTypeParameters)) Debug.Assert(candidateTypeMap.PairsIncludingParent.Length = arity) Dim desiredTypeMap = TypeSubstitution.Create( desiredTypeParameters(0).ContainingSymbol, desiredTypeParameters, indexedTypeParameters) Debug.Assert(desiredTypeMap.PairsIncludingParent.Length = arity) For i = 0 To arity - 1 If Not MethodSignatureComparer.HaveSameConstraints(candidateMethodDefinition.TypeParameters(i), candidateTypeMap, desiredTypeParameters(i), desiredTypeMap) Then Return False End If Next Return True End Function <DebuggerDisplay("{GetDebuggerDisplay(), nq}")> Private Structure DisplayClassInstanceAndFields Friend ReadOnly Instance As DisplayClassInstance Friend ReadOnly Fields As ConsList(Of FieldSymbol) Friend Sub New(instance As DisplayClassInstance) MyClass.New(instance, ConsList(Of FieldSymbol).Empty) Debug.Assert(instance.Type.IsClosureOrStateMachineType() OrElse GeneratedNameParser.GetKind(instance.Type.Name) = GeneratedNameKind.AnonymousType) End Sub Private Sub New(instance As DisplayClassInstance, fields As ConsList(Of FieldSymbol)) Me.Instance = instance Me.Fields = fields End Sub Friend ReadOnly Property Type As TypeSymbol Get Return If(Me.Fields.Any(), Me.Fields.Head.Type, Me.Instance.Type) End Get End Property Friend ReadOnly Property Depth As Integer Get Return Me.Fields.Count() End Get End Property Friend Function FromField(field As FieldSymbol) As DisplayClassInstanceAndFields Debug.Assert(field.Type.IsClosureOrStateMachineType() OrElse GeneratedNameParser.GetKind(field.Type.Name) = GeneratedNameKind.AnonymousType) Return New DisplayClassInstanceAndFields(Me.Instance, Me.Fields.Prepend(field)) End Function Friend Function ToVariable(name As String, kind As DisplayClassVariableKind, field As FieldSymbol) As DisplayClassVariable Return New DisplayClassVariable(name, kind, Me.Instance, Me.Fields.Prepend(field)) End Function Private Function GetDebuggerDisplay() As String Return Instance.GetDebuggerDisplay(Fields) End Function End Structure End Class End Namespace