File: Emit\EditAndContinue\PEDeltaAssemblyBuilder.vb
Web Access
Project: src\src\Compilers\VisualBasic\Portable\Microsoft.CodeAnalysis.VisualBasic.vbproj (Microsoft.CodeAnalysis.VisualBasic)
' Licensed to the .NET Foundation under one or more agreements.
' The .NET Foundation licenses this file to you under the MIT license.
' See the LICENSE file in the project root for more information.
 
Imports System.Collections.Immutable
Imports System.Globalization
Imports System.Reflection.Metadata
Imports System.Runtime.InteropServices
Imports Microsoft.Cci
Imports Microsoft.CodeAnalysis.CodeGen
Imports Microsoft.CodeAnalysis.Collections
Imports Microsoft.CodeAnalysis.Emit
Imports Microsoft.CodeAnalysis.PooledObjects
Imports Microsoft.CodeAnalysis.Symbols
Imports Microsoft.CodeAnalysis.VisualBasic.Symbols
Imports Microsoft.CodeAnalysis.VisualBasic.Symbols.Metadata.PE
 
Namespace Microsoft.CodeAnalysis.VisualBasic.Emit
 
    Friend NotInheritable Class PEDeltaAssemblyBuilder
        Inherits PEAssemblyBuilderBase
        Implements IPEDeltaAssemblyBuilder
 
        Private ReadOnly _previousDefinitions As VisualBasicDefinitionMap
        Private ReadOnly _changes As SymbolChanges
        Private ReadOnly _deepTranslator As VisualBasicSymbolMatcher.DeepTranslator
 
        Public Sub New(sourceAssembly As SourceAssemblySymbol,
                       emitOptions As EmitOptions,
                       outputKind As OutputKind,
                       serializationProperties As ModulePropertiesForSerialization,
                       manifestResources As IEnumerable(Of ResourceDescription),
                       previousGeneration As EmitBaseline,
                       edits As IEnumerable(Of SemanticEdit),
                       isAddedSymbol As Func(Of ISymbol, Boolean))
 
            MyBase.New(sourceAssembly, emitOptions, outputKind, serializationProperties, manifestResources, additionalTypes:=ImmutableArray(Of NamedTypeSymbol).Empty)
 
            Dim initialBaseline = previousGeneration.InitialBaseline
            Dim previousSourceAssembly = DirectCast(previousGeneration.Compilation, VisualBasicCompilation).SourceAssembly
 
            ' Hydrate symbols from initial metadata. Once we do so it is important to reuse these symbols across all generations,
            ' in order for the symbol matcher to be able to use reference equality once it maps symbols to initial metadata.
            Dim metadataSymbols = GetOrCreateMetadataSymbols(initialBaseline, sourceAssembly.DeclaringCompilation)
 
            Dim metadataDecoder = DirectCast(metadataSymbols.MetadataDecoder, MetadataDecoder)
            Dim metadataAssembly = DirectCast(metadataDecoder.ModuleSymbol.ContainingAssembly, PEAssemblySymbol)
            Dim matchToMetadata = New VisualBasicSymbolMatcher(initialBaseline.LazyMetadataSymbols.SynthesizedTypes, sourceAssembly, metadataAssembly)
 
            Dim previousSourceToMetadata = New VisualBasicSymbolMatcher(
                metadataSymbols.SynthesizedTypes,
                previousSourceAssembly,
                metadataAssembly)
 
            Dim matchToPrevious As VisualBasicSymbolMatcher = Nothing
            If previousGeneration.Ordinal > 0 Then
 
                matchToPrevious = New VisualBasicSymbolMatcher(
                    sourceAssembly:=sourceAssembly,
                    otherAssembly:=previousSourceAssembly,
                    previousGeneration.SynthesizedTypes,
                    otherSynthesizedMembersOpt:=previousGeneration.SynthesizedMembers,
                    otherDeletedMembersOpt:=previousGeneration.DeletedMembers)
            End If
 
            _previousDefinitions = New VisualBasicDefinitionMap(edits, metadataDecoder, previousSourceToMetadata, matchToMetadata, matchToPrevious, previousGeneration)
            _changes = New VisualBasicSymbolChanges(_previousDefinitions, edits, isAddedSymbol)
 
            ' Workaround for https://github.com/dotnet/roslyn/issues/3192. 
            ' When compiling state machine we stash types of awaiters and state-machine hoisted variables,
            ' so that next generation can look variables up and reuse their slots if possible.
            '
            ' When we are about to allocate a slot for a lifted variable while compiling the next generation
            ' we map its type to the previous generation and then check the slot types that we stashed earlier.
            ' If the variable type matches we reuse it. In order to compare the previous variable type with the current one
            ' both need to be completely lowered (translated). Standard translation only goes one level deep. 
            ' Generic arguments are not translated until they are needed by metadata writer. 
            '
            ' In order to get the fully lowered form we run the type symbols of stashed variables through a deep translator
            ' that translates the symbol recursively.
            _deepTranslator = New VisualBasicSymbolMatcher.DeepTranslator(sourceAssembly.GetSpecialType(SpecialType.System_Object))
        End Sub
 
        Friend Overrides Function EncTranslateLocalVariableType(type As TypeSymbol, diagnostics As DiagnosticBag) As ITypeReference
            ' Note: The translator is Not aware of synthesized types. If type is a synthesized type it won't get mapped.
            ' In such case use the type itself. This can only happen for variables storing lambda display classes.
            Dim visited = DirectCast(_deepTranslator.Visit(type), TypeSymbol)
            Debug.Assert(visited IsNot Nothing OrElse TypeOf type Is LambdaFrame OrElse TypeOf DirectCast(type, NamedTypeSymbol).ConstructedFrom Is LambdaFrame)
            Return Translate(If(visited, type), Nothing, diagnostics)
        End Function
 
        Public Overrides ReadOnly Property EncSymbolChanges As SymbolChanges
            Get
                Return _changes
            End Get
        End Property
 
        Public Overrides ReadOnly Property PreviousGeneration As EmitBaseline
            Get
                Return _previousDefinitions.Baseline
            End Get
        End Property
 
        Private Shared Function GetOrCreateMetadataSymbols(initialBaseline As EmitBaseline, compilation As VisualBasicCompilation) As EmitBaseline.MetadataSymbols
            If initialBaseline.LazyMetadataSymbols IsNot Nothing Then
                Return initialBaseline.LazyMetadataSymbols
            End If
 
            Dim originalMetadata = initialBaseline.OriginalMetadata
 
            ' The purpose of this compilation is to provide PE symbols for original metadata.
            ' We need to transfer the references from the current source compilation but don't need its syntax trees.
            Dim metadataCompilation = compilation.RemoveAllSyntaxTrees()
 
            Dim assemblyReferenceIdentityMap As ImmutableDictionary(Of AssemblyIdentity, AssemblyIdentity) = Nothing
            Dim metadataAssembly = metadataCompilation.GetBoundReferenceManager().CreatePEAssemblyForAssemblyMetadata(AssemblyMetadata.Create(originalMetadata), MetadataImportOptions.All, assemblyReferenceIdentityMap)
            Dim metadataDecoder = New MetadataDecoder(metadataAssembly.PrimaryModule)
 
            Dim synthesizedTypes = GetSynthesizedTypesFromMetadata(originalMetadata.MetadataReader, metadataDecoder)
            Dim metadataSymbols = New EmitBaseline.MetadataSymbols(synthesizedTypes, metadataDecoder, assemblyReferenceIdentityMap)
 
            Return InterlockedOperations.Initialize(initialBaseline.LazyMetadataSymbols, metadataSymbols)
        End Function
 
        ' friend for testing
        Friend Overloads Shared Function GetSynthesizedTypesFromMetadata(reader As MetadataReader, metadataDecoder As MetadataDecoder) As SynthesizedTypeMaps
            ' In general, the anonymous type name Is 'VB$Anonymous' ('Type'|'Delegate') '_' (submission-index '_')? index module-id
            ' but EnC Is not supported for modules nor submissions. Hence we only look for type names with no module id and no submission index:
            ' e.g. VB$AnonymousType_123, VB$AnonymousDelegate_123
 
            Dim anonymousTypes = ImmutableSegmentedDictionary.CreateBuilder(Of AnonymousTypeKey, AnonymousTypeValue)
            For Each handle In reader.TypeDefinitions
                Dim def = reader.GetTypeDefinition(handle)
                If Not def.Namespace.IsNil Then
                    Continue For
                End If
 
                If Not reader.StringComparer.StartsWith(def.Name, GeneratedNameConstants.AnonymousTypeOrDelegateCommonPrefix) Then
                    Continue For
                End If
 
                Dim metadataName = reader.GetString(def.Name)
                Dim arity As Short = 0
                Dim name = MetadataHelpers.InferTypeArityAndUnmangleMetadataName(metadataName, arity)
 
                Dim index As Integer = 0
                If TryParseAnonymousTypeTemplateName(GeneratedNameConstants.AnonymousTypeTemplateNamePrefix, name, index) Then
                    Dim type = DirectCast(metadataDecoder.GetTypeOfToken(handle), NamedTypeSymbol)
                    Dim key = GetAnonymousTypeKey(type)
                    Dim value = New AnonymousTypeValue(name, index, type.GetCciAdapter())
                    anonymousTypes.Add(key, value)
                ElseIf TryParseAnonymousTypeTemplateName(GeneratedNameConstants.AnonymousDelegateTemplateNamePrefix, name, index) Then
                    Dim type = DirectCast(metadataDecoder.GetTypeOfToken(handle), NamedTypeSymbol)
                    Dim key = GetAnonymousDelegateKey(type)
                    Dim value = New AnonymousTypeValue(name, index, type.GetCciAdapter())
                    anonymousTypes.Add(key, value)
                End If
            Next
 
            ' VB anonymous delegates are handled as anonymous types
            Return New SynthesizedTypeMaps(
                anonymousTypes.ToImmutable(),
                anonymousDelegates:=Nothing,
                anonymousDelegatesWithIndexedNames:=Nothing)
        End Function
 
        Friend Shared Function TryParseAnonymousTypeTemplateName(prefix As String, name As String, <Out()> ByRef index As Integer) As Boolean
            If name.StartsWith(prefix, StringComparison.Ordinal) AndAlso
                Integer.TryParse(name.Substring(prefix.Length), NumberStyles.None, CultureInfo.InvariantCulture, index) Then
                Return True
            End If
            index = -1
            Return False
        End Function
 
        Private Shared Function GetAnonymousTypeKey(type As NamedTypeSymbol) As AnonymousTypeKey
            ' The key is the set of properties that correspond to type parameters.
            ' For each type parameter, get the name of the property of that type.
            Dim n = type.TypeParameters.Length
            If n = 0 Then
                Return New AnonymousTypeKey(ImmutableArray(Of AnonymousTypeKeyField).Empty)
            End If
 
            ' Properties indexed by type parameter ordinal.
            Dim properties = New AnonymousTypeKeyField(n - 1) {}
            For Each member In type.GetMembers()
                If member.Kind <> SymbolKind.Property Then
                    Continue For
                End If
 
                Dim [property] = DirectCast(member, PropertySymbol)
                Dim propertyType = [property].Type
                If propertyType.TypeKind = TypeKind.TypeParameter Then
                    Dim typeParameter = DirectCast(propertyType, TypeParameterSymbol)
                    Debug.Assert(TypeSymbol.Equals(DirectCast(typeParameter.ContainingSymbol, TypeSymbol), type, TypeCompareKind.ConsiderEverything))
                    Dim index = typeParameter.Ordinal
                    Debug.Assert(properties(index).Name Is Nothing)
                    ' ReadOnly anonymous type properties were 'Key' properties.
                    properties(index) = New AnonymousTypeKeyField([property].Name, isKey:=[property].IsReadOnly, ignoreCase:=True)
                End If
            Next
 
            Debug.Assert(properties.All(Function(f) Not String.IsNullOrEmpty(f.Name)))
            Return New AnonymousTypeKey(ImmutableArray.Create(properties))
        End Function
 
        Private Shared Function GetAnonymousDelegateKey(type As NamedTypeSymbol) As AnonymousTypeKey
            Debug.Assert(type.BaseTypeNoUseSiteDiagnostics.SpecialType = SpecialType.System_MulticastDelegate)
 
            ' The key is the set of parameter names to the Invoke method,
            ' where the parameters are of the type parameters.
            Dim members = type.GetMembers(WellKnownMemberNames.DelegateInvokeName)
            Debug.Assert(members.Length = 1 AndAlso members(0).Kind = SymbolKind.Method)
            Dim method = DirectCast(members(0), MethodSymbol)
            Debug.Assert(method.Parameters.Length + If(method.IsSub, 0, 1) = type.TypeParameters.Length)
            Dim parameters = ArrayBuilder(Of AnonymousTypeKeyField).GetInstance()
            parameters.AddRange(method.Parameters.SelectAsArray(Function(p) New AnonymousTypeKeyField(p.Name, isKey:=p.IsByRef, ignoreCase:=True)))
            parameters.Add(New AnonymousTypeKeyField(AnonymousTypeDescriptor.GetReturnParameterName(Not method.IsSub), isKey:=False, ignoreCase:=True))
            Return New AnonymousTypeKey(parameters.ToImmutableAndFree(), isDelegate:=True)
        End Function
 
        Friend ReadOnly Property PreviousDefinitions As VisualBasicDefinitionMap
            Get
                Return _previousDefinitions
            End Get
        End Property
 
        Friend Overloads Function GetSynthesizedTypes() As SynthesizedTypeMaps Implements IPEDeltaAssemblyBuilder.GetSynthesizedTypes
            ' VB anonymous delegates are handled as anonymous types
            Dim result = New SynthesizedTypeMaps(
                Compilation.AnonymousTypeManager.GetAnonymousTypeMap(),
                anonymousDelegates:=Nothing,
                anonymousDelegatesWithIndexedNames:=Nothing)
 
            ' Should contain all entries in previous generation.
            Debug.Assert(PreviousGeneration.SynthesizedTypes.IsSubsetOf(result))
 
            Return result
        End Function
 
        Friend Overrides Function TryCreateVariableSlotAllocator(method As MethodSymbol, topLevelMethod As MethodSymbol, diagnostics As DiagnosticBag) As VariableSlotAllocator
            Return _previousDefinitions.TryCreateVariableSlotAllocator(Compilation, method, topLevelMethod, diagnostics)
        End Function
 
        Friend Overrides Function GetMethodBodyInstrumentations(method As MethodSymbol) As MethodInstrumentation
            Return _previousDefinitions.GetMethodBodyInstrumentations(method)
        End Function
 
        Friend Overrides Function GetPreviousAnonymousTypes() As ImmutableArray(Of AnonymousTypeKey)
            Return ImmutableArray.CreateRange(PreviousGeneration.SynthesizedTypes.AnonymousTypes.Keys)
        End Function
 
        Friend Overrides Function GetNextAnonymousTypeIndex(fromDelegates As Boolean) As Integer
            Return PreviousGeneration.GetNextAnonymousTypeIndex(fromDelegates)
        End Function
 
        Friend Overrides Function TryGetAnonymousTypeName(template As AnonymousTypeManager.AnonymousTypeOrDelegateTemplateSymbol, <Out> ByRef name As String, <Out> ByRef index As Integer) As Boolean
            Debug.Assert(Compilation Is template.DeclaringCompilation)
            Return _previousDefinitions.TryGetAnonymousTypeName(template, name, index)
        End Function
 
        Public Overrides Function GetTopLevelTypeDefinitions(context As EmitContext) As IEnumerable(Of Cci.INamespaceTypeDefinition)
            Return GetTopLevelTypeDefinitionsCore(context)
        End Function
 
        Public Overrides Function GetTopLevelSourceTypeDefinitions(context As EmitContext) As IEnumerable(Of Cci.INamespaceTypeDefinition)
            Return _changes.GetTopLevelSourceTypeDefinitions(context)
        End Function
 
        Friend Sub OnCreatedIndices(diagnostics As DiagnosticBag) Implements IPEDeltaAssemblyBuilder.OnCreatedIndices
            Dim embeddedTypesManager = Me.EmbeddedTypesManagerOpt
            If embeddedTypesManager IsNot Nothing Then
                For Each embeddedType In embeddedTypesManager.EmbeddedTypesMap.Keys
                    diagnostics.Add(ErrorFactory.ErrorInfo(ERRID.ERR_EncNoPIAReference, embeddedType.AdaptedNamedTypeSymbol), Location.None)
                Next
            End If
        End Sub
 
        Friend Overrides ReadOnly Property AllowOmissionOfConditionalCalls As Boolean
            Get
                Return True
            End Get
        End Property
 
        Public Overrides ReadOnly Property LinkedAssembliesDebugInfo As IEnumerable(Of String)
            Get
                ' This debug information is only emitted for the benefit of legacy EE.
                ' Since EnC requires Roslyn and Roslyn doesn't need this information we don't emit it during EnC.
                Return SpecializedCollections.EmptyEnumerable(Of String)()
            End Get
        End Property
    End Class
End Namespace