File: Symbols\AnonymousTypes\AnonymousTypeManager_Templates.vb
Web Access
Project: src\src\Compilers\VisualBasic\Portable\Microsoft.CodeAnalysis.VisualBasic.vbproj (Microsoft.CodeAnalysis.VisualBasic)
' Licensed to the .NET Foundation under one or more agreements.
' The .NET Foundation licenses this file to you under the MIT license.
' See the LICENSE file in the project root for more information.
 
Imports System.Collections.Concurrent
Imports System.Collections.Generic
Imports System.Collections.Immutable
Imports System.Diagnostics
Imports System.Threading
Imports Microsoft.CodeAnalysis
Imports Microsoft.CodeAnalysis.Collections
Imports Microsoft.CodeAnalysis.PooledObjects
 
Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols
 
    Partial Friend NotInheritable Class AnonymousTypeManager
 
        ''' <summary> Cache of created anonymous types </summary>
        Private _concurrentTypesCache As ConcurrentDictionary(Of String, AnonymousTypeTemplateSymbol) = Nothing
 
        ''' <summary> Cache of created anonymous delegates </summary>
        Private _concurrentDelegatesCache As ConcurrentDictionary(Of String, AnonymousDelegateTemplateSymbol) = Nothing
 
#If DEBUG Then
        ''' <summary>
        ''' Holds a collection of all the locations of anonymous types and delegates from source
        ''' </summary>
        Private ReadOnly _sourceLocationsSeen As New ConcurrentDictionary(Of Location, Boolean)
#End If
 
        <Conditional("DEBUG")>
        Private Sub CheckSourceLocationSeen(anonymous As AnonymousTypeOrDelegatePublicSymbol)
#If DEBUG Then
            Dim location As Location = anonymous.Locations(0)
            If location.IsInSource Then
                If Me.AreTemplatesSealed Then
                    Debug.Assert(Me._sourceLocationsSeen.ContainsKey(location))
                Else
                    Me._sourceLocationsSeen.TryAdd(location, True)
                End If
            End If
#End If
        End Sub
 
        Private ReadOnly Property AnonymousTypeTemplates As ConcurrentDictionary(Of String, AnonymousTypeTemplateSymbol)
            Get
                ' Lazily create a template types cache
                If Me._concurrentTypesCache Is Nothing Then
 
                    Dim previousSubmission As VisualBasicCompilation = Me.Compilation.PreviousSubmission
                    Dim previousCache = If(previousSubmission Is Nothing, Nothing,
                                       previousSubmission.AnonymousTypeManager._concurrentTypesCache)
 
                    Interlocked.CompareExchange(Me._concurrentTypesCache,
                                            If(previousCache Is Nothing,
                                               New ConcurrentDictionary(Of String, AnonymousTypeTemplateSymbol),
                                               New ConcurrentDictionary(Of String, AnonymousTypeTemplateSymbol)(previousCache)),
                                            Nothing)
                End If
 
                Return Me._concurrentTypesCache
            End Get
        End Property
 
        Private ReadOnly Property AnonymousDelegateTemplates As ConcurrentDictionary(Of String, AnonymousDelegateTemplateSymbol)
            Get
                If Me._concurrentDelegatesCache Is Nothing Then
                    Dim previousSubmission As VisualBasicCompilation = Me.Compilation.PreviousSubmission
                    Dim previousCache = If(previousSubmission Is Nothing, Nothing,
                                       previousSubmission.AnonymousTypeManager._concurrentDelegatesCache)
 
                    Interlocked.CompareExchange(Me._concurrentDelegatesCache,
                                            If(previousCache Is Nothing,
                                               New ConcurrentDictionary(Of String, AnonymousDelegateTemplateSymbol),
                                               New ConcurrentDictionary(Of String, AnonymousDelegateTemplateSymbol)(previousCache)),
                                            Nothing)
                End If
 
                Return Me._concurrentDelegatesCache
            End Get
        End Property
 
        ''' <summary> 
        ''' Given anonymous type public symbol construct an anonymous type symbol to be used 
        ''' in emit; the type symbol is created based on generic type generated for each 
        ''' 'unique' anonymous type structure.
        ''' </summary>
        Private Function ConstructAnonymousTypeImplementationSymbol(anonymous As AnonymousTypePublicSymbol) As NamedTypeSymbol
            CheckSourceLocationSeen(anonymous)
 
            Dim typeDescr As AnonymousTypeDescriptor = anonymous.TypeDescriptor
            typeDescr.AssertGood()
 
            ' Get anonymous type template
            Dim template As AnonymousTypeTemplateSymbol = Nothing
            Dim typeKey As String = typeDescr.Key
 
            If Not AnonymousTypeTemplates.TryGetValue(typeKey, template) Then
                template = AnonymousTypeTemplates.GetOrAdd(typeKey, New AnonymousTypeTemplateSymbol(Me, typeDescr))
            End If
 
            ' Adjust names in the template
            If template.Manager Is Me Then
                template.AdjustMetadataNames(typeDescr)
            End If
 
            ' Specialize anonymous type template with field types, adjusted names and locations
            Dim typeArguments = typeDescr.Fields.SelectAsArray(Function(f) f.Type)
            Return template.Construct(typeArguments)
        End Function
 
        ''' <summary> 
        ''' Given anonymous delegate public symbol construct an anonymous type symbol to be 
        ''' used in emit; the type symbol may be created based on generic type generated for 
        ''' each 'unique' anonymous delegate structure OR if the delegate's signature is 
        ''' 'Sub()' it will be an instance of NonGenericAnonymousDelegateSymbol type.
        ''' </summary>
        Private Function ConstructAnonymousDelegateImplementationSymbol(anonymous As AnonymousDelegatePublicSymbol) As NamedTypeSymbol
            CheckSourceLocationSeen(anonymous)
 
            Dim delegateDescr As AnonymousTypeDescriptor = anonymous.TypeDescriptor
            delegateDescr.AssertGood()
            Dim parameters As ImmutableArray(Of AnonymousTypeField) = delegateDescr.Parameters
 
            ' Get anonymous template
            Dim template As AnonymousDelegateTemplateSymbol = Nothing
            Dim delegateKey As String = delegateDescr.Key
 
            If Not AnonymousDelegateTemplates.TryGetValue(delegateKey, template) Then
                template = AnonymousDelegateTemplates.GetOrAdd(delegateKey, AnonymousDelegateTemplateSymbol.Create(Me, delegateDescr))
            End If
 
            ' Adjust names in the template
            If template.Manager Is Me Then
                template.AdjustMetadataNames(delegateDescr)
            End If
 
            ' 'template' may be an instance of NonGenericAnonymousDelegateSymbol if which case 
            ' we can just return it, otherwise we need to construct type using the parameters
            If template.Arity = 0 Then
                Return template
            End If
 
            ' Specialize anonymous delegate template with parameter types, adjusted names and locations
            Dim typeArguments() As TypeSymbol = New TypeSymbol(template.Arity - 1) {}
            For index = 0 To template.Arity - 1
                typeArguments(index) = parameters(index).Type
            Next
            Return template.Construct(typeArguments)
        End Function
 
        Private Sub AddFromCache(Of T As AnonymousTypeOrDelegateTemplateSymbol)(
                            builder As ArrayBuilder(Of AnonymousTypeOrDelegateTemplateSymbol),
                            cache As ConcurrentDictionary(Of String, T))
 
            If cache IsNot Nothing Then
                For Each template In cache.Values
                    If template.Manager Is Me Then
                        builder.Add(template)
                    End If
                Next
            End If
        End Sub
 
        Private Shared Function CreatePlaceholderTypeDescriptor(key As Microsoft.CodeAnalysis.Emit.AnonymousTypeKey) As AnonymousTypeDescriptor
            Dim names = key.Fields.SelectAsArray(Function(f) New AnonymousTypeField(f.Name, Location.None, f.IsKey))
            Return New AnonymousTypeDescriptor(names, Location.None, True)
        End Function
 
        ''' <summary>
        ''' Resets numbering in anonymous type names and compiles the
        ''' anonymous type methods. Also seals the collection of templates.
        ''' </summary>
        Public Sub AssignTemplatesNamesAndCompile(compiler As MethodCompiler, moduleBeingBuilt As Emit.PEModuleBuilder, diagnostics As BindingDiagnosticBag)
 
            ' Ensure all previous anonymous type templates are included so the
            ' types are available for subsequent edit and continue generations.
            For Each key In moduleBeingBuilt.GetPreviousAnonymousTypes()
                Dim templateKey = AnonymousTypeDescriptor.ComputeKey(key.Fields, Function(f) f.Name, Function(f) f.IsKey)
                If key.IsDelegate Then
                    AnonymousDelegateTemplates.GetOrAdd(templateKey, Function(k) AnonymousDelegateTemplateSymbol.Create(Me, CreatePlaceholderTypeDescriptor(key)))
                Else
                    AnonymousTypeTemplates.GetOrAdd(templateKey, Function(k) New AnonymousTypeTemplateSymbol(Me, CreatePlaceholderTypeDescriptor(key)))
                End If
            Next
 
            ' Get all anonymous types owned by this manager
            Dim builder = ArrayBuilder(Of AnonymousTypeOrDelegateTemplateSymbol).GetInstance()
            GetAllCreatedTemplates(builder)
 
            ' If the collection is not sealed yet we should assign new indexes 
            ' to the created anonymous type and delegate templates
            If Not Me.AreTemplatesSealed Then
 
                ' If we are emitting .NET module, include module's name into type's name to ensure
                ' uniqueness across added modules.
                Dim moduleId As String
 
                If moduleBeingBuilt.OutputKind = OutputKind.NetModule Then
                    moduleId = moduleBeingBuilt.Name
                    Dim extension As String = OutputKind.NetModule.GetDefaultExtension()
 
                    If moduleId.EndsWith(extension, StringComparison.OrdinalIgnoreCase) Then
                        moduleId = moduleId.Substring(0, moduleId.Length - extension.Length)
                    End If
 
                    moduleId = "<" & MetadataHelpers.MangleForTypeNameIfNeeded(moduleId) & ">"
                Else
                    moduleId = String.Empty
                End If
 
                Dim typeIndex = moduleBeingBuilt.GetNextAnonymousTypeIndex(fromDelegates:=False)
                Dim delegateIndex = moduleBeingBuilt.GetNextAnonymousTypeIndex(fromDelegates:=True)
                For Each template In builder
                    Dim name As String = Nothing
                    Dim index As Integer = 0
                    If Not moduleBeingBuilt.TryGetAnonymousTypeName(template, name, index) Then
                        Select Case template.TypeKind
                            Case TypeKind.Delegate
                                index = delegateIndex
                                delegateIndex += 1
                            Case TypeKind.Class
                                index = typeIndex
                                typeIndex += 1
                            Case Else
                                Throw ExceptionUtilities.UnexpectedValue(template.TypeKind)
                        End Select
                        Dim slotIndex = Compilation.GetSubmissionSlotIndex()
                        name = GeneratedNames.MakeAnonymousTypeTemplateName(template.GeneratedNamePrefix, index, slotIndex, moduleId)
                    End If
                    ' normally it should only happen once, but in case there is a race
                    ' NameAndIndex.set has an assert which guarantees that the
                    ' template name provided is the same as the one already assigned
                    template.NameAndIndex = New NameAndIndex(name, index)
                Next
 
                Me.SealTemplates()
            End If
 
            If builder.Count > 0 AndAlso Not Me.CheckAndReportMissingSymbols(builder, diagnostics) Then
 
                ' Process all the templates
                For Each template In builder
                    template.Accept(compiler)
                Next
            End If
 
            builder.Free()
        End Sub
 
        Friend Function GetAnonymousTypeMap() As ImmutableSegmentedDictionary(Of Microsoft.CodeAnalysis.Emit.AnonymousTypeKey, Microsoft.CodeAnalysis.Emit.AnonymousTypeValue)
            Dim templates = ArrayBuilder(Of AnonymousTypeOrDelegateTemplateSymbol).GetInstance()
            GetAllCreatedTemplates(templates)
 
            Dim result = templates.ToImmutableSegmentedDictionary(
                keySelector:=Function(template) template.GetAnonymousTypeKey(),
                elementSelector:=Function(template) New Microsoft.CodeAnalysis.Emit.AnonymousTypeValue(template.NameAndIndex.Name, template.NameAndIndex.Index, template.GetCciAdapter()))
 
            templates.Free()
            Return result
        End Function
 
        ''' <summary>
        ''' Translates anonymous type public symbol into an implementation type symbol to be used in emit.
        ''' </summary>
        Friend Shared Function TranslateAnonymousTypeSymbol(type As NamedTypeSymbol) As NamedTypeSymbol
            Debug.Assert(type IsNot Nothing)
            Debug.Assert(type.IsAnonymousType)
 
            Dim anonymous = DirectCast(type, AnonymousTypeManager.AnonymousTypeOrDelegatePublicSymbol)
            Return anonymous.MapToImplementationSymbol()
        End Function
 
        ''' <summary>
        ''' Translates anonymous type method symbol into an implementation method symbol to be used in emit.
        ''' </summary>
        Friend Shared Function TranslateAnonymousTypeMethodSymbol(method As MethodSymbol) As MethodSymbol
            Dim type = method.ContainingType
            Debug.Assert(type.IsAnonymousType)
 
            Dim anonymousType = DirectCast(type, AnonymousTypeManager.AnonymousTypeOrDelegatePublicSymbol)
            Return anonymousType.MapMethodToImplementationSymbol(method)
        End Function
 
        ''' <summary> Returns all templates owned by this type manager </summary>
        Friend ReadOnly Property AllCreatedTemplates As ImmutableArray(Of NamedTypeSymbol)
            Get
                ' NOTE: templates may not be sealed at this point in case metadata is being emitted without IL
                Dim builder = ArrayBuilder(Of AnonymousTypeOrDelegateTemplateSymbol).GetInstance()
                GetAllCreatedTemplates(builder)
                Return StaticCast(Of NamedTypeSymbol).From(builder.ToImmutableAndFree())
            End Get
        End Property
 
        Private Sub GetAllCreatedTemplates(builder As ArrayBuilder(Of AnonymousTypeOrDelegateTemplateSymbol))
            Debug.Assert(Not builder.Any())
 
            AddFromCache(builder, Me._concurrentTypesCache)
            AddFromCache(builder, Me._concurrentDelegatesCache)
 
            If builder.Any() Then
                ' Sort types and delegates using smallest location
                builder.Sort(New AnonymousTypeComparer(Me.Compilation))
            End If
        End Sub
 
        Private NotInheritable Class AnonymousTypeComparer
            Implements IComparer(Of AnonymousTypeOrDelegateTemplateSymbol)
 
            Private ReadOnly _compilation As VisualBasicCompilation
 
            Friend Sub New(compilation As VisualBasicCompilation)
                Me._compilation = compilation
            End Sub
 
            Public Function Compare(x As AnonymousTypeOrDelegateTemplateSymbol, y As AnonymousTypeOrDelegateTemplateSymbol) As Integer Implements IComparer(Of AnonymousTypeOrDelegateTemplateSymbol).Compare
                If x Is y Then
                    Return 0
                End If
 
                ' We compare two anonymous type templates by comparing their smallest locations
                ' NOTE: If anonymous type got to this phase it must have the location set
                Dim result = CompareLocations(x.SmallestLocation, y.SmallestLocation)
                If result = 0 Then
                    ' Two templates may have same location if they are created indirectly (for example for queries) 
                    ' and reference the same syntax node, in this case we also compare 'keys' of their descriptors
                    result = x.TypeDescriptorKey.CompareTo(y.TypeDescriptorKey)
                End If
 
                Debug.Assert(result <> 0)
                Return result
            End Function
 
            Private Function CompareLocations(x As Location, y As Location) As Integer
                If x Is y Then
                    Return 0
                ElseIf x = Location.None Then
                    Return -1
                ElseIf y = Location.None Then
                    Return 1
                Else
                    Return _compilation.CompareSourceLocations(x, y)
                End If
            End Function
        End Class
 
    End Class
 
End Namespace