Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Nov 17, 2025

Completed: Reduce cascading errors when record inherits from non-record

  • Understand the current error reporting flow
  • Identify all locations where cascading errors are generated
  • Add checks to prevent cascading errors when ERR_BadRecordBase is already reported
    • Fix copy constructor check in Binder_Statements.cs
    • Fix PrintMembers override check in SynthesizedRecordPrintMembers.cs
    • Fix EqualityContract override check in SynthesizedRecordEqualityContractProperty.cs
    • Fix BaseEquals creation in SourceMemberContainerSymbol.cs
    • Fix Equals method generation in SynthesizedRecordEquals.cs
    • Fix modifiers for synthesized members to use virtual instead of override when base is not a record
  • Test the changes to ensure only CS8864 is reported
  • Update test expectations
  • Run tests to ensure no regressions
  • Extract helper method BaseTypeIsRecordNoUseSiteDiagnostics
  • Remove unnecessary checks

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:

  • CS8864: Records may only inherit from object or another record (the relevant error)
  • CS8867: No accessible copy constructor found in base type
  • CS0115: No suitable method found to override (EqualityContract)
  • CS0115: No suitable method found to override (Equals)
  • CS0115: No suitable method found to override (PrintMembers)

After: Only CS8864 is reported (the relevant error)

All relevant tests pass.

Original prompt

This section details on the original issue you should resolve

<issue_title>Reduce cascading errors when record inherits from non-record</issue_title>
<issue_description>SharpLab

class Base { }

record Derived : Base { }

This scenario produces 5 errors (at the time this issue was filed). It seems like it would be reasonable to only produce error CS8864: Records may only inherit from object or another record in this case.

Originally posted by @RikkiGibson in #62999 (comment)</issue_description>

<agent_instructions>Copilot, the errors reported are:

CS8864: Records may only inherit from object or another record
No accessible copy constructor found in base type 'Base'.
'Derived.PrintMembers(StringBuilder)': no suitable method found to override
'Derived.Equals(Base?)': no suitable method found to override
'Derived.EqualityContract': no suitable method found to override

The first one is the relevant one. First, it means we've detected that there is a problem with the base type and have a distinct way to check for that in the future.

For the remainder, we should perform the same check. First, we should not look for a copy constructor if hte base type is not a record. Second, we should not attempt to emit these overrides if the base type is not a record.</agent_instructions>

Comments on the Issue (you are @copilot in this section)


💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.

Copilot AI changed the title [WIP] Fix cascading errors for record inheritance Reduce cascading errors when record inherits from non-record Nov 17, 2025
Copilot AI requested a review from CyrusNajmabadi November 17, 2025 12:09
// 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)
Copy link
Member

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.

Copy link
Contributor Author

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.
Copy link
Member

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?

Copy link
Contributor Author

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

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.

Copy link
Contributor Author

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)

CyrusNajmabadi and others added 2 commits November 17, 2025 13:20
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;
}
Copy link
Member

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.

Copy link
Contributor Author

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.

Copy link
Member

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?

Copy link
Contributor Author

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.

Copy link
Member

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?

Copy link
Contributor Author

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().

Copilot AI and others added 2 commits November 17, 2025 12:47
- 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;
}
Copy link
Member

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

jjonescz
jjonescz previously approved these changes Nov 24, 2025
/// <summary>
/// Returns true if the base type is a record (has a valid Clone method).
/// </summary>
internal static bool BaseTypeIsRecordNoUseSiteDiagnostics(NamedTypeSymbol baseType)
Copy link
Contributor

@AlekseyTs AlekseyTs Nov 24, 2025

Choose a reason for hiding this comment

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

BaseTypeIsRecordNoUseSiteDiagnostics

  1. It doesn't feel like this helper belongs in this class/file.
  2. It doesn't look like we need this helper at all. Tit looks like existing IsRecord property will be sufficient, especially as we aren't interested in any use-site information from this check. #Closed

Copy link
Member

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

@AlekseyTs AlekseyTs Nov 24, 2025

Choose a reason for hiding this comment

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

return;

I am not comfortable with this approach. I think the suppression should happen only for specific members synthesized by compiler. #Closed

Copy link
Member

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

@AlekseyTs AlekseyTs Nov 24, 2025

Choose a reason for hiding this comment

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

if (!baseType.IsObjectType() && SynthesizedRecordClone.BaseTypeIsRecordNoUseSiteDiagnostics(baseType))

Please keep an early out approach that was used before. Otherwise, the local declaration and the if statement following this one should be moved inside this one. #Closed

Copy link
Member

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

@AlekseyTs AlekseyTs Nov 24, 2025

Choose a reason for hiding this comment

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

if (SynthesizedRecordClone.BaseTypeIsRecordNoUseSiteDiagnostics(baseType))

Similar comment as for VerifyOverridesEqualityContractFromBase. Either move all related code inside, or use an early out approach. #Closed

@AlekseyTs
Copy link
Contributor

AlekseyTs commented Nov 24, 2025

Done with review pass (commit 56) #Closed

@CyrusNajmabadi CyrusNajmabadi force-pushed the copilot/reduce-cascading-errors branch from b62e639 to 8385e02 Compare November 24, 2025 20:31
Copy link
Contributor

@AlekseyTs AlekseyTs left a 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
Copy link
Member

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.

Copy link
Member

Choose a reason for hiding this comment

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

good point. revising verbiage.

@CyrusNajmabadi CyrusNajmabadi enabled auto-merge (squash) November 25, 2025 09:43
@CyrusNajmabadi CyrusNajmabadi merged commit 411961a into main Nov 25, 2025
28 of 29 checks passed
@dotnet-policy-service dotnet-policy-service bot added this to the Next milestone Nov 25, 2025
@CyrusNajmabadi CyrusNajmabadi deleted the copilot/reduce-cascading-errors branch November 25, 2025 14:28
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.

Reduce cascading errors when record inherits from non-record

4 participants