File: Binding\Binder_AnonymousTypes.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.Runtime.InteropServices
Imports Microsoft.CodeAnalysis.CodeGen
Imports Microsoft.CodeAnalysis.Collections
Imports Microsoft.CodeAnalysis.PooledObjects
Imports Microsoft.CodeAnalysis.Text
Imports Microsoft.CodeAnalysis.VisualBasic.Symbols
Imports Microsoft.CodeAnalysis.VisualBasic.Syntax
Imports TypeKind = Microsoft.CodeAnalysis.TypeKind
 
Namespace Microsoft.CodeAnalysis.VisualBasic
 
    ' This portion of the binder converts an ExpressionSyntax into a BoundExpression
 
    Partial Friend Class Binder
 
        Private Function BindAnonymousObjectCreationExpression(node As AnonymousObjectCreationExpressionSyntax, diagnostics As BindingDiagnosticBag) As BoundExpression
            Return AnonymousTypeCreationBinder.BindAnonymousObjectInitializer(Me, node, node.Initializer, node.NewKeyword, diagnostics)
        End Function
 
        Private Function BindAnonymousObjectCreationExpression(node As VisualBasicSyntaxNode,
                                                               typeDescr As AnonymousTypeDescriptor,
                                                               initExpressions As ImmutableArray(Of BoundExpression),
                                                               diagnostics As BindingDiagnosticBag) As BoundExpression
            '  Check for restricted types.
            For Each field As AnonymousTypeField In typeDescr.Fields
                Dim restrictedType As TypeSymbol = Nothing
                If field.Type.IsRestrictedTypeOrArrayType(restrictedType) Then
                    ReportDiagnostic(diagnostics, field.Location, ERRID.ERR_RestrictedType1, restrictedType)
                End If
            Next
 
            Return CreateAnonymousObjectCreationExpression(node, typeDescr, initExpressions)
        End Function
 
        Private Function CreateAnonymousObjectCreationExpression(node As VisualBasicSyntaxNode,
                                                               typeDescr As AnonymousTypeDescriptor,
                                                               initExpressions As ImmutableArray(Of BoundExpression),
                                                               Optional hasErrors As Boolean = False) As BoundExpression
            '  Get or create an anonymous type
            Dim anonymousType As AnonymousTypeManager.AnonymousTypePublicSymbol =
                Me.Compilation.AnonymousTypeManager.ConstructAnonymousTypeSymbol(typeDescr)
 
            ' get constructor
            Dim constructor As MethodSymbol = anonymousType.InstanceConstructors.First()
            Debug.Assert(constructor IsNot Nothing)
            Debug.Assert(constructor.ParameterCount = initExpressions.Length)
 
            Return CreateAnonymousObjectCreationExpression(node, anonymousType, initExpressions, hasErrors)
        End Function
 
        Protected Overridable Function CreateAnonymousObjectCreationExpression(node As VisualBasicSyntaxNode,
                                                                               anonymousType As AnonymousTypeManager.AnonymousTypePublicSymbol,
                                                                               initExpressions As ImmutableArray(Of BoundExpression),
                                                                               Optional hasErrors As Boolean = False) As BoundAnonymousTypeCreationExpression
 
            ' By default BoundAnonymousTypeCreationExpression is created without 
            ' locals and bound nodes for 'declarations' of properties
            Return New BoundAnonymousTypeCreationExpression(node, Nothing,
                                                            ImmutableArray(Of BoundAnonymousTypePropertyAccess).Empty,
                                                            initExpressions, anonymousType, hasErrors)
        End Function
 
        ''' <summary>
        ''' Binder to be used for binding New With { ... } expressions. 
        ''' </summary>
        Friend Class AnonymousTypeCreationBinder
            Inherits Binder
 
            ' NOTE: field descriptors in 'Fields' array are mutable, field types are being assigned 
            '       in successfully processed fields as we go through the field initializers
            Private ReadOnly _fields() As AnonymousTypeField
 
            ' NOTE: after the binder is initialized, some elements in 'Fields' may remain empty because 
            '       errors in field declaration; '_fieldName2index' map actually stores the list of 
            '       'good' fields into corresponding slots in 'Fields', those slots must not be empty
            Private ReadOnly _fieldName2index As Dictionary(Of String, Integer)
 
            ' field declaration bound node is created for fields with implicitly 
            ' specified name to provide semantic info on those identifier;
            ' the array builder is being created lazily if needed
            Private _fieldDeclarations As ArrayBuilder(Of BoundAnonymousTypePropertyAccess)
 
            ' '_locals' field points to an array which holds locals introduced during binding
            ' for each field which is being used in other field initialization;
            ' Thus while binding 'New With { .a = 1, .b = 1 + .a }' a local will be created to 
            ' hold the value of '1' and to be used as '.a = <local_a>' and '.b = 1 + <local_a>'
            ' Note that no local is created for not referenced fields (like '.b' in the example 
            ' above) leaving correspondent slots in '_locals' empty.
            Private ReadOnly _locals() As LocalSymbol
 
            Private ReadOnly _propertySymbols() As PropertySymbol
 
            ''' <summary>
            ''' If set, the state of the binder shouldn't be modified by subsequent binding operations,
            ''' which could be performed by SemanticModel in context of this binder.
            ''' </summary>
            Private _freeze As Boolean
 
            Friend Shared Function BindAnonymousObjectInitializer(containingBinder As Binder,
                                                                  owningSyntax As VisualBasicSyntaxNode,
                                                                  initializerSyntax As ObjectMemberInitializerSyntax,
                                                                  typeLocationToken As SyntaxToken,
                                                                  diagnostics As BindingDiagnosticBag) As BoundExpression
 
                Dim fieldsCount = initializerSyntax.Initializers.Count
 
                If fieldsCount = 0 Then
                    ' ERR_AnonymousTypeNeedField must have been reported in Parser
                    Return BadExpression(owningSyntax, ImmutableArray(Of BoundExpression).Empty, ErrorTypeSymbol.UnknownResultType)
                End If
 
                Return New AnonymousTypeCreationBinder(containingBinder, initializerSyntax, diagnostics).
                            BindInitializersAndCreateBoundNode(owningSyntax, initializerSyntax, diagnostics, typeLocationToken)
            End Function
 
#Region "Binder Creation and Initial Analysis"
 
            Private Sub New(containingBinder As Binder,
                            initializerSyntax As ObjectMemberInitializerSyntax,
                            diagnostics As BindingDiagnosticBag)
                MyBase.New(containingBinder)
 
                Dim objectType As TypeSymbol = GetSpecialType(SpecialType.System_Object, initializerSyntax, diagnostics)
 
                ' Examine 'initializerSyntax' node and builds the list of anonymous type field declarations
                ' with no type assigned to fields yet (those will be assigned when the binder binds field 
                ' initializers one-by-one). Some diagnostics may be reported as we go...
                Dim initializers = initializerSyntax.Initializers
                Dim initializersCount As Integer = initializers.Count
                Debug.Assert(initializersCount > 0)
 
                ' Initialize binder fields
                Me._fieldName2index = New Dictionary(Of String, Integer)(initializersCount, CaseInsensitiveComparison.Comparer)
                Me._fields = New AnonymousTypeField(initializersCount - 1) {}
                Me._fieldDeclarations = Nothing
                Me._locals = New LocalSymbol(initializersCount - 1) {}
                Me._propertySymbols = New PropertySymbol(initializersCount - 1) {}
 
                '  Process field initializers
                For fieldIndex = 0 To initializersCount - 1
                    Dim fieldSyntax As FieldInitializerSyntax = initializers(fieldIndex)
 
                    Dim fieldName As String = Nothing
                    Dim fieldNode As VisualBasicSyntaxNode = Nothing
                    Dim fieldIsKey As Boolean = False
 
                    ' get field's name 
                    If fieldSyntax.Kind = SyntaxKind.InferredFieldInitializer Then
                        Dim inferredFieldInitializer = DirectCast(fieldSyntax, InferredFieldInitializerSyntax)
 
                        Dim fieldNameToken As SyntaxToken = inferredFieldInitializer.Expression.ExtractAnonymousTypeMemberName(Nothing)
 
                        If fieldNameToken.Kind = SyntaxKind.None Then
                            ' failed to infer field name, create a dummy field descriptor
                            ' NOTE: errors are supposed to be reported by parser
                            fieldName = Nothing
                            fieldNode = inferredFieldInitializer.Expression
                            fieldIsKey = False
 
                        Else
                            ' field name successfully inferred
                            fieldName = fieldNameToken.ValueText
                            fieldNode = DirectCast(fieldNameToken.Parent, VisualBasicSyntaxNode)
                            fieldIsKey = inferredFieldInitializer.KeyKeyword.Kind = SyntaxKind.KeyKeyword
                        End If
 
                    Else
                        ' field name is specified implicitly
                        Dim namedFieldInitializer = DirectCast(fieldSyntax, NamedFieldInitializerSyntax)
                        fieldNode = namedFieldInitializer.Name
                        fieldIsKey = namedFieldInitializer.KeyKeyword.Kind = SyntaxKind.KeyKeyword
                        fieldName = namedFieldInitializer.Name.Identifier.ValueText
                    End If
 
                    '  check type character
                    Dim typeChar As TypeCharacter = ExtractTypeCharacter(fieldNode)
                    If typeChar <> TypeCharacter.None Then
                        ' report the error and proceed to the next field initializer
                        ReportDiagnostic(diagnostics, fieldSyntax, ERRID.ERR_AnonymousTypeDisallowsTypeChar)
                    End If
 
                    If String.IsNullOrEmpty(fieldName) Then
                        ' since the field does not have name, we generate a pseudo name to be used in template
                        fieldName = "$"c & fieldIndex.ToString()
 
                    Else
                        ' check the name for duplications (in System.Object and in the list of fields)
                        If objectType.GetMembers(fieldName).Any() OrElse Me._fieldName2index.ContainsKey(fieldName) Then
                            ' report the error 
                            ReportDiagnostic(diagnostics, fieldSyntax, ErrorFactory.ErrorInfo(ERRID.ERR_DuplicateAnonTypeMemberName1, fieldName))
                        End If
                    End If
 
                    ' build anonymous type field descriptor
                    Me._fields(fieldIndex) = New AnonymousTypeField(fieldName, fieldNode.GetLocation(), fieldIsKey)
                    Me._fieldName2index(fieldName) = fieldIndex ' This might overwrite fields in error-cases
                Next
            End Sub
 
#End Region
 
#Region "Binding of anonymous type creation field initializers"
 
            Private Function BindInitializersAndCreateBoundNode(owningSyntax As VisualBasicSyntaxNode,
                                                                initializerSyntax As ObjectMemberInitializerSyntax,
                                                                diagnostics As BindingDiagnosticBag,
                                                                typeLocationToken As SyntaxToken) As BoundExpression
                Dim fieldsCount As Integer = Me._fields.Length
 
                ' Try to bind expressions from field initializers one-by-one; after each of the 
                ' expression is bound successfully assign the type of the field in 'fields'.
                Dim boundInitializers(fieldsCount - 1) As BoundExpression
 
                ' WARNING: Note that SemanticModel.GetDeclaredSymbol for field initializer node relies on 
                '          the fact that the order of properties in anonymous type template corresponds 
                '          1-to-1 to the appropriate filed initializer syntax nodes; This means such 
                '          correspondence must be preserved all the time including erroneous scenarios
 
                ' NOTE: if one field initializer references another, the binder creates an 
                '       BoundAnonymousTypePropertyAccess node to represent the value of the field, 
                '       if the field referenced is not processed yet an error will be generated
                For index = 0 To fieldsCount - 1
                    Dim initializer As FieldInitializerSyntax = initializerSyntax.Initializers(index)
 
                    ' to be used if we need to create BoundAnonymousTypePropertyAccess node
                    Dim namedFieldInitializer As NamedFieldInitializerSyntax = Nothing
 
                    Dim initExpression As ExpressionSyntax = Nothing
                    If initializer.Kind = SyntaxKind.InferredFieldInitializer Then
                        initExpression = DirectCast(initializer, InferredFieldInitializerSyntax).Expression
                    Else
                        namedFieldInitializer = DirectCast(initializer, NamedFieldInitializerSyntax)
                        initExpression = namedFieldInitializer.Expression
                    End If
 
                    Dim initializerBinder As New AnonymousTypeFieldInitializerBinder(Me, index)
 
                    Dim boundExpression As BoundExpression = initializerBinder.BindRValue(initExpression, diagnostics)
                    boundExpression = New BoundAnonymousTypeFieldInitializer(initializer, initializerBinder, boundExpression, boundExpression.Type).MakeCompilerGenerated()
 
                    boundInitializers(index) = boundExpression
 
                    Dim fieldType As TypeSymbol = boundExpression.Type
 
                    '  check for restricted type
                    Dim restrictedType As TypeSymbol = Nothing
                    If fieldType.IsRestrictedTypeOrArrayType(restrictedType) Then
                        ReportDiagnostic(diagnostics, initExpression, ERRID.ERR_RestrictedType1, restrictedType)
                    End If
 
                    ' always assign the type, event if there were errors in binding and/or 
                    ' the type is an error type, we are going to use it for anonymous type fields
                    Me._fields(index).AssignFieldType(fieldType)
 
                    If namedFieldInitializer IsNot Nothing Then
                        ' create an instance of BoundAnonymousTypePropertyAccess to 
                        ' guarantee semantic info on the identifier
 
                        If Me._fieldDeclarations Is Nothing Then
                            Me._fieldDeclarations = ArrayBuilder(Of BoundAnonymousTypePropertyAccess).GetInstance()
                        End If
 
                        Me._fieldDeclarations.Add(
                                        New BoundAnonymousTypePropertyAccess(
                                            namedFieldInitializer.Name,
                                            Me, index, fieldType))
                    End If
 
                    ' TODO: when Dev10 reports ERR_BadOrCircularInitializerReference (BC36555) ??
                Next
 
                ' just return a new bound anonymous type creation node
                Dim result As BoundExpression = Me.CreateAnonymousObjectCreationExpression(owningSyntax,
                                                                New AnonymousTypeDescriptor(
                                                                    Me._fields.AsImmutableOrNull(),
                                                                    typeLocationToken.GetLocation(),
                                                                    False),
                                                                boundInitializers.AsImmutableOrNull())
 
                Me._freeze = True
 
                Return result
            End Function
 
            Protected Overrides Function CreateAnonymousObjectCreationExpression(node As VisualBasicSyntaxNode,
                                                                                 anonymousType As AnonymousTypeManager.AnonymousTypePublicSymbol,
                                                                                 initExpressions As ImmutableArray(Of BoundExpression),
                                                                                 Optional hasErrors As Boolean = False) As BoundAnonymousTypeCreationExpression
                ' cache anonymous type property symbols created
                For index = 0 To Me._fields.Length - 1
 
                    Dim name As String = Me._fields(index).Name
 
                    ' NOTE: we use the following criteria as an indicator of the fact that 
                    '       the name of the field is not correct, so we don't want to return 
                    '       symbols of such fields to semantic API
                    If name(0) <> "$"c Then
                        Me._propertySymbols(index) = anonymousType.Properties(index)
                    End If
                Next
 
                ' create a node
                Return New BoundAnonymousTypeCreationExpression(node, Me,
                                                                If(Me._fieldDeclarations Is Nothing,
                                                                   ImmutableArray(Of BoundAnonymousTypePropertyAccess).Empty,
                                                                   Me._fieldDeclarations.ToImmutableAndFree()),
                                                                initExpressions, anonymousType, hasErrors)
            End Function
 
#End Region
 
#Region "Accessors for anonymous type creation bound nodes "
 
            Friend Function GetAnonymousTypePropertySymbol(index As Integer) As PropertySymbol
                Return Me._propertySymbols(index)
            End Function
 
            Friend Function GetAnonymousTypePropertyLocal(index As Integer) As LocalSymbol
                Return Me._locals(index)
            End Function
 
            Friend Function TryGetField(name As String, <Out()> ByRef field As AnonymousTypeField, <Out()> ByRef fieldIndex As Integer) As Boolean
                If Me._fieldName2index.TryGetValue(name, fieldIndex) Then
                    field = Me._fields(fieldIndex)
                    Return True
                End If
 
                field = Nothing
                Return False
            End Function
 
            Friend Sub RegisterFieldReference(fieldIndex As Integer)
                Debug.Assert(Me._fields(fieldIndex).Type IsNot Nothing)
 
                If Not _freeze Then
                    ' check if there is already a local symbol created for this field
 
                    Dim local = Me._locals(fieldIndex)
                    If local Is Nothing Then
                        ' create a local
                        local = New SynthesizedLocal(Me.ContainingMember, Me._fields(fieldIndex).Type, SynthesizedLocalKind.LoweringTemp)
                        Me._locals(fieldIndex) = local
                    End If
                End If
            End Sub
#End Region
 
        End Class
 
        ''' <summary>
        ''' Having this binder, which is created for each field initializer within AnonymousObjectCreationExpressionSyntax
        ''' gives us the following advantages:
        '''   - We no longer rely on transient state of AnonymousTypeField objects to detect out of order field references
        '''     within initializers. This way we can be sure that result of binding performed by SemanticModel is consistent
        '''     with result of initial binding of the entire node.
        '''   - AnonymousTypeCreationBinder overrides CreateAnonymousObjectCreationExpression in such a way that it mutates
        '''     its state. That overridden method shouldn't be called while we are binding each initializer (by queries, for example), 
        '''     it should be called only by AnonymousTypeCreationBinder itself after all initializers are bound and we are producing 
        '''     the resulting node. So having an extra binder in between takes care of that.
        ''' </summary>
        Friend Class AnonymousTypeFieldInitializerBinder
            Inherits Binder
 
            Private ReadOnly _initializerOrdinal As Integer
 
            Public Sub New(creationBinder As AnonymousTypeCreationBinder, initializerOrdinal As Integer)
                MyBase.New(creationBinder)
 
                _initializerOrdinal = initializerOrdinal
            End Sub
 
#Region "Binding of member access with omitted left like '.fieldName'"
 
            Protected Friend Overrides Function TryBindOmittedLeftForMemberAccess(node As MemberAccessExpressionSyntax,
                                                                                  diagnostics As BindingDiagnosticBag,
                                                                                  accessingBinder As Binder,
                                                                                  <Out> ByRef wholeMemberAccessExpressionBound As Boolean) As BoundExpression
                wholeMemberAccessExpressionBound = True
 
                Dim creationBinder = DirectCast(ContainingBinder, AnonymousTypeCreationBinder)
 
                ' filter out parser errors
                If node.ContainsDiagnostics Then
                    Return BadExpression(node, ErrorTypeSymbol.UnknownResultType)
                End If
 
                Dim nameSyntax As SimpleNameSyntax = node.Name
                Dim name As String = nameSyntax.Identifier.ValueText
 
                ' 'nameSyntax' points to '.<nameSyntax>' which is supposed to be either 
                ' a property name or a name inherited from System.Object
                Dim fieldIndex As Integer = 0
                Dim field As AnonymousTypeField = Nothing
                If creationBinder.TryGetField(name, field, fieldIndex) Then
                    ' Field is found
 
                    Dim hasErrors As Boolean = False
                    ' check for type arguments
                    If nameSyntax.Kind = SyntaxKind.GenericName Then
                        ' referencing a field, but with type arguments specified
                        ' NOTE: since we don't have the symbol of the anonymous type's 
                        '       property, we mock property name to be used in this message
                        ' TODO: revise
                        ReportDiagnostic(diagnostics, DirectCast(nameSyntax, GenericNameSyntax).TypeArgumentList,
                                         ERRID.ERR_TypeOrMemberNotGeneric1,
                                         String.Format(
                                             "Public {0}Property {1} As T{2}",
                                             If(field.IsKey, "Readonly ", ""),
                                             name, fieldIndex))
                        hasErrors = True
                    End If
 
                    ' check if the field referenced is already processed, and is 'good', e.g. has type assigned
                    If fieldIndex >= _initializerOrdinal Then
 
                        ' referencing a field which is not processed yet or has an error
                        ' report an error and return a bad expression
                        If Not hasErrors Then
                            ' don't report this error if other diagnostics are already reported
                            ReportDiagnostic(diagnostics, node, ERRID.ERR_AnonymousTypePropertyOutOfOrder1, name)
                        End If
                        Return BadExpression(node, ErrorTypeSymbol.UnknownResultType)
 
                    Else
                        creationBinder.RegisterFieldReference(fieldIndex)
 
                        If Me.ContainingMember IsNot accessingBinder.ContainingMember Then
                            ReportDiagnostic(diagnostics, node, ERRID.ERR_CannotLiftAnonymousType1, node.Name.Identifier.ValueText)
                            hasErrors = True
                        End If
 
                        ' return bound anonymous type access
                        Debug.Assert(field.Type IsNot Nothing)
                        Return New BoundAnonymousTypePropertyAccess(node, creationBinder, fieldIndex, field.Type, hasErrors)
                    End If
                Else
                    ' NOTE: Dev10 allows references to methods defined of anonymous type, which boils 
                    '       down to those defined on System.Object AND extension methods defined 
                    '       for System.Object:
                    '
                    '       - In case an instance method of System.Object is being called, the result of 
                    '         Dev10 compilation with throw an exception in runtime
                    '       - In case a shared method of System.Object is being called, like 
                    '         New With {.a = .ReferenceEquals(Nothing, Nothing)}, the call finishes fine
                    '       - The result of calling extension methods depends on method's implementation 
                    '         (Nothing is being passed as the first argument)
                    '
                    '       In Roslyn we disable this functionality which is a breaking change in a sense,
                    '       but really should only affect a very few customers.
 
                    ' TODO: revise and maybe report a special error message
                End If
 
                ' NOTE: since we don't have the symbol of the anonymous type, we use 
                '       "<anonymous type>" literal to be consistent with Dev10
                ReportDiagnostic(diagnostics, node, ERRID.ERR_NameNotMemberOfAnonymousType2, name, StringConstants.AnonymousTypeName)
                Return BadExpression(node, ErrorTypeSymbol.UnknownResultType)
            End Function
 
            Protected Overrides Function TryBindOmittedLeftForDictionaryAccess(node As MemberAccessExpressionSyntax, accessingBinder As Binder, diagnostics As BindingDiagnosticBag) As BoundExpression
                ' NOTE: since we don't have the symbol of the anonymous type, we use 
                '       "<anonymous type>" literal to be consistent with Dev10
                ReportDiagnostic(diagnostics, node, ERRID.ERR_NoDefaultNotExtend1, StringConstants.AnonymousTypeName)
                Return BadExpression(node, ErrorTypeSymbol.UnknownResultType)
            End Function
 
            Protected Overrides Function TryBindOmittedLeftForConditionalAccess(node As ConditionalAccessExpressionSyntax, accessingBinder As Binder, diagnostics As BindingDiagnosticBag) As BoundExpression
                Return Nothing
            End Function
#End Region
 
        End Class
    End Class
 
End Namespace