Skip to content

Commit

Permalink
Use new record equality specification (#43626)
Browse files Browse the repository at this point in the history
Per design changes, record equality is now defined using all instance
fields, instead of just record fields.
  • Loading branch information
agocke authored May 1, 2020
1 parent f423b85 commit a0534a0
Show file tree
Hide file tree
Showing 9 changed files with 502 additions and 283 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -123,27 +123,16 @@ internal override void GenerateMethodBody(TypeCompilationState compilationState,
F.Convert(manager.System_Object, boundLocal),
F.Null(manager.System_Object));

// prepare symbols
MethodSymbol equalityComparer_Equals = manager.System_Collections_Generic_EqualityComparer_T__Equals;
MethodSymbol equalityComparer_get_Default = manager.System_Collections_Generic_EqualityComparer_T__get_Default;
NamedTypeSymbol equalityComparerType = equalityComparer_Equals.ContainingType;

// Compare fields
for (int index = 0; index < anonymousType.Properties.Length; index++)
if (anonymousType.Properties.Length > 0)
{
// Prepare constructed symbols
TypeParameterSymbol typeParameter = anonymousType.TypeParameters[index];
FieldSymbol fieldSymbol = anonymousType.Properties[index].BackingField;
NamedTypeSymbol constructedEqualityComparer = equalityComparerType.Construct(typeParameter);

// Generate 'retExpression' = 'retExpression && System.Collections.Generic.EqualityComparer<T_index>.
// Default.Equals(this.backingFld_index, local.backingFld_index)'
retExpression = F.LogicalAnd(retExpression,
F.Call(F.StaticCall(constructedEqualityComparer,
equalityComparer_get_Default.AsMember(constructedEqualityComparer)),
equalityComparer_Equals.AsMember(constructedEqualityComparer),
F.Field(F.This(), fieldSymbol),
F.Field(boundLocal, fieldSymbol)));
var fields = ArrayBuilder<FieldSymbol>.GetInstance(anonymousType.Properties.Length);
foreach (var prop in anonymousType.Properties)
{
fields.Add(prop.BackingField);
}
retExpression = MethodBodySynthesizer.GenerateFieldEquals(retExpression, boundLocal, fields, F);
fields.Free();
}

// Final return statement
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,13 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

#nullable enable

using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Linq;
using Microsoft.CodeAnalysis.CSharp.Emit;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.RuntimeMembers;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;

namespace Microsoft.CodeAnalysis.CSharp.Symbols
{
Expand Down Expand Up @@ -253,6 +251,61 @@ internal override void GenerateMethodBody(TypeCompilationState compilationState,
/// </summary>
internal static partial class MethodBodySynthesizer
{
/// <summary>
/// Given a set of fields, produce an expression that is true when all of the given fields on
/// `this` are equal to the fields on <paramref name="otherReceiver" /> according to the
/// default EqualityComparer.
/// </summary>
public static BoundExpression GenerateFieldEquals<TList>(
BoundExpression? initialExpression,
BoundExpression otherReceiver,
TList fields,
SyntheticBoundNodeFactory F) where TList : IReadOnlyList<FieldSymbol>
{
Debug.Assert(fields.Count > 0);

// Expression:
//
// System.Collections.Generic.EqualityComparer<T_1>.Default.Equals(this.backingFld_1, value.backingFld_1)
// ...
// && System.Collections.Generic.EqualityComparer<T_N>.Default.Equals(this.backingFld_N, value.backingFld_N)

// prepare symbols
var equalityComparer_get_Default = F.WellKnownMethod(
WellKnownMember.System_Collections_Generic_EqualityComparer_T__get_Default);
var equalityComparer_Equals = F.WellKnownMethod(
WellKnownMember.System_Collections_Generic_EqualityComparer_T__Equals);

NamedTypeSymbol equalityComparerType = equalityComparer_Equals.ContainingType;

BoundExpression? retExpression = initialExpression;

// Compare fields
foreach (var field in fields)
{
// Prepare constructed comparer
var constructedEqualityComparer = equalityComparerType.Construct(field.Type);

// System.Collections.Generic.EqualityComparer<T_index>.
// Default.Equals(this.backingFld_index, local.backingFld_index)'
BoundExpression nextEquals = F.Call(
F.StaticCall(constructedEqualityComparer,
equalityComparer_get_Default.AsMember(constructedEqualityComparer)),
equalityComparer_Equals.AsMember(constructedEqualityComparer),
F.Field(F.This(), field),
F.Field(otherReceiver, field));

// Generate 'retExpression' = 'retExpression && nextEquals'
retExpression = retExpression is null
? nextEquals
: F.LogicalAnd(retExpression, nextEquals);
}

RoslynDebug.AssertNotNull(retExpression);

return retExpression;
}

/// <summary>
/// Construct a body for a method containing a call to a single other method with the same signature (modulo name).
/// </summary>
Expand All @@ -263,12 +316,11 @@ internal static partial class MethodBodySynthesizer
internal static BoundBlock ConstructSingleInvocationMethodBody(SyntheticBoundNodeFactory F, MethodSymbol methodToInvoke, bool useBaseReference)
{
var argBuilder = ArrayBuilder<BoundExpression>.GetInstance();
//var refKindBuilder = ArrayBuilder<RefKind>.GetInstance();

RoslynDebug.AssertNotNull(F.CurrentFunction);
foreach (var param in F.CurrentFunction.Parameters)
{
argBuilder.Add(F.Parameter(param));
//refKindBuilder.Add(param.RefKind);
}

BoundExpression invocation = F.Call(useBaseReference ? (BoundExpression)F.Base(baseType: methodToInvoke.ContainingType) : F.This(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2931,6 +2931,8 @@ private void AddSynthesizedRecordMembersIfNecessary(MembersAndInitializersBuilde

if (paramList.ParameterCount == 0 || !_declModifiers.HasFlag(DeclarationModifiers.Data))
{
// PROTOTYPE: The semantics of an empty parameter list have not been decided. Error
// for now
diagnostics.Add(ErrorCode.ERR_BadRecordDeclaration, paramList.Location);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@
using System.Collections.Immutable;
using System.Reflection;
using Microsoft.Cci;
using Microsoft.CodeAnalysis.CSharp.Emit;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.PooledObjects;

namespace Microsoft.CodeAnalysis.CSharp.Symbols
Expand Down Expand Up @@ -144,25 +142,23 @@ internal override void GenerateMethodBody(TypeCompilationState compilationState,
retExpr = F.ObjectNotEqual(other, F.Null(F.SpecialType(SpecialType.System_Object)));
}

var recordProperties = ArrayBuilder<FieldSymbol>.GetInstance();
foreach (var member in ContainingType.GetMembers())
var fields = ArrayBuilder<FieldSymbol>.GetInstance();
foreach (var f in ContainingType.GetFieldsToEmit())
{
// PROTOTYPE: Should generate equality on user-written members as well
if (member is SynthesizedRecordPropertySymbol p)
if (!f.IsStatic)
{
recordProperties.Add(p.BackingField);
fields.Add(f);
}
}
if (recordProperties.Count > 0)
if (fields.Count > 0)
{
var comparisons = EqualityMethodBodySynthesizer.GenerateEqualsComparisons(
ContainingType,
retExpr = MethodBodySynthesizer.GenerateFieldEquals(
retExpr,
other,
recordProperties,
fields,
F);
retExpr = retExpr is null ? comparisons : F.LogicalAnd(retExpr, comparisons);
}
recordProperties.Free();
fields.Free();

F.CloseMethod(F.Block(F.Return(retExpr)));
}
Expand Down
Loading

0 comments on commit a0534a0

Please sign in to comment.