|
' 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 Microsoft.CodeAnalysis.Collections
Imports Microsoft.CodeAnalysis.PooledObjects
Imports Microsoft.CodeAnalysis.Text
Imports Microsoft.CodeAnalysis.VisualBasic.Symbols
Imports Microsoft.CodeAnalysis.VisualBasic.Syntax
Namespace Microsoft.CodeAnalysis.VisualBasic.Symbols
Partial Friend NotInheritable Class AnonymousTypeManager
Partial Private Class AnonymousTypeConstructorSymbol
Inherits SynthesizedConstructorBase
Friend Overrides Function GetBoundMethodBody(compilationState As TypeCompilationState, diagnostics As BindingDiagnosticBag, Optional ByRef methodBodyBinder As Binder = Nothing) As BoundBlock
methodBodyBinder = Nothing
Dim syntax As SyntaxNode = Me.Syntax
' List of statements
Dim statements = ArrayBuilder(Of BoundStatement).GetInstance()
Dim anonymousType = DirectCast(Me.ContainingType, AnonymousTypeTemplateSymbol)
Debug.Assert(anonymousType.Properties.Length = Me.ParameterCount)
' 'Me' reference
Dim boundMeReference = New BoundMeReference(syntax, anonymousType).MakeCompilerGenerated()
For index = 0 To Me.ParameterCount - 1
Dim [property] As AnonymousTypePropertySymbol = anonymousType.Properties(index)
Dim propertyType As TypeSymbol = [property].Type
' Generate 'field' = 'parameter' statement
Dim fieldAccess = New BoundFieldAccess(syntax, boundMeReference, [property].AssociatedField, True, propertyType).MakeCompilerGenerated()
Dim parameter = New BoundParameter(syntax, Me._parameters(index), isLValue:=False, type:=propertyType).MakeCompilerGenerated()
Dim assignment = New BoundAssignmentOperator(syntax, fieldAccess, parameter, False, propertyType).MakeCompilerGenerated()
statements.Add(New BoundExpressionStatement(syntax, assignment).MakeCompilerGenerated())
Next
' Final return statement
statements.Add(New BoundReturnStatement(syntax, Nothing, Nothing, Nothing).MakeCompilerGenerated())
' Create a bound block
Return New BoundBlock(syntax, Nothing, ImmutableArray(Of LocalSymbol).Empty, statements.ToImmutableAndFree()).MakeCompilerGenerated()
End Function
End Class
Partial Private NotInheritable Class AnonymousTypeEqualsMethodSymbol
Inherits SynthesizedRegularMethodBase
Friend Overrides Function GetBoundMethodBody(compilationState As TypeCompilationState, diagnostics As BindingDiagnosticBag, Optional ByRef methodBodyBinder As Binder = Nothing) As BoundBlock
methodBodyBinder = Nothing
Dim syntax As SyntaxNode = Me.Syntax
' 'Me' reference
Dim boundMeReference As BoundMeReference = New BoundMeReference(syntax, AnonymousType).MakeCompilerGenerated()
' Argument 'obj' reference
Dim boundObjReference As BoundParameter = New BoundParameter(syntax, Me._parameters(0), isLValue:=False,
type:=AnonymousType.Manager.System_Object).MakeCompilerGenerated()
' TryCast(obj, <anonymous-type>)
Dim boundTryCast As BoundExpression = New BoundTryCast(syntax, boundObjReference, ConversionKind.NarrowingReference,
AnonymousType, Nothing).MakeCompilerGenerated()
' Call Me.Equals(TryCast(obj, <anonymous-type>))
Dim boundCallToEquals As BoundExpression = New BoundCall(syntax, Me._iEquatableEqualsMethod, Nothing,
boundMeReference, ImmutableArray.Create(Of BoundExpression)(boundTryCast),
Nothing, AnonymousType.Manager.System_Boolean).MakeCompilerGenerated()
' Create a bound block
Return New BoundBlock(syntax, Nothing, ImmutableArray(Of LocalSymbol).Empty,
ImmutableArray.Create(Of BoundStatement)(
New BoundReturnStatement(syntax, boundCallToEquals, Nothing, Nothing).MakeCompilerGenerated()))
End Function
End Class
Partial Private NotInheritable Class AnonymousTypeGetHashCodeMethodSymbol
Inherits SynthesizedRegularMethodBase
Friend Overrides Function GetBoundMethodBody(compilationState As TypeCompilationState, diagnostics As BindingDiagnosticBag, Optional ByRef methodBodyBinder As Binder = Nothing) As BoundBlock
methodBodyBinder = Nothing
Dim syntax As SyntaxNode = Me.Syntax
Dim objectType As TypeSymbol = Me.AnonymousType.Manager.System_Object
Dim getHashCodeMethod As MethodSymbol = Me.AnonymousType.Manager.System_Object__GetHashCode
Dim integerType As TypeSymbol = Me.AnonymousType.Manager.System_Int32
Dim booleanType As TypeSymbol = Me.AnonymousType.Manager.System_Boolean
' Generate Hash base
#If False Then
' NOTE: Following Dev10 behavior we ensure all anonymous types with the same field
' names (case-insensitive) have the same hash base. Algorithm differs though...
Dim builder = PooledStringBuilder.GetInstance()
For Each [property] In Me.AnonymousType.Properties
If [property].IsReadOnly Then
builder.Builder.Append("|"c)
builder.Builder.Append([property].Name)
End If
Next
Dim hashBase As Integer = builder.ToStringAndFree().ToLower().GetHashCode()
#Else
Dim properties = Me.AnonymousType.Properties
Dim names(properties.Length - 1) As String
For i = 0 To properties.Length - 1
names(i) = properties(i).Name
Next
Dim hashBase As Integer = CInt(CRC32.ComputeCRC32(names))
#End If
' 'Me' reference
Dim boundMeReference = New BoundMeReference(syntax, AnonymousType).MakeCompilerGenerated()
' This variable accumulates the expression as we build it, final expression should be a bound
' expression representing the following expression (only readonly=key fields are processed):
' '((...(( <hashBase> )*&HA5555529 + IF(field0 Is Nothing, 0, field0.GetHashCode())
' )*&HA5555529 + IF(field1 Is Nothing, 0, field1.GetHashCode())
' ...
' )*&HA5555529 + IF(lastField Is Nothing, 0, lastField.GetHashCode()))'
Dim expression As BoundExpression = Nothing
' <expression> ::= <hashBase>
expression = New BoundLiteral(syntax, ConstantValue.Create(hashBase), integerType).MakeCompilerGenerated()
Dim factorLiteral = New BoundLiteral(syntax, ConstantValue.Create(&HA5555529), integerType).MakeCompilerGenerated()
Dim zeroLiteral = New BoundLiteral(syntax, ConstantValue.Create(0), integerType).MakeCompilerGenerated()
Dim nothingLiteral = New BoundLiteral(syntax, ConstantValue.Nothing, objectType).MakeCompilerGenerated()
For Each [property] In Me.AnonymousType.Properties
If [property].IsReadOnly Then
' <expression> ::= <expression>*<factor>
expression = New BoundBinaryOperator(syntax, BinaryOperatorKind.Multiply,
expression, factorLiteral, False, integerType).MakeCompilerGenerated()
' boundCondition ::= 'DirectCast(field, System.Object) is nothing'
Dim boundCondition = New BoundBinaryOperator(syntax, BinaryOperatorKind.Is,
New BoundDirectCast(syntax,
New BoundFieldAccess(
syntax, boundMeReference,
[property].AssociatedField, False,
[property].Type).MakeCompilerGenerated(),
ConversionKind.WideningTypeParameter,
objectType, Nothing).MakeCompilerGenerated(),
nothingLiteral, False, booleanType).MakeCompilerGenerated()
' boundGetHashCode ::= 'field.GetHashCode()'
Dim boundGetHashCode = New BoundCall(syntax, getHashCodeMethod, Nothing,
New BoundFieldAccess(
syntax, boundMeReference,
[property].AssociatedField, False,
[property].Type).MakeCompilerGenerated(),
ImmutableArray(Of BoundExpression).Empty,
Nothing, integerType).MakeCompilerGenerated()
' boundTernaryConditional = IF(<boundCondition>, 0, <boundGetHashCode>)
Dim boundTernaryConditional = New BoundTernaryConditionalExpression(syntax,
boundCondition, zeroLiteral, boundGetHashCode,
Nothing, integerType).MakeCompilerGenerated()
' <expression> ::= <expression> + <boundTernaryConditional>
expression = New BoundBinaryOperator(syntax, BinaryOperatorKind.Add, expression,
boundTernaryConditional, False, integerType).MakeCompilerGenerated()
End If
Next
' Create a bound block
Return New BoundBlock(syntax, Nothing,
ImmutableArray(Of LocalSymbol).Empty,
ImmutableArray.Create(Of BoundStatement)(
New BoundReturnStatement(syntax, expression, Nothing, Nothing).MakeCompilerGenerated()))
End Function
End Class
Partial Private NotInheritable Class AnonymousType_IEquatable_EqualsMethodSymbol
Inherits SynthesizedRegularMethodBase
Friend Overrides Function GetBoundMethodBody(compilationState As TypeCompilationState, diagnostics As BindingDiagnosticBag, Optional ByRef methodBodyBinder As Binder = Nothing) As BoundBlock
methodBodyBinder = Nothing
Dim syntax As SyntaxNode = Me.Syntax
Dim objectType As TypeSymbol = Me.AnonymousType.Manager.System_Object
Dim booleanType As TypeSymbol = Me.AnonymousType.Manager.System_Boolean
' Locals
Dim localMyFieldBoxed As LocalSymbol = New SynthesizedLocal(Me, objectType, SynthesizedLocalKind.LoweringTemp)
Dim localOtherFieldBoxed As LocalSymbol = New SynthesizedLocal(Me, objectType, SynthesizedLocalKind.LoweringTemp)
' 'Me' reference
Dim boundMeReference As BoundMeReference = New BoundMeReference(syntax, AnonymousType)
' Argument 'val' reference
Dim boundValReference As BoundParameter = New BoundParameter(syntax, Me._parameters(0), isLValue:=False, type:=AnonymousType)
' 'Nothing' Literal to be reused
Dim nothingLiteral = New BoundLiteral(syntax, ConstantValue.Nothing, objectType).MakeCompilerGenerated()
' Build combined condition for all fields
Dim combinedFieldCheck = BuildConditionsForFields(boundMeReference, boundValReference, nothingLiteral,
localMyFieldBoxed, localOtherFieldBoxed, booleanType)
' Build 'val IsNot Nothing' condition
Dim valIsNotNothing = BuildIsCheck(New BoundDirectCast(syntax, boundValReference,
ConversionKind.WideningReference, objectType, Nothing).MakeCompilerGenerated(),
nothingLiteral, booleanType, reverse:=True)
' Final equality check: Me Is val OrElse (<valIsNotNothing> AndAlso <combinedFieldCheck>)
Dim finalEqualityCheck = BuildAndAlso(valIsNotNothing, combinedFieldCheck, booleanType)
Dim meIsValCheck = BuildIsCheck(boundMeReference, boundValReference, booleanType)
finalEqualityCheck = BuildOrElse(meIsValCheck, finalEqualityCheck, booleanType)
' Create a bound block
Return New BoundBlock(syntax, Nothing,
ImmutableArray.Create(localMyFieldBoxed, localOtherFieldBoxed),
ImmutableArray.Create(Of BoundStatement)(
New BoundReturnStatement(syntax, finalEqualityCheck, Nothing, Nothing).MakeCompilerGenerated()))
End Function
Private Function BuildConditionsForFields(boundMe As BoundMeReference, boundOther As BoundParameter, boundNothing As BoundExpression,
localMyFieldBoxed As LocalSymbol, localOtherFieldBoxed As LocalSymbol, booleanType As TypeSymbol) As BoundExpression
' <expression> will be build in a form of (only key fields are reported)
' (<condition-for-field-1> AndAlso <condition-for-field-2> OrElse ... AndAlso <condition-for-field-N> )
Dim expression As BoundExpression = Nothing
' Process fields
For Each [property] In AnonymousType.Properties
If [property].IsReadOnly Then
Dim condition As BoundExpression = BuildConditionForField([property], boundMe, boundOther, boundNothing, localMyFieldBoxed, localOtherFieldBoxed, booleanType)
expression = If(expression Is Nothing, condition, BuildAndAlso(expression, condition, booleanType))
End If
Next
Return expression
End Function
''' <summary>
''' Builds a condition in the following form:
'''
''' [preaction: localMyFieldBoxed = DirectCast(Me.field, System.Object)]
''' [preaction: localOtherFieldBoxed = DirectCast(Other.field, System.Object)]
''' IF(localMyFieldBoxed IsNot Nothing AndAlso localOtherFieldBoxed IsNot Nothing,
''' localMyFieldBoxed.Equals(localOtherFieldBoxed),
''' localMyFieldBoxed Is localOtherFieldBoxed
''' )
''' </summary>
Private Function BuildConditionForField([property] As AnonymousTypePropertySymbol, boundMe As BoundMeReference, boundOther As BoundParameter,
boundNothing As BoundExpression, localMyFieldBoxed As LocalSymbol, localOtherFieldBoxed As LocalSymbol,
booleanType As TypeSymbol) As BoundExpression
Dim field As FieldSymbol = [property].AssociatedField
Dim syntax As SyntaxNode = Me.Syntax
Dim boundLocalMyFieldBoxed = New BoundLocal(syntax, localMyFieldBoxed,
False, localMyFieldBoxed.Type).MakeCompilerGenerated()
Dim boundLocalOtherFieldBoxed = New BoundLocal(syntax, localOtherFieldBoxed,
False, localOtherFieldBoxed.Type).MakeCompilerGenerated()
Dim condition As BoundExpression = BuildAndAlso(BuildIsCheck(boundLocalMyFieldBoxed, boundNothing, booleanType, reverse:=True),
BuildIsCheck(boundLocalOtherFieldBoxed, boundNothing, booleanType, reverse:=True),
booleanType)
' TODO: Make sure we don't call GetObjectValue here
Dim boundCallToEquals As BoundExpression = New BoundCall(syntax,
Me.AnonymousType.Manager.System_Object__Equals, Nothing,
boundLocalMyFieldBoxed,
ImmutableArray.Create(Of BoundExpression)(boundLocalOtherFieldBoxed),
Nothing, booleanType).MakeCompilerGenerated()
Dim ternary As BoundExpression = New BoundTernaryConditionalExpression(syntax,
condition, boundCallToEquals,
BuildIsCheck(boundLocalMyFieldBoxed, boundLocalOtherFieldBoxed, booleanType),
Nothing, booleanType).MakeCompilerGenerated()
Dim assignLocalMyField As BoundExpression = New BoundAssignmentOperator(syntax,
New BoundLocal(syntax, localMyFieldBoxed,
True, localMyFieldBoxed.Type),
BuildBoxedFieldAccess(boundMe, field),
True, localMyFieldBoxed.Type).MakeCompilerGenerated()
Dim assignLocalOtherField As BoundExpression = New BoundAssignmentOperator(syntax,
New BoundLocal(syntax, localOtherFieldBoxed,
True, localOtherFieldBoxed.Type),
BuildBoxedFieldAccess(boundOther, field),
True, localOtherFieldBoxed.Type).MakeCompilerGenerated()
Return New BoundSequence(syntax, ImmutableArray(Of LocalSymbol).Empty,
ImmutableArray.Create(Of BoundExpression)(assignLocalMyField, assignLocalOtherField),
ternary, ternary.Type).MakeCompilerGenerated()
End Function
Private Function BuildBoxedFieldAccess(receiver As BoundExpression, field As FieldSymbol) As BoundExpression
Return New BoundDirectCast(Syntax,
New BoundFieldAccess(Syntax, receiver, field, False, field.Type).MakeCompilerGenerated(),
ConversionKind.WideningTypeParameter,
Me.AnonymousType.Manager.System_Object, Nothing).MakeCompilerGenerated()
End Function
Private Function BuildIsCheck(left As BoundExpression, right As BoundExpression, booleanType As TypeSymbol, Optional reverse As Boolean = False) As BoundExpression
Return New BoundBinaryOperator(Syntax,
If(reverse, BinaryOperatorKind.IsNot, BinaryOperatorKind.Is),
left, right, False, booleanType).MakeCompilerGenerated()
End Function
Private Function BuildAndAlso(left As BoundExpression, right As BoundExpression, booleanType As TypeSymbol) As BoundExpression
Return New BoundBinaryOperator(Syntax, BinaryOperatorKind.AndAlso,
left, right, False, booleanType).MakeCompilerGenerated()
End Function
Private Function BuildOrElse(left As BoundExpression, right As BoundExpression, booleanType As TypeSymbol) As BoundExpression
Return New BoundBinaryOperator(Syntax, BinaryOperatorKind.OrElse,
left, right, False, booleanType).MakeCompilerGenerated()
End Function
End Class
Partial Private NotInheritable Class AnonymousTypeToStringMethodSymbol
Inherits SynthesizedRegularMethodBase
Friend Overrides Function GetBoundMethodBody(compilationState As TypeCompilationState, diagnostics As BindingDiagnosticBag, Optional ByRef methodBodyBinder As Binder = Nothing) As BoundBlock
methodBodyBinder = Nothing
Dim syntax As SyntaxNode = Me.Syntax
Dim objectType As TypeSymbol = Me.AnonymousType.Manager.System_Object
Dim stringType As TypeSymbol = Me.ReturnType
Dim arrayOfObjectsType As TypeSymbol = Me.AnonymousType.Manager.Compilation.CreateArrayTypeSymbol(objectType)
Dim numberOfFields As Integer = AnonymousType.Properties.Length
' Process all parameters, build string format pattern, build arguments
Dim boundMeReference = New BoundMeReference(syntax, AnonymousType).MakeCompilerGenerated()
Dim boundFieldAccessArray(numberOfFields - 1) As BoundExpression
Dim formatStringBuilder = PooledStringBuilder.GetInstance()
formatStringBuilder.Builder.Append("{{ ")
For index = 0 To numberOfFields - 1
Dim [property] As AnonymousTypePropertySymbol = AnonymousType.Properties(index)
' format pattern
formatStringBuilder.Builder.AppendFormat(If(index = 0, "{0} = {{{1}}}", ", {0} = {{{1}}}"), [property].MetadataName, index)
' put the field accessor to boundFieldAccessArray
boundFieldAccessArray(index) = New BoundDirectCast(syntax,
New BoundFieldAccess(syntax, boundMeReference, [property].AssociatedField,
False, [property].Type).MakeCompilerGenerated(),
ConversionKind.WideningTypeParameter, objectType, Nothing).MakeCompilerGenerated()
Next
formatStringBuilder.Builder.Append(" }}")
Dim formatString As String = formatStringBuilder.ToStringAndFree()
' array initializer: { field0, field1, ... }
Dim boundArrayInitializer As BoundArrayInitialization = New BoundArrayInitialization(syntax, boundFieldAccessArray.AsImmutableOrNull(),
arrayOfObjectsType).MakeCompilerGenerated()
' New Object(numberOfFields - 1) { field0, field1, ... }
Dim arrayInstantiation As BoundExpression = New BoundArrayCreation(syntax,
ImmutableArray.Create(Of BoundExpression)(
New BoundLiteral(syntax, ConstantValue.Create(numberOfFields),
Me.AnonymousType.Manager.System_Int32).MakeCompilerGenerated()),
boundArrayInitializer, arrayOfObjectsType).MakeCompilerGenerated()
' String.Format(<formatPattern>, New Object(numberOfFields - 1) { field0, field1, ... })
Dim formatMethod = Me.AnonymousType.Manager.System_String__Format_IFormatProvider
Dim [call] As BoundExpression = New BoundCall(syntax, formatMethod, Nothing, Nothing,
ImmutableArray.Create(Of BoundExpression)(
New BoundLiteral(syntax, ConstantValue.Nothing,
formatMethod.Parameters(0).Type).MakeCompilerGenerated(),
New BoundLiteral(syntax, ConstantValue.Create(formatString),
stringType).MakeCompilerGenerated(),
arrayInstantiation),
Nothing, stringType).MakeCompilerGenerated()
' Create a bound block
Return New BoundBlock(syntax, Nothing,
ImmutableArray(Of LocalSymbol).Empty,
ImmutableArray.Create(Of BoundStatement)(
New BoundReturnStatement(syntax, [call], Nothing, Nothing).MakeCompilerGenerated()))
End Function
End Class
End Class
End Namespace
|