Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Nov 3, 2025

MA0095 was reporting diagnostics when IEquatable<T> was inherited via CRTP (Curiously Recurring Template Pattern), while CA1067 correctly does not. This caused false positives for common base class patterns:

// Base class with CRTP pattern
public abstract class Entity<T> : IEquatable<T> where T : Entity<T>
{
    public bool Equals(T? other) => true;
}

// MA0095 incorrectly fired here, CA1067 did not
public sealed class User : Entity<User>;

Changes

Analyzer logic

  • Check if IEquatable<T> is directly declared (via symbol.Interfaces) vs inherited (via symbol.AllInterfaces)
  • Only report MA0095 for types that directly declare the interface
  • Search entire type hierarchy for Equals(object) override, not just the declaring type

Tests

  • Added EqualityShouldBeCorrectlyImplementedAnalyzerMA0095Tests.cs covering:
    • Direct implementation with/without override
    • CRTP inheritance patterns
    • Mixed direct + inherited scenarios

The fix aligns MA0095 behavior with CA1067: types inheriting IEquatable<T> via CRTP are not required to override Equals(object) themselves, as the pattern intentionally delegates equality to the base class.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • www.nuget.org
    • Triggering command: /usr/share/dotnet/dotnet exec --runtimeconfig /home/REDACTED/work/Meziantou.Analyzer/Meziantou.Analyzer/tests/Meziantou.Analyzer.Test/bin/Debug/net10.0/Meziantou.Analyzer.Test.runtimeconfig.json --depsfile /home/REDACTED/work/Meziantou.Analyzer/Meziantou.Analyzer/tests/Meziantou.Analyzer.Test/bin/Debug/net10.0/Meziantou.Analyzer.Test.deps.json /home/REDACTED/work/Meziantou.Analyzer/Meziantou.Analyzer/tests/Meziantou.Analyzer.Test/bin/Debug/net10.0/testhost.dll --port 34457 --endpoint 127.0.0.1:034457 --role client --parentprocessid 4269 --datacollectionport 44183 --telemetryoptedin false (dns block)
    • Triggering command: /usr/share/dotnet/dotnet exec --runtimeconfig /home/REDACTED/work/Meziantou.Analyzer/Meziantou.Analyzer/tests/Meziantou.Analyzer.Test/bin/Debug/net10.0/Meziantou.Analyzer.Test.runtimeconfig.json --depsfile /home/REDACTED/work/Meziantou.Analyzer/Meziantou.Analyzer/tests/Meziantou.Analyzer.Test/bin/Debug/net10.0/Meziantou.Analyzer.Test.deps.json /home/REDACTED/work/Meziantou.Analyzer/Meziantou.Analyzer/tests/Meziantou.Analyzer.Test/bin/Debug/net10.0/testhost.dll --port 34339 --endpoint 127.0.0.1:034339 --role client --parentprocessid 4667 --datacollectionport 42301 --telemetryoptedin false (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

Original prompt

This section details on the original issue you should resolve

<issue_title>MA0095 behaves differently to CA1067 with CRTP</issue_title>
<issue_description>### Version of the Meziantou.Analyzer NuGet package

2.0.239

Rule Identifier

MA0095

Target Framework

net9.0

C# Language version

C# 13

Description

Unsure if the difference in behaviour between MA0085 and CA1067 is expected, but MA0095 fires when using CRTP and CA1067 does not. I thought it was worth reporting just in-case. Thanks!

Reproduction Steps

Minimal code:

public abstract class Crtp<T> : IEquatable<T> where T : Crtp<T>
{
    public bool Equals(T? other) => true;
}

public sealed class TriggersMA0095Only : Crtp<TriggersMA0095Only>;

public sealed class TriggersMA0095AndCA1067 : IEquatable<TriggersMA0095AndCA1067>
{
    public bool Equals(TriggersMA0095AndCA1067? other) => true;
}

Other information

Updating Crtp to the following has no change on analyzers that are triggered:

public abstract class Crtp<T> : IEquatable<T> where T : Crtp<T>
{
    public bool Equals(T? other) => true;
    public override bool Equals(object? obj) => true;
    public override int GetHashCode() => 0;
}
```</issue_description>

<agent_instructions>Look at the repro and ensure MA0095 is reported</agent_instructions>

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

<comments>
</comments>

✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

Copilot AI changed the title [WIP] Fix behavior difference between MA0095 and CA1067 with CRTP Fix MA0095 to not report for CRTP-inherited IEquatable<T> Nov 3, 2025
Copilot finished work on behalf of meziantou November 3, 2025 18:32
Copilot AI requested a review from meziantou November 3, 2025 18:32
@meziantou meziantou marked this pull request as ready for review November 4, 2025 01:50
@meziantou meziantou merged commit f2aa48f into main Nov 4, 2025
12 checks passed
@meziantou meziantou deleted the copilot/fix-ma0095-crtp-difference branch November 4, 2025 02:01
This was referenced Nov 5, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

MA0095 behaves differently to CA1067 with CRTP

2 participants