-
Notifications
You must be signed in to change notification settings - Fork 4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Records: Support EqualityContract in Equals #44882
Changes from 3 commits
0068758
7974b1b
64de377
42f1b09
fa59d0a
3bec675
be62ec1
233988f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2991,10 +2991,16 @@ private void AddSynthesizedRecordMembersIfNecessary(MembersAndInitializersBuilde | |
addCopyCtor(); | ||
addCloneMethod(); | ||
|
||
var thisEquals = addThisEquals(); | ||
var equalityContract = addEqualityContract(); | ||
var otherEqualsMethods = ArrayBuilder<MethodSymbol>.GetInstance(); // PROTOTYPE: We don't need to hold onto the other Equals methods. The values aren't used. | ||
getOtherEquals(otherEqualsMethods, equalityContract); | ||
|
||
var thisEquals = addThisEquals(equalityContract, otherEqualsMethods.Count == 0 ? null : otherEqualsMethods[0]); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
nit: consider naming the second argument |
||
addOtherEquals(otherEqualsMethods, equalityContract, thisEquals); | ||
addObjectEquals(thisEquals); | ||
addHashCode(); | ||
|
||
otherEqualsMethods.Free(); | ||
memberSignatures.Free(); | ||
|
||
return; | ||
|
@@ -3017,7 +3023,7 @@ SynthesizedRecordConstructor addCtor(RecordDeclarationSyntax declWithParameters) | |
|
||
void addCopyCtor() | ||
{ | ||
var ctor = new SynthesizedRecordCopyCtor(this, diagnostics); | ||
var ctor = new SynthesizedRecordCopyCtor(this, memberOffset: members.Count); | ||
if (!memberSignatures.ContainsKey(ctor)) | ||
{ | ||
members.Add(ctor); | ||
|
@@ -3040,7 +3046,7 @@ void addProperties(ImmutableArray<ParameterSymbol> recordParameters) | |
{ | ||
var property = new SynthesizedRecordPropertySymbol(this, param, diagnostics); | ||
if (!memberSignatures.ContainsKey(property) && | ||
!hidesInheritedMember(property, this)) | ||
getInheritedMember(property, this) is null) | ||
{ | ||
members.Add(property); | ||
members.Add(property.GetMethod); | ||
|
@@ -3062,7 +3068,7 @@ void addProperties(ImmutableArray<ParameterSymbol> recordParameters) | |
#endif | ||
} | ||
|
||
static bool hidesInheritedMember(Symbol symbol, NamedTypeSymbol type) | ||
static Symbol? getInheritedMember(Symbol symbol, NamedTypeSymbol type) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
What's the benefit of returning a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm, I was using the |
||
{ | ||
while ((type = type.BaseTypeNoUseSiteDiagnostics) is object) | ||
{ | ||
|
@@ -3076,20 +3082,21 @@ static bool hidesInheritedMember(Symbol symbol, NamedTypeSymbol type) | |
out var hiddenBuilder); | ||
if (hiddenBuilder is object) | ||
{ | ||
var result = hiddenBuilder[0]; | ||
hiddenBuilder.Free(); | ||
return true; | ||
return result; | ||
} | ||
if (bestMatch is object) | ||
{ | ||
return true; | ||
return bestMatch; | ||
} | ||
} | ||
return false; | ||
return null; | ||
} | ||
|
||
void addObjectEquals(MethodSymbol thisEquals) | ||
{ | ||
var objEquals = new SynthesizedRecordObjEquals(this, thisEquals); | ||
var objEquals = new SynthesizedRecordObjEquals(this, thisEquals, memberOffset: members.Count); | ||
if (!memberSignatures.ContainsKey(objEquals)) | ||
{ | ||
// https://github.com/dotnet/roslyn/issues/44617: Don't add if the overridden method is sealed | ||
|
@@ -3099,24 +3106,81 @@ void addObjectEquals(MethodSymbol thisEquals) | |
|
||
void addHashCode() | ||
{ | ||
var hashCode = new SynthesizedRecordGetHashCode(this); | ||
var hashCode = new SynthesizedRecordGetHashCode(this, memberOffset: members.Count); | ||
if (!memberSignatures.ContainsKey(hashCode)) | ||
{ | ||
// https://github.com/dotnet/roslyn/issues/44617: Don't add if the overridden method is sealed | ||
members.Add(hashCode); | ||
} | ||
} | ||
|
||
MethodSymbol addThisEquals() | ||
PropertySymbol addEqualityContract() | ||
{ | ||
var property = new SynthesizedRecordEqualityContractProperty(this, isOverride: false); | ||
// PROTOTYPE: Test with explicit EqualityContract property from source. | ||
// PROTOTYPE: Handle inherited member of unexpected member kind or unexpected | ||
// property signature (distinct type, not virtual, sealed, etc.) | ||
if (getInheritedMember(property, this) is PropertySymbol inheritedProperty) | ||
{ | ||
// PROTOTYPE: Should not have to re-create property! | ||
property = new SynthesizedRecordEqualityContractProperty(this, isOverride: true); | ||
} | ||
members.Add(property); | ||
members.Add(property.GetMethod); | ||
return property; | ||
} | ||
|
||
MethodSymbol addThisEquals(PropertySymbol equalityContract, MethodSymbol? otherEqualsMethod) | ||
{ | ||
var thisEquals = new SynthesizedRecordEquals(this); | ||
var thisEquals = new SynthesizedRecordEquals(this, parameterType: this, isOverride: false, equalityContract, otherEqualsMethod, memberOffset: members.Count); | ||
if (!memberSignatures.TryGetValue(thisEquals, out var existing)) | ||
{ | ||
members.Add(thisEquals); | ||
return thisEquals; | ||
} | ||
return (MethodSymbol)existing; | ||
} | ||
|
||
static void getOtherEquals(ArrayBuilder<MethodSymbol> otherEqualsMethods, PropertySymbol equalityContract) | ||
{ | ||
while ((equalityContract = equalityContract.OverriddenProperty) is object) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is there something specific we're trying to do by traversing up the equalityContract.OverriddenProperty chain instead of just up the BaseType chain? |
||
{ | ||
var containingType = equalityContract.ContainingType; | ||
var member = containingType.GetMembers("Equals").FirstOrDefault(m => | ||
{ | ||
if (m is MethodSymbol method) | ||
{ | ||
var parameters = method.Parameters; | ||
if (parameters.Length == 1 && parameters[0].Type.Equals(containingType, TypeCompareKind.AllIgnoreOptions)) | ||
{ | ||
return true; | ||
} | ||
} | ||
return false; | ||
}); | ||
// PROTOTYPE: Test with missing or unexpected Equals(Base) methods on base type. | ||
if (member is MethodSymbol method) | ||
{ | ||
otherEqualsMethods.Add(method); | ||
} | ||
} | ||
} | ||
|
||
void addOtherEquals(ArrayBuilder<MethodSymbol> otherEqualsMethods, PropertySymbol equalityContract, MethodSymbol thisEquals) | ||
{ | ||
foreach (var otherEqualsMethod in otherEqualsMethods) | ||
{ | ||
var method = new SynthesizedRecordEquals( | ||
this, | ||
parameterType: otherEqualsMethod.Parameters[0].Type, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consider the following case
Will There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Great test case, thanks. It looks like it's handled correctly but I'll add a test. Added in #44912. |
||
isOverride: true, | ||
equalityContract, | ||
otherEqualsMethod: thisEquals, | ||
memberOffset: members.Count); | ||
// PROTOTYPE: Test with explicit strongly-typed Equals(Base) methods on derived record type. | ||
members.Add(method); | ||
} | ||
} | ||
} | ||
|
||
private void AddSynthesizedConstructorsIfNecessary(ArrayBuilder<Symbol> members, ArrayBuilder<ArrayBuilder<FieldOrPropertyInitializer.Builder>> staticInitializers, DiagnosticBag diagnostics) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: having type (
PropertySymbol
) would be helpful for readability here