Skip to content

Do not override 'Equals(object)' in records. #48767

Merged
CyrusNajmabadi merged 6 commits intodotnet:masterfrom
CyrusNajmabadi:recordOverrides
Oct 25, 2020
Merged

Do not override 'Equals(object)' in records. #48767
CyrusNajmabadi merged 6 commits intodotnet:masterfrom
CyrusNajmabadi:recordOverrides

Conversation

@CyrusNajmabadi
Copy link
Contributor

Fixes #48640

depends on #47960 going in first.

@CyrusNajmabadi CyrusNajmabadi requested review from a team as code owners October 19, 2020 18:33
@CyrusNajmabadi CyrusNajmabadi requested a review from a team October 19, 2020 18:33
@Dotnet-GitSync-Bot
Copy link
Collaborator

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())
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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))
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is the fix. we specially case things that aren't truly user overridable due to language rules.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment here specifies that this is only for records, but I don't see any check for records in the code.

Copy link
Contributor

@allisonchou allisonchou Oct 23, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a little confused, is overriding Equals(object) invalid for classes too? (That's my overall impression from the PR, despite the title)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 :)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that's fair :)

public async Task ObjectEqualsInClass()
{
await VerifyItemIsAbsentAsync(@"
class Program
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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))
Copy link
Contributor

@allisonchou allisonchou Oct 23, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an extension method right? Why not just libComp.EmitToArray()?

overriddenMembers = new HashSet<ISymbol>();
}

overriddenMembers ??= new();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

huh... slick, but I can't tell if I like it yet :)

}

if (symbol.IsOverride && symbol.OverriddenMember() != null)
if (symbol.IsOverride && symbol.GetOverriddenMember() != null)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In what case is the GetOverriddenMember() null AND IsOverride true?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment here specifies that this is only for records, but I don't see any check for records in the code.

@CyrusNajmabadi CyrusNajmabadi merged commit b544ef0 into dotnet:master Oct 25, 2020
@ghost ghost added this to the Next milestone Oct 25, 2020
@CyrusNajmabadi CyrusNajmabadi deleted the recordOverrides branch October 25, 2020 21:44
@allisonchou allisonchou modified the milestones: Next, 16.9.P2 Nov 24, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Override completion should not suggest overriding Equals in records

6 participants