Records: Support EqualityContract in Equals#44882
Records: Support EqualityContract in Equals#44882cston merged 8 commits intodotnet:features/recordsfrom
Conversation
|
Will take a look after lunch. |
| /// A strongly-typed `public bool Equals(T other)` method. | ||
| /// There are two types of strongly-typed Equals methods: | ||
| /// the strongly-typed virtual method where T is the containing type; and | ||
| /// overrides of the strongly-typed virtual methods from base record types. |
There was a problem hiding this comment.
overrides of the strongly-typed virtual methods from base record types [](start = 8, length = 70)
According to the specification at https://github.com/dotnet/csharplang/blob/master/proposals/records.md no such overrides are supposed to be produced.
There was a problem hiding this comment.
The spec needs to be updated to handle calls to base.Equals(Base). For instance, the code below should report False False rather than False True.
using System;
data class A(int X);
data class B(int X, int Y) : A(X);
class Program
{
static void Main()
{
var b1 = new B(1, 2);
var b2 = new B(1, 3);
Console.WriteLine(b1.Equals(b2));
Console.WriteLine(((A)b1).Equals(b2));
}
}| public override bool IsStatic => false; | ||
|
|
||
| public override bool IsVirtual => false; | ||
| public override bool IsVirtual => true; |
There was a problem hiding this comment.
IsVirtual => true [](start = 29, length = 17)
This disagrees with the specification.
RikkiGibson
left a comment
There was a problem hiding this comment.
LGTM modulo remaining PROTOTYPE comments.
|
|
||
| static void getOtherEquals(ArrayBuilder<MethodSymbol> otherEqualsMethods, PropertySymbol equalityContract) | ||
| { | ||
| while ((equalityContract = equalityContract.OverriddenProperty) is object) |
There was a problem hiding this comment.
is there something specific we're trying to do by traversing up the equalityContract.OverriddenProperty chain instead of just up the BaseType chain?
|
|
||
| public override RefKind RefKind => RefKind.None; | ||
|
|
||
| public override TypeWithAnnotations TypeWithAnnotations { get; } |
There was a problem hiding this comment.
consider ordering auto properties before manual properties.
| "System.Boolean B.Equals(System.Object? )", | ||
| "System.Int32 B.GetHashCode()", | ||
| "System.Boolean B.Equals(System.Object? )", | ||
| "System.Boolean B.Equals(B? )", |
There was a problem hiding this comment.
I wonder if the IDE experience is negatively affected by these parameters being unnamed? (no need to address in this PR)
| IL_0012: ldc.i4.0 | ||
| IL_0013: ret | ||
| }"); | ||
| verifier.VerifyIL("B1.Equals(B1)", |
There was a problem hiding this comment.
would it be worthwhile to VerifyIL on B2.Equals(B2) or perhaps on B1.Equals(A)? (It looks like other tests have verified this but only in records with no properties.)
| internal override IEnumerable<SecurityAttribute> GetSecurityInformation() | ||
| => Array.Empty<SecurityAttribute>(); | ||
|
|
||
| internal override bool IsMetadataNewSlot(bool ignoreInterfaceImplementationChanges = false) => false; |
There was a problem hiding this comment.
I think this needs to be true for the first method, and then false for every override
|
|
||
| public override ImmutableArray<SyntaxReference> DeclaringSyntaxReferences => ImmutableArray<SyntaxReference>.Empty; | ||
|
|
||
| public override Accessibility DeclaredAccessibility => Accessibility.Public; |
| { | ||
| internal sealed class SynthesizedRecordEqualityContractProperty : PropertySymbol | ||
| { | ||
| internal const string PropertyName = "EqualityContract"; |
There was a problem hiding this comment.
May want to put this in WellKnownMemberNames
|
I'm not sure there's a test for overriding EqualityContract across emit reference. Can you validate there is one or add one? |
| } | ||
|
|
||
| static bool hidesInheritedMember(Symbol symbol, NamedTypeSymbol type) | ||
| static Symbol? getInheritedMember(Symbol symbol, NamedTypeSymbol type) |
There was a problem hiding this comment.
getInheritedMember [](start = 27, length = 18)
What's the benefit of returning a Symbol rather than a bool? It looks like we're only using the value to test for null. #Pending
There was a problem hiding this comment.
Hmm, I was using the Symbol at one point, but not now. Reverted. #Pending
| addCloneMethod(); | ||
|
|
||
| var thisEquals = addThisEquals(); | ||
| var equalityContract = addEqualityContract(); |
There was a problem hiding this comment.
var [](start = 12, length = 3)
nit: having type (PropertySymbol) would be helpful for readability here
| var otherEqualsMethods = ArrayBuilder<MethodSymbol>.GetInstance(); | ||
| getOtherEquals(otherEqualsMethods, equalityContract); | ||
|
|
||
| var thisEquals = addThisEquals(equalityContract, otherEqualsMethods.Count == 0 ? null : otherEqualsMethods[0]); |
There was a problem hiding this comment.
, [](start = 59, length = 1)
nit: consider naming the second argument
| { | ||
| var property = new SynthesizedRecordEqualityContractProperty(this, isOverride: getInheritedEqualityContract(this) is object); | ||
| // https://github.com/dotnet/roslyn/issues/44903: Check explicit member has expected signature. | ||
| if (!memberSignatures.ContainsKey(property)) |
There was a problem hiding this comment.
not blocking this PR: should we check both whether the property and the get accessor are already in the list of members? Could we have the get accessor defined without property?
|
Oops, I didn't realize @agocke was already mostly done with review. I'll let him finish. |
| int memberOffset) | ||
| { | ||
| var compilation = containingType.DeclaringCompilation; | ||
| bool isStruct = parameterType.IsStructType(); |
There was a problem hiding this comment.
Given that records are emitted as class when would this be true?
There was a problem hiding this comment.
It cannot be true currently. I left the code here since the code existed before this PR but I can remove it.
There was a problem hiding this comment.
Looks like we're green now with two sign offs. I'd say go for the merge now and remove this later. Easy to squeeze in as a bug fix later.
| { | ||
| var method = new SynthesizedRecordEquals( | ||
| this, | ||
| parameterType: otherEqualsMethod.Parameters[0].Type, |
There was a problem hiding this comment.
Consider the following case
record A<T> { }
record B : A<int> { }
Will otherEqualsMethod.Parameters[0].Type be the closed or open generic here?
There was a problem hiding this comment.
Great test case, thanks. It looks like it's handled correctly but I'll add a test.
Added in #44912.
|
It looks like EqualityContract was made |
See spec.