File: EvaluationContext.vb
Web Access
Project: src\src\ExpressionEvaluator\VisualBasic\Source\ExpressionCompiler\Microsoft.CodeAnalysis.VisualBasic.ExpressionCompiler.vbproj (Microsoft.CodeAnalysis.VisualBasic.ExpressionCompiler)
' 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.Collections.ObjectModel
Imports System.IO
Imports System.Reflection
Imports System.Reflection.Metadata
Imports System.Reflection.Metadata.Ecma335
Imports System.Runtime.InteropServices
Imports Microsoft.CodeAnalysis.CodeGen
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.DiaSymReader
Imports Microsoft.VisualStudio.Debugger.Clr
Imports Microsoft.VisualStudio.Debugger.Evaluation
Imports Microsoft.VisualStudio.Debugger.Evaluation.ClrCompilation
Imports Roslyn.Utilities
 
Namespace Microsoft.CodeAnalysis.VisualBasic.ExpressionEvaluator
 
    Friend NotInheritable Class EvaluationContext
        Inherits EvaluationContextBase
 
        ' These names are arbitrary, so we'll just use the same names as
        ' the C# expression compiler.
        Private Const s_typeName = "<>x"
        Private Const s_methodName = "<>m0"
 
        Friend ReadOnly MethodContextReuseConstraints As MethodContextReuseConstraints?
        Friend ReadOnly Compilation As VisualBasicCompilation
 
        Private ReadOnly _currentFrame As MethodSymbol
        Private ReadOnly _currentSourceMethod As MethodSymbol
        Private ReadOnly _locals As ImmutableArray(Of LocalSymbol)
        Private ReadOnly _inScopeHoistedLocalSlots As ImmutableSortedSet(Of Integer)
        Private ReadOnly _methodDebugInfo As MethodDebugInfo(Of TypeSymbol, LocalSymbol)
 
        Private Sub New(
            methodContextReuseConstraints As MethodContextReuseConstraints?,
            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))
 
            Me.MethodContextReuseConstraints = methodContextReuseConstraints
            Me.Compilation = compilation
            _currentFrame = currentFrame
            _currentSourceMethod = currentSourceMethod
            _locals = locals
            _inScopeHoistedLocalSlots = inScopeHoistedLocalSlots
            _methodDebugInfo = methodDebugInfo
        End Sub
 
        ''' <summary>
        ''' Create a context for evaluating expressions at a type scope.
        ''' </summary>
        ''' <param name="compilation">Compilation.</param>
        ''' <param name="moduleVersionId">Module containing type.</param>
        ''' <param name="typeToken">Type metadata token.</param>
        ''' <returns>Evaluation context.</returns>
        ''' <remarks>
        ''' No locals since locals are associated with methods, not types.
        ''' </remarks>
        Friend Shared Function CreateTypeContext(
            compilation As VisualBasicCompilation,
            moduleVersionId As Guid,
            typeToken As Integer) As EvaluationContext
 
            Debug.Assert(MetadataTokens.Handle(typeToken).Kind = HandleKind.TypeDefinition)
 
            Dim currentType = compilation.GetType(moduleVersionId, typeToken)
            Debug.Assert(currentType IsNot Nothing)
 
            Dim currentFrame = New SynthesizedContextMethodSymbol(currentType)
            Return New EvaluationContext(
                Nothing,
                compilation,
                currentFrame,
                Nothing,
                locals:=Nothing,
                inScopeHoistedLocalSlots:=Nothing,
                methodDebugInfo:=MethodDebugInfo(Of TypeSymbol, LocalSymbol).None)
        End Function
 
        ''' <summary>
        ''' Create a context for evaluating expressions within a method scope.
        ''' </summary>
        ''' <param name="previous">Previous context, if any, for possible re-use.</param>
        ''' <param name="metadataBlocks">Module metadata.</param>
        ''' <param name="symReader"><see cref="ISymUnmanagedReader"/> for PDB associated with <paramref name="moduleVersionId"/>.</param>
        ''' <param name="moduleVersionId">Module containing method.</param>
        ''' <param name="methodToken">Method metadata token.</param>
        ''' <param name="methodVersion">Method version.</param>
        ''' <param name="ilOffset">IL offset of instruction pointer in method.</param>
        ''' <param name="localSignatureToken">Method local signature token.</param>
        ''' <returns>Evaluation context.</returns>
        Friend Shared Function CreateMethodContext(
            previous As VisualBasicMetadataContext,
            metadataBlocks As ImmutableArray(Of MetadataBlock),
            lazyAssemblyReaders As Lazy(Of ImmutableArray(Of AssemblyReaders)),
            symReader As Object,
            moduleVersionId As Guid,
            methodToken As Integer,
            methodVersion As Integer,
            ilOffset As UInteger,
            localSignatureToken As Integer) As EvaluationContext
 
            Dim offset = NormalizeILOffset(ilOffset)
 
            Dim compilation As VisualBasicCompilation = metadataBlocks.ToCompilation(Nothing, MakeAssemblyReferencesKind.AllAssemblies)
 
            Return CreateMethodContext(
                compilation,
                lazyAssemblyReaders,
                symReader,
                moduleVersionId,
                methodToken,
                methodVersion,
                offset,
                localSignatureToken)
        End Function
 
        ''' <summary>
        ''' Create a context for evaluating expressions within a method scope.
        ''' </summary>
        ''' <param name="compilation">Compilation.</param>
        ''' <param name="symReader"><see cref="ISymUnmanagedReader"/> for PDB associated with <paramref name="moduleVersionId"/>.</param>
        ''' <param name="moduleVersionId">Module containing method.</param>
        ''' <param name="methodToken">Method metadata token.</param>
        ''' <param name="methodVersion">Method version.</param>
        ''' <param name="ilOffset">IL offset of instruction pointer in method.</param>
        ''' <param name="localSignatureToken">Method local signature token.</param>
        ''' <returns>Evaluation context.</returns>
        Friend Shared Function CreateMethodContext(
            compilation As VisualBasicCompilation,
            lazyAssemblyReaders As Lazy(Of ImmutableArray(Of AssemblyReaders)),
            symReader As Object,
            moduleVersionId As Guid,
            methodToken As Integer,
            methodVersion As Integer,
            ilOffset As Integer,
            localSignatureToken As Integer) As EvaluationContext
 
            Dim methodHandle = CType(MetadataTokens.Handle(methodToken), MethodDefinitionHandle)
            Dim currentSourceMethod = compilation.GetSourceMethod(moduleVersionId, methodHandle)
            Dim localSignatureHandle = If(localSignatureToken <> 0, CType(MetadataTokens.Handle(localSignatureToken), StandaloneSignatureHandle), Nothing)
 
            Dim currentFrame = compilation.GetMethod(moduleVersionId, methodHandle)
            Debug.Assert(currentFrame IsNot Nothing)
            Dim symbolProvider = New VisualBasicEESymbolProvider(DirectCast(currentFrame.ContainingModule, PEModuleSymbol), currentFrame)
 
            Dim metadataDecoder = New MetadataDecoder(DirectCast(currentFrame.ContainingModule, PEModuleSymbol), currentFrame)
            Dim localInfo = metadataDecoder.GetLocalInfo(localSignatureHandle)
 
            Dim debugInfo As MethodDebugInfo(Of TypeSymbol, LocalSymbol)
 
            If IsDteeEntryPoint(currentFrame) Then
                debugInfo = SynthesizeMethodDebugInfoForDtee(lazyAssemblyReaders.Value)
            Else
                Dim typedSymReader = DirectCast(symReader, ISymUnmanagedReader3)
                debugInfo = MethodDebugInfo(Of TypeSymbol, LocalSymbol).ReadMethodDebugInfo(typedSymReader, symbolProvider, methodToken, methodVersion, ilOffset, isVisualBasicMethod:=True)
            End If
 
            Dim reuseSpan = debugInfo.ReuseSpan
 
            Dim inScopeHoistedLocalSlots As ImmutableSortedSet(Of Integer)
            If debugInfo.HoistedLocalScopeRecords.IsDefault Then
                inScopeHoistedLocalSlots = GetInScopeHoistedLocalSlots(debugInfo.LocalVariableNames)
            Else
                inScopeHoistedLocalSlots = debugInfo.GetInScopeHoistedLocalIndices(ilOffset, reuseSpan)
            End If
 
            Dim localNames = debugInfo.LocalVariableNames.WhereAsArray(
                Function(name) name Is Nothing OrElse Not name.StartsWith(GeneratedNameConstants.StateMachineHoistedUserVariableOrDisplayClassPrefix, StringComparison.Ordinal))
 
            Dim localsBuilder = ArrayBuilder(Of LocalSymbol).GetInstance()
            MethodDebugInfo(Of TypeSymbol, LocalSymbol).GetLocals(localsBuilder, symbolProvider, localNames, localInfo, Nothing, debugInfo.TupleLocalMap)
 
            GetStaticLocals(localsBuilder, currentFrame, methodHandle, metadataDecoder)
            localsBuilder.AddRange(debugInfo.LocalConstants)
 
            Return New EvaluationContext(
                New MethodContextReuseConstraints(moduleVersionId, methodToken, methodVersion, reuseSpan),
                compilation,
                currentFrame,
                currentSourceMethod,
                localsBuilder.ToImmutableAndFree(),
                inScopeHoistedLocalSlots,
                debugInfo)
        End Function
 
        Private Shared Function GetInScopeHoistedLocalSlots(allLocalNames As ImmutableArray(Of String)) As ImmutableSortedSet(Of Integer)
            Dim builder = ArrayBuilder(Of Integer).GetInstance()
            For Each localName In allLocalNames
                Dim hoistedLocalName As String = Nothing
                Dim hoistedLocalSlot As Integer = 0
                If localName IsNot Nothing AndAlso GeneratedNameParser.TryParseStateMachineHoistedUserVariableOrDisplayClassName(localName, hoistedLocalName, hoistedLocalSlot) Then
                    builder.Add(hoistedLocalSlot)
                End If
            Next
 
            Dim result = builder.ToImmutableSortedSet()
            builder.Free()
            Return result
        End Function
 
        ''' <summary>
        ''' When using DTEE with the hosting process enabled, if the assembly being debugged doesn't have
        ''' a Main method, the debugger will actually stop in a driver method in the hosting process.
        ''' As in the native EE, we detect such methods by name (respecting case).
        ''' </summary>
        ''' <remarks>
        ''' Logic copied from ProcedureContext::IsDteeEntryPoint.
        ''' Friend for testing.
        ''' </remarks>
        ''' <seealso cref="SynthesizeMethodDebugInfoForDtee"/>
        Friend Shared Function IsDteeEntryPoint(currentFrame As MethodSymbol) As Boolean
            Dim typeName = currentFrame.ContainingType.Name
            Dim methodName = currentFrame.Name
            Return _
                (String.Equals(typeName, "HostProc", StringComparison.Ordinal) AndAlso ' DLL
                    String.Equals(methodName, "BreakForDebugger", StringComparison.Ordinal)) OrElse
                (String.Equals(typeName, "AppDomain", StringComparison.Ordinal) AndAlso ' WPF app
                    String.Equals(methodName, "ExecuteAssembly", StringComparison.Ordinal))
        End Function
 
        ''' <summary>
        ''' When using DTEE with the hosting process enabled, if the assembly being debugged doesn't have
        ''' a Main method, the debugger will actually stop in a driver method in the hosting process.
        ''' (This condition is detected by <see cref="IsDteeEntryPoint"/>.)
        ''' 
        ''' Since such driver methods have no knowledge of the assembly being debugged, we synthesize 
        ''' imports to bring symbols from the target assembly into scope (for convenience).  In particular,
        ''' we import the root namespace of any assembly that isn't obviously a framework assembly and
        ''' any namespace containing a module type.
        ''' </summary>
        ''' <remarks>
        ''' Logic copied from ProcedureContext::LoadImportsAndDefaultNamespaceForDteeEntryPoint.
        ''' Friend for testing.
        ''' </remarks>
        ''' <seealso cref="IsDteeEntryPoint"/>
        ''' <seealso cref="PENamedTypeSymbol.TypeKind"/>
        Friend Shared Function SynthesizeMethodDebugInfoForDtee(assemblyReaders As ImmutableArray(Of AssemblyReaders)) As MethodDebugInfo(Of TypeSymbol, LocalSymbol)
            Dim [imports] = PooledHashSet(Of String).GetInstance()
 
            For Each readers In assemblyReaders
                Dim metadataReader = readers.MetadataReader
                Dim symReader = DirectCast(readers.SymReader, ISymUnmanagedReader3)
 
                ' Ignore assemblies for which we don't have PDBs.
                If symReader Is Nothing Then Continue For
 
                Try
                    For Each typeDefHandle In metadataReader.TypeDefinitions
                        Dim typeDef = metadataReader.GetTypeDefinition(typeDefHandle)
                        Dim foundAttributeType = False
 
                        ' VB does ignores the StandardModuleAttribute on interfaces and nested
                        ' or generic types (see PENamedTypeSymbol.TypeKind).
                        If Not PEModule.IsNested(typeDef.Attributes) AndAlso
                            typeDef.GetGenericParameters().Count = 0 AndAlso
                            (typeDef.Attributes And TypeAttributes.[Interface]) = 0 AndAlso
                            PEModule.FindTargetAttribute(metadataReader, typeDefHandle, AttributeDescription.StandardModuleAttribute, foundAttributeType).HasValue Then
 
                            Dim namespaceName = metadataReader.GetString(typeDef.Namespace)
                            [imports].Add(namespaceName)
                        End If
                    Next
 
                    For Each methodDefHandle In metadataReader.MethodDefinitions
                        ' TODO: this can be done better
                        ' EnC can't change the default namespace of the assembly, so version 1 will suffice.
                        Dim debugInfo = MethodDebugInfo(Of TypeSymbol, LocalSymbol).ReadMethodDebugInfo(symReader,
                                                                                                        Nothing,
                                                                                                        metadataReader.GetToken(methodDefHandle),
                                                                                                        methodVersion:=1,
                                                                                                        ilOffset:=0,
                                                                                                        isVisualBasicMethod:=True)
 
                        ' Some methods aren't decorated with import custom debug info.
                        If Not String.IsNullOrEmpty(debugInfo.DefaultNamespaceName) Then
 
                            ' NOTE: We're adding it as a project-level import, not as the default namespace
                            ' (because there's one for each assembly and they can't all be the default).
                            [imports].Add(debugInfo.DefaultNamespaceName)
 
                            ' The default namespace should be the same for all methods, so we only need to check one.
                            Exit For
                        End If
                    Next
 
                Catch ex As BadImageFormatException
                    ' This will only prevent us from synthesizing imports for the module types in
                    ' one assembly - it is decidedly recoverable.
                End Try
            Next
 
            Dim projectLevelImportRecords = ImmutableArray.CreateRange([imports].Select(
                Function(namespaceName) New ImportRecord(ImportTargetKind.Namespace,
                                                         alias:=Nothing,
                                                         targetType:=Nothing,
                                                         targetString:=namespaceName,
                                                         targetAssembly:=Nothing,
                                                         targetAssemblyAlias:=Nothing)))
 
            [imports].Free()
            Dim fileLevelImportRecords = ImmutableArray(Of ImportRecord).Empty
 
            Dim importRecordGroups = ImmutableArray.Create(fileLevelImportRecords, projectLevelImportRecords)
 
            Return New MethodDebugInfo(Of TypeSymbol, LocalSymbol)(
                hoistedLocalScopeRecords:=ImmutableArray(Of HoistedLocalScopeRecord).Empty,
                importRecordGroups:=importRecordGroups,
                defaultNamespaceName:="",
                externAliasRecords:=ImmutableArray(Of ExternAliasRecord).Empty,
                dynamicLocalMap:=Nothing,
                tupleLocalMap:=Nothing,
                localVariableNames:=ImmutableArray(Of String).Empty,
                localConstants:=ImmutableArray(Of LocalSymbol).Empty,
                reuseSpan:=Nothing,
                containingDocumentName:=Nothing,
                isPrimaryConstructor:=False)
        End Function
 
        Friend Function CreateCompilationContext(withSyntax As Boolean) As CompilationContext
            Return New CompilationContext(
                Compilation,
                _currentFrame,
                _currentSourceMethod,
                _locals,
                _inScopeHoistedLocalSlots,
                _methodDebugInfo,
                withSyntax)
        End Function
 
        Friend Overrides Function CompileExpression(
            expr As String,
            compilationFlags As DkmEvaluationFlags,
            aliases As ImmutableArray(Of [Alias]),
            diagnostics As DiagnosticBag,
            <Out> ByRef resultProperties As ResultProperties,
            testData As Microsoft.CodeAnalysis.CodeGen.CompilationTestData) As CompileResult
 
            resultProperties = Nothing
            Dim formatSpecifiers As ReadOnlyCollection(Of String) = Nothing
            Dim syntax = If((compilationFlags And DkmEvaluationFlags.TreatAsExpression) <> 0,
                    expr.ParseExpression(diagnostics, allowFormatSpecifiers:=True, formatSpecifiers:=formatSpecifiers),
                    expr.ParseStatement(diagnostics))
            If syntax Is Nothing Then
                Return Nothing
            End If
 
            If Not IsSupportedDebuggerStatement(syntax) Then
                diagnostics.Add(New SimpleMessageDiagnostic(String.Format(Resources.InvalidDebuggerStatement, syntax.Kind)))
                Return Nothing
            End If
 
            Dim context = Me.CreateCompilationContext(withSyntax:=True)
            Dim synthesizedMethod As EEMethodSymbol = Nothing
            Dim moduleBuilder = context.Compile(DirectCast(syntax, ExecutableStatementSyntax), s_typeName, s_methodName, aliases, testData, diagnostics, synthesizedMethod)
            If moduleBuilder Is Nothing Then
                Return Nothing
            End If
 
            Using stream As New MemoryStream()
                Cci.PeWriter.WritePeToStream(
                        New EmitContext(moduleBuilder, Nothing, diagnostics, metadataOnly:=False, includePrivateMembers:=True),
                        context.MessageProvider,
                        Function() stream,
                        getPortablePdbStreamOpt:=Nothing,
                        nativePdbWriterOpt:=Nothing,
                        pdbPathOpt:=Nothing,
                        metadataOnly:=False,
                        isDeterministic:=False,
                        emitTestCoverageData:=False,
                        privateKeyOpt:=Nothing,
                        cancellationToken:=Nothing)
 
                If diagnostics.HasAnyErrors() Then
                    Return Nothing
                End If
 
                Debug.Assert(synthesizedMethod.ContainingType.MetadataName = s_typeName)
                Debug.Assert(synthesizedMethod.MetadataName = s_methodName)
 
                resultProperties = synthesizedMethod.ResultProperties
                Return New VisualBasicCompileResult(
                        stream.ToArray(),
                        synthesizedMethod,
                        formatSpecifiers)
            End Using
        End Function
 
        Friend Overrides Function CompileAssignment(
            target As String,
            expr As String,
            aliases As ImmutableArray(Of [Alias]),
            diagnostics As DiagnosticBag,
            <Out> ByRef resultProperties As ResultProperties,
            testData As Microsoft.CodeAnalysis.CodeGen.CompilationTestData) As CompileResult
 
            Dim assignment = target.ParseAssignment(expr, diagnostics)
            If assignment Is Nothing Then
                Return Nothing
            End If
 
            Dim context = Me.CreateCompilationContext(withSyntax:=True)
            Dim synthesizedMethod As EEMethodSymbol = Nothing
            Dim modulebuilder = context.Compile(assignment, s_typeName, s_methodName, aliases, testData, diagnostics, synthesizedMethod)
            If modulebuilder Is Nothing Then
                Return Nothing
            End If
 
            Using stream As New MemoryStream()
                Cci.PeWriter.WritePeToStream(
                        New EmitContext(modulebuilder, Nothing, diagnostics, metadataOnly:=False, includePrivateMembers:=True),
                        context.MessageProvider,
                        Function() stream,
                        getPortablePdbStreamOpt:=Nothing,
                        nativePdbWriterOpt:=Nothing,
                        pdbPathOpt:=Nothing,
                        metadataOnly:=False,
                        isDeterministic:=False,
                        emitTestCoverageData:=False,
                        privateKeyOpt:=Nothing,
                        cancellationToken:=Nothing)
 
                If diagnostics.HasAnyErrors() Then
                    Return Nothing
                End If
 
                Debug.Assert(synthesizedMethod.ContainingType.MetadataName = s_typeName)
                Debug.Assert(synthesizedMethod.MetadataName = s_methodName)
 
                Dim properties = synthesizedMethod.ResultProperties
                resultProperties = New ResultProperties(
                        properties.Flags Or DkmClrCompilationResultFlags.PotentialSideEffect,
                        properties.Category,
                        properties.AccessType,
                        properties.StorageType,
                        properties.ModifierFlags)
                Return New VisualBasicCompileResult(
                        stream.ToArray(),
                        synthesizedMethod,
                        formatSpecifiers:=Nothing)
            End Using
        End Function
 
        Private Shared ReadOnly s_emptyBytes As New ReadOnlyCollection(Of Byte)(Array.Empty(Of Byte))
 
        Friend Overrides Function CompileGetLocals(
            locals As ArrayBuilder(Of LocalAndMethod),
            argumentsOnly As Boolean,
            aliases As ImmutableArray(Of [Alias]),
            diagnostics As DiagnosticBag,
            <Out> ByRef typeName As String,
            testData As CompilationTestData) As ReadOnlyCollection(Of Byte)
 
            Dim context = Me.CreateCompilationContext(withSyntax:=False)
            Dim modulebuilder = context.CompileGetLocals(s_typeName, locals, argumentsOnly, aliases, testData, diagnostics)
            Dim assembly As ReadOnlyCollection(Of Byte) = Nothing
 
            If modulebuilder IsNot Nothing AndAlso locals.Count > 0 Then
                Using stream As New MemoryStream()
                    Cci.PeWriter.WritePeToStream(
                        New EmitContext(modulebuilder, Nothing, diagnostics, metadataOnly:=False, includePrivateMembers:=True),
                        context.MessageProvider,
                        Function() stream,
                        getPortablePdbStreamOpt:=Nothing,
                        nativePdbWriterOpt:=Nothing,
                        pdbPathOpt:=Nothing,
                        metadataOnly:=False,
                        isDeterministic:=False,
                        emitTestCoverageData:=False,
                        privateKeyOpt:=Nothing,
                        cancellationToken:=Nothing)
 
                    If Not diagnostics.HasAnyErrors() Then
                        assembly = New ReadOnlyCollection(Of Byte)(stream.ToArray())
                    End If
                End Using
            End If
 
            If assembly Is Nothing Then
                locals.Clear()
                assembly = s_emptyBytes
            End If
 
            typeName = EvaluationContext.s_typeName
            Return assembly
        End Function
 
        ''' <summary>
        ''' Include static locals for the given method. Static locals
        ''' are represented as fields on the containing class named
        ''' "$STATIC$[methodname]$[methodsignature]$[localname]".
        ''' </summary>
        Private Shared Sub GetStaticLocals(
            builder As ArrayBuilder(Of LocalSymbol),
            method As MethodSymbol,
            methodHandle As MethodDefinitionHandle,
            metadataDecoder As MetadataDecoder)
 
            Dim type = method.ContainingType
            If type.TypeKind <> TypeKind.Class Then
                Return
            End If
 
            For Each member In type.GetMembers()
                If member.Kind <> SymbolKind.Field Then
                    Continue For
                End If
                Dim methodName As String = Nothing
                Dim methodSignature As String = Nothing
                Dim localName As String = Nothing
                If GeneratedNameParser.TryParseStaticLocalFieldName(member.Name, methodName, methodSignature, localName) AndAlso
                    String.Equals(methodName, method.Name, StringComparison.Ordinal) AndAlso
                    String.Equals(methodSignature, GetMethodSignatureString(metadataDecoder, methodHandle), StringComparison.Ordinal) Then
                    builder.Add(New EEStaticLocalSymbol(method, DirectCast(member, FieldSymbol), localName))
                End If
            Next
        End Sub
 
        Private Shared Function GetMethodSignatureString(metadataDecoder As MetadataDecoder, methodHandle As MethodDefinitionHandle) As String
            Dim [module] = metadataDecoder.Module
            Dim signatureHandle = [module].GetMethodSignatureOrThrow(methodHandle)
            Dim signatureReader = [module].GetMemoryReaderOrThrow(signatureHandle)
            Dim signature = signatureReader.ReadBytes(signatureReader.Length)
            Return GeneratedNames.MakeSignatureString(signature)
        End Function
 
        Friend Overrides Function HasDuplicateTypesOrAssemblies(diagnostic As Diagnostic) As Boolean
            Select Case CType(diagnostic.Code, ERRID)
                Case ERRID.ERR_DuplicateReference2,
                     ERRID.ERR_DuplicateReferenceStrong,
                     ERRID.ERR_AmbiguousInUnnamedNamespace1,
                     ERRID.ERR_AmbiguousInNamespace2,
                     ERRID.ERR_NoMostSpecificOverload2,
                     ERRID.ERR_AmbiguousInModules2
                    Return True
                Case Else
                    Return False
            End Select
        End Function
 
        Friend Overrides Function GetMissingAssemblyIdentities(diagnostic As Diagnostic, linqLibrary As AssemblyIdentity) As ImmutableArray(Of AssemblyIdentity)
            Return GetMissingAssemblyIdentitiesHelper(CType(diagnostic.Code, ERRID), diagnostic.Arguments, Me.Compilation.GlobalNamespace, linqLibrary)
        End Function
 
        ''' <remarks>
        ''' Friend for testing.
        ''' </remarks>
        Friend Shared Function GetMissingAssemblyIdentitiesHelper(code As ERRID, arguments As IReadOnlyList(Of Object), globalNamespace As NamespaceSymbol, linqLibrary As AssemblyIdentity) As ImmutableArray(Of AssemblyIdentity)
            Debug.Assert(linqLibrary IsNot Nothing)
 
            Select Case code
                Case ERRID.ERR_UnreferencedAssemblyEvent3, ERRID.ERR_UnreferencedAssembly3
                    For Each argument As Object In arguments
                        Dim identity = If(TryCast(argument, AssemblyIdentity), TryCast(argument, AssemblySymbol)?.Identity)
                        If IsValidMissingAssemblyIdentity(identity) Then
                            Return ImmutableArray.Create(identity)
                        End If
                    Next
                Case ERRID.ERR_ForwardedTypeUnavailable3
                    If arguments.Count = 3 Then
                        Dim identity As AssemblyIdentity = TryCast(arguments(2), AssemblySymbol)?.Identity
                        If IsValidMissingAssemblyIdentity(identity) Then
                            Return ImmutableArray.Create(identity)
                        End If
                    End If
                Case ERRID.ERR_NameNotMember2
                    If arguments.Count = 2 Then
                        Dim namespaceName = TryCast(arguments(0), String)
                        Dim containingNamespace = TryCast(arguments(1), NamespaceSymbol)
                        If namespaceName IsNot Nothing AndAlso containingNamespace IsNot Nothing AndAlso HasConstituentFromWindowsAssembly(containingNamespace) Then
                            ' This is just a heuristic, but it has the advantage of being portable, particularly
                            ' across different versions of (desktop) windows.
                            Dim identity = New AssemblyIdentity($"{containingNamespace.ToDisplayString}.{namespaceName}", contentType:=AssemblyContentType.WindowsRuntime)
                            Return ImmutableArray.Create(identity)
                        Else
                            ' Maybe it's a missing LINQ extension method.  Let's try adding the "LINQ library" to see if that helps.
                            Return ImmutableArray.Create(linqLibrary)
                        End If
                    End If
                Case ERRID.ERR_UndefinedType1
                    If arguments.Count = 1 Then
                        Dim qualifiedName = TryCast(arguments(0), String)
                        If Not String.IsNullOrEmpty(qualifiedName) Then
                            Dim nameParts = qualifiedName.Split("."c)
                            Dim numParts = nameParts.Length
                            Dim pos = 0
                            If CaseInsensitiveComparison.Comparer.Equals(nameParts(0), "global") Then
                                pos = 1
                                Debug.Assert(pos < numParts)
                            End If
                            Dim currNamespace = globalNamespace
                            While pos < numParts
                                Dim nextNamespace = currNamespace.GetMembers(nameParts(pos)).OfType(Of NamespaceSymbol).SingleOrDefault()
                                If nextNamespace Is Nothing Then
                                    Exit While
                                End If
                                pos += 1
                                currNamespace = nextNamespace
                            End While
 
                            If currNamespace IsNot globalNamespace AndAlso HasConstituentFromWindowsAssembly(currNamespace) AndAlso pos < numParts Then
                                Dim nextNamePart = nameParts(pos)
                                If nextNamePart.All(AddressOf SyntaxFacts.IsIdentifierPartCharacter) Then
                                    ' This is just a heuristic, but it has the advantage of being portable, particularly
                                    ' across different versions of (desktop) windows.
                                    Dim identity = New AssemblyIdentity($"{currNamespace.ToDisplayString}.{nameParts(pos)}", contentType:=AssemblyContentType.WindowsRuntime)
                                    Return ImmutableArray.Create(identity)
                                End If
                            End If
                        End If
                    End If
                Case ERRID.ERR_XmlFeaturesNotAvailable
                    Return ImmutableArray.Create(SystemIdentity, linqLibrary, SystemXmlIdentity, SystemXmlLinqIdentity)
                Case ERRID.ERR_MissingRuntimeHelper
                    Return ImmutableArray.Create(MicrosoftVisualBasicIdentity)
            End Select
 
            Return Nothing
        End Function
 
        Private Shared Function HasConstituentFromWindowsAssembly(namespaceSymbol As NamespaceSymbol) As Boolean
            Return namespaceSymbol.ConstituentNamespaces.Any(Function(n) n.ContainingAssembly.Identity.IsWindowsAssemblyIdentity)
        End Function
 
        Private Shared Function IsValidMissingAssemblyIdentity(identity As AssemblyIdentity) As Boolean
            Return identity IsNot Nothing AndAlso Not identity.Equals(MissingCorLibrarySymbol.Instance.Identity)
        End Function
    End Class
 
End Namespace