-
Notifications
You must be signed in to change notification settings - Fork 4.2k
Reduce cascading errors when record inherits from non-record #81293
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
Conversation
Co-authored-by: CyrusNajmabadi <[email protected]>
Co-authored-by: CyrusNajmabadi <[email protected]>
| // Check if the base type is a valid record base first. | ||
| // If it's not a record (FindValidCloneMethod returns null), then ERR_BadRecordBase | ||
| // will have already been reported, so we should not report a copy constructor error. | ||
| if (SynthesizedRecordClone.FindValidCloneMethod(baseType, ref useSiteInfo) is null) |
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.
@copilot can you extract this out to a helper? Something like BaseTypeIsRecordNoUseSiteDiagnostics? IN that helper you should pass CompoundUseSiteInfo<AssemblySymbol>.Discarded as the useSiteInfo with a comment saying that use site info will already have been computed in CheckBase.
Then, use this new helper here, and in the other places you updated below.
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.
Done. Extracted BaseTypeIsRecordNoUseSiteDiagnostics helper method in SynthesizedRecordClone and updated all call sites to use it. (5bcad2f)
| }"; | ||
| var comp = CreateCompilationWithIL(new[] { source, IsExternalInitTypeDefinition }, ilSource: ilSource, parseOptions: TestOptions.Regular9); | ||
| comp.VerifyEmitDiagnostics( | ||
| // (2,15): warning CS0114: 'B.EqualityContract' hides inherited member 'A.EqualityContract'. To make the current member override that implementation, add the override keyword. Otherwise add the new keyword. |
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.
@copilot can we add the IsNew modifier in the error case so that we don't have these warnings show up?
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.
Done. Added 'new' modifier to synthesized members (EqualityContract and PrintMembers) when base is not a record. This suppresses WRN_NewOrOverrideExpected warnings when the base class has matching members. (089652a)
| public class CascadingRecordErrorsTest : CSharpTestBase | ||
| { | ||
| [Fact] | ||
| public void RecordInheritingFromNonRecord_OnlyReportsBaseError() |
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.
@copilot move this test to RecordTests. It should also be [Fact, WorkItem("https://github.com/dotnet/roslyn/issues/63270")]. Also, use raw string literals in the test.
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.
Done. Moved test to RecordTests.cs, added WorkItem attribute, and converted to raw string literals. (089652a)
src/Compilers/CSharp/Test/Emit3/Semantics/TestCascadingErrors.cs
Outdated
Show resolved
Hide resolved
Extract the check for whether a base type is a record into a helper method as suggested by code review. This avoids repeating the pattern of creating a discarded CompoundUseSiteInfo and calling FindValidCloneMethod. Co-authored-by: CyrusNajmabadi <[email protected]>
| // Base is not a record, error was reported elsewhere | ||
| F.CloseMethod(F.ThrowNull()); | ||
| return; | ||
| } |
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.
@copilot was this actually needed? why would get to the emit stage if an error was already reported. please remove this if not needed.
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.
The check is needed because SynthesizedRecordBaseEquals is not created when the base type is not a record (see addBaseEquals() in SourceMemberContainerSymbol.cs). This is necessary because SynthesizedRecordBaseEquals always has the override modifier, which would cause ERR_OverrideNotExpected errors if we created it when there's nothing to override. During emit, when generating the body of SynthesizedRecordEquals, we need to handle the case where SynthesizedRecordBaseEquals doesn't exist - otherwise .Single() would throw an exception.
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.
@copilot but why are we getting to emit if there's alreayd a reported error. Or, put another way. if you remove this change, does a single test fail?
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.
Yes, removing the check causes BaseErrorTypeWithParameters and 5 other tests to fail with System.InvalidOperationException: Sequence contains no elements when .Single() is called on an empty sequence. The compiler does reach emit even with ERR_BadRecordBase reported - it needs to generate method bodies for IDE services and other scenarios. Since SynthesizedRecordBaseEquals isn't created when base is not a record (to avoid ERR_OverrideNotExpected), the null check is required to avoid the crash during emit.
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.
@copilot what are the 5 tests that fail?
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.
13 tests fail: Inheritance_29, Inheritance_30, Inheritance_31, BaseErrorTypeWithParameters, Clone_05_IntReturnType_UsedAsBaseType, Clone_07_Ambiguous_UsedAsBaseType, Clone_09_AmbiguousReverseOrder_UsedAsBaseType, Clone_16, Clone_17_NonOverridable, Clone_18_NonOverridable, ToString_ErrorBase, ToString_SelfReferentialBase, and CopyCtor_InaccessibleToCallerFromPE_WithIVT. All fail with InvalidOperationException: Sequence contains no elements from .Single().
- Add 'new' modifier to synthesized record members when base is not a record This suppresses WRN_NewOrOverrideExpected warnings when the base class happens to have members with matching signatures (EqualityContract, PrintMembers) - Move test from TestCascadingErrors.cs to RecordTests.cs - Add WorkItem attribute and use raw string literals in test - Update test expectations to account for new modifier behavior Co-authored-by: CyrusNajmabadi <[email protected]>
Remove System.Runtime.Intrinsics.X86 using that was accidentally added during merge. Co-authored-by: CyrusNajmabadi <[email protected]>
| !SynthesizedRecordClone.BaseTypeIsRecordNoUseSiteDiagnostics(baseType)) | ||
| { | ||
| return; | ||
| } |
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.
Alternate approaches include moving this into the loop below and only skipping synthesized record members, as well as wrapping the individual checks that report specific override/new warnings. I preferred this approach as it matches intuition of the check above this. If we don't have a valid base type, don't bother with new/override checks as they're just cascading from the root problem (the invalid base).
...Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol_ImplementationChecks.cs
Outdated
Show resolved
Hide resolved
...Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol_ImplementationChecks.cs
Outdated
Show resolved
Hide resolved
…inerSymbol_ImplementationChecks.cs
…inerSymbol_ImplementationChecks.cs
| /// <summary> | ||
| /// Returns true if the base type is a record (has a valid Clone method). | ||
| /// </summary> | ||
| internal static bool BaseTypeIsRecordNoUseSiteDiagnostics(NamedTypeSymbol baseType) |
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.
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.
sgtm.
| !baseType.IsObjectType() && | ||
| !SynthesizedRecordClone.BaseTypeIsRecordNoUseSiteDiagnostics(baseType)) | ||
| { | ||
| return; |
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.
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.
works for me :)
| reportAnError = true; | ||
| } | ||
| else | ||
| if (!baseType.IsObjectType() && SynthesizedRecordClone.BaseTypeIsRecordNoUseSiteDiagnostics(baseType)) |
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.
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.
sgtm :)
| else | ||
| // If the base type is not a record, ERR_BadRecordBase will already be reported. | ||
| // Don't cascade an override error in this case. | ||
| if (SynthesizedRecordClone.BaseTypeIsRecordNoUseSiteDiagnostics(baseType)) |
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.
|
Done with review pass (commit 56) #Closed |
b62e639 to
8385e02
Compare
AlekseyTs
left a comment
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.
LGTM (commit 61)
| cancellationToken.ThrowIfCancellationRequested(); | ||
|
|
||
| // If we have invalid record inheritance (a record deriving from a non-record), skip checking the | ||
| // synthesized members for proper new/override usage as we're very likely to just report cascaded |
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.
Is it just "very likely"? I thought it better be guaranteed otherwise we could have an invalid program for which we don't report any errors.
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.
good point. revising verbiage.
...Compilers/CSharp/Portable/Symbols/Source/SourceMemberContainerSymbol_ImplementationChecks.cs
Outdated
Show resolved
Hide resolved
…inerSymbol_ImplementationChecks.cs
Completed: Reduce cascading errors when record inherits from non-record
Summary of Changes:
This PR successfully reduces cascading errors when a record inherits from a non-record type. Previously, 5 errors were reported. Now only CS8864 is reported.
Before: When
record Derived : Base { }where Base is not a record:After: Only CS8864 is reported (the relevant error)
All relevant tests pass.
Original prompt
💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.