Do not override 'Equals(object)' in records. #48767
Do not override 'Equals(object)' in records. #48767CyrusNajmabadi merged 6 commits intodotnet:masterfrom
Conversation
|
I couldn't figure out the best area label to add to this PR. If you have write-permissions please help me learn by adding exactly one area label. |
| var overrideMembersSet = new HashSet<ISymbol>(); | ||
| for (var symbol = selectedMember; symbol != null; symbol = symbol.OverriddenMember()) | ||
| { | ||
| for (var symbol = selectedMember; symbol != null; symbol = symbol.GetOverriddenMember()) |
There was a problem hiding this comment.
we had two methods that did the same thing. i removed one and moved everyone on to the other.
| // for. This is true for all implicit overrides *except* for the one for `bool object.Equals(object)`. | ||
| // This override is not one the user is allowed to provide their own override for as it must have a very | ||
| // particular implementation to ensure proper record equality semantics. | ||
| if (!member.IsImplicitlyDeclared || IsEqualsObjectOverride(member)) |
There was a problem hiding this comment.
this is the fix. we specially case things that aren't truly user overridable due to language rules.
There was a problem hiding this comment.
The comment here specifies that this is only for records, but I don't see any check for records in the code.
There was a problem hiding this comment.
I'm a little confused, is overriding Equals(object) invalid for classes too? (That's my overall impression from the PR, despite the title)
There was a problem hiding this comment.
so, the 'if' checks two things:
First, is the member is implicitly declared. If not, then we consider it. For normal classes, the member is not implicitly declared, because you have to actually write out your override.
However, with records the overrides are implict and are automatically provided. The user can override all the implicit ones except the core Equals(object) one.
So that's how this check works :)
There was a problem hiding this comment.
I don't think its a big deal, but the comment could make it a bit more obvious that this is for records, where overriding Equals(object) is a compile error. I'm guessing you know better than me how likely it is that there will be a new language construct added where there is an implicitly declared Equals method that the user is allowed to override :)
There was a problem hiding this comment.
that's fair :)
| public async Task ObjectEqualsInClass() | ||
| { | ||
| await VerifyItemIsAbsentAsync(@" | ||
| class Program |
There was a problem hiding this comment.
If overriding bool Equals(object obj) is invalid in the class case, should we indicate this to the user like we do with records (i.e. via red squiggle)?
There was a problem hiding this comment.
Equals(object) is fine in the normal case. It's only invalid in the record case. But we detect htat just by looking for an implicit overridde of that member as only records do that.
| // for. This is true for all implicit overrides *except* for the one for `bool object.Equals(object)`. | ||
| // This override is not one the user is allowed to provide their own override for as it must have a very | ||
| // particular implementation to ensure proper record equality semantics. | ||
| if (!member.IsImplicitlyDeclared || IsEqualsObjectOverride(member)) |
There was a problem hiding this comment.
I'm a little confused, is overriding Equals(object) invalid for classes too? (That's my overall impression from the PR, despite the title)
| var options = CSharpParseOptions.Default.WithLanguageVersion(LanguageVersion.Latest); | ||
| var libComp = origComp.RemoveAllSyntaxTrees().AddSyntaxTrees(CSharpSyntaxTree.ParseText(before, options: options)); | ||
| var libRef = MetadataReference.CreateFromImage(CompilationExtensions.EmitToArray(libComp)); | ||
| var libRef = MetadataReference.CreateFromImage(Test.Utilities.CompilationExtensions.EmitToArray(libComp)); |
There was a problem hiding this comment.
This is an extension method right? Why not just libComp.EmitToArray()?
src/EditorFeatures/CSharpTest/Completion/CompletionProviders/OverrideCompletionProviderTests.cs
Show resolved
Hide resolved
| overriddenMembers = new HashSet<ISymbol>(); | ||
| } | ||
|
|
||
| overriddenMembers ??= new(); |
There was a problem hiding this comment.
huh... slick, but I can't tell if I like it yet :)
| } | ||
|
|
||
| if (symbol.IsOverride && symbol.OverriddenMember() != null) | ||
| if (symbol.IsOverride && symbol.GetOverriddenMember() != null) |
There was a problem hiding this comment.
In what case is the GetOverriddenMember() null AND IsOverride true?
There was a problem hiding this comment.
not sure. likely overly paranoid code. but i could imagine a world where .IsOverride might be true in strange metadata, but compiler couldn't actually return the member that was overridden.
| // for. This is true for all implicit overrides *except* for the one for `bool object.Equals(object)`. | ||
| // This override is not one the user is allowed to provide their own override for as it must have a very | ||
| // particular implementation to ensure proper record equality semantics. | ||
| if (!member.IsImplicitlyDeclared || IsEqualsObjectOverride(member)) |
There was a problem hiding this comment.
The comment here specifies that this is only for records, but I don't see any check for records in the code.
Fixes #48640
depends on #47960 going in first.