Skip to content

Comments

Fix redundantEquatable incorrectly removing custom == on Strideable types#2417

Merged
calda merged 5 commits intodevelopfrom
copilot/fix-redundant-equatable-issue
Feb 24, 2026
Merged

Fix redundantEquatable incorrectly removing custom == on Strideable types#2417
calda merged 5 commits intodevelopfrom
copilot/fix-redundant-equatable-issue

Conversation

Copy link

Copilot AI commented Feb 24, 2026

Strideable provides a default == implementation derived from distance(to:). A type conforming to Strideable may intentionally override this with a custom ==, and the redundantEquatable rule was removing it — silently restoring the Strideable default and changing runtime behavior.

Changes

  • Sources/Rules/RedundantEquatable.swift: In manuallyImplementedEquatableTypes, track fully-qualified names of types with a Strideable conformance (across primary declarations and extensions). Exclude those types from == removal.
  • Tests/Rules/RedundantEquatableTests.swift: Add four test cases covering all combinations of where the conformances can appear:
    • Both Strideable and Equatable declared inline on the type
    • Both conformances in separate extensions
    • Strideable inline, Equatable (with ==) in a separate extension
    • Equatable (with ==) inline, Strideable in a separate extension
  • README.md: Added a Known Issues entry noting that the redundantEquatable rule may incorrectly remove a == implementation that intentionally overrides a default == provided by any protocol (not just Strideable). The entry explicitly notes that SwiftFormat does handle the Strideable-in-same-file case correctly, but cannot detect Strideable conformances in a different file or default == implementations from other protocols. The recommended workaround is to use // swiftformat:disable:next redundantEquatable or disable the rule entirely.

Example

// Before fix: `==` was incorrectly removed
struct Foo: Strideable, Equatable {
    let value: Int

    // This overrides Strideable's default ==; removal changes runtime behavior
    static func == (lhs: Foo, rhs: Foo) -> Bool {
        lhs.value == rhs.value
    }
    ...
}
Original prompt

This section details on the original issue you should resolve

<issue_title>redundantEquatable removes non-redundant ==</issue_title>
<issue_description>See discussion here.

Conformance to Strideable implies a default Equitable implementation that may be undesirable by the type. In this case, the type may choose to implement its own Equitable == implementation. This rule would remove that implementation, thus restoring the default Strideable implementation and effectively altering runtime behaviour of the type.</issue_description>

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


💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

@calda
Copy link
Collaborator

calda commented Feb 24, 2026

@copilot, also add a mention about this to Known Issues in the README. We can only detect this if the Equatable and Strideable conformances are in the same file.

Co-authored-by: calda <1811727+calda@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix redundantEquatable removing custom == implementation Fix redundantEquatable incorrectly removing custom == on Strideable types Feb 24, 2026
…le conformances

Co-authored-by: calda <1811727+calda@users.noreply.github.com>
Copy link
Author

Copilot AI commented Feb 24, 2026

@copilot, also add a mention about this to Known Issues in the README. We can only detect this if the Equatable and Strideable conformances are in the same file.

Added in bf4ccf1. The Known Issues entry notes that the rule will correctly preserve == when the Strideable conformance is in the same file, but if it's in a different file SwiftFormat cannot detect it — and recommends using // swiftformat:disable:next redundantEquatable as a workaround.

README.md Outdated

* The `propertyTypes` rule can cause a build failure in cases like `let foo = Foo.bar` where the value is a static member that doesn't return the same time. For example, `let foo: Foo = .bar` would be invalid if the `bar` property was defined as `static var bar: Bar`. As a workaround you can write the name of the type explicitly, like `let foo: Bar = Foo.bar`, or exclude the type name and/or property name with `--preserve-symbols Bar,bar,etc`.

* The `redundantEquatable` rule will not remove a custom `==` implementation if the type also conforms to `Strideable` *in the same file*. If the `Strideable` conformance is defined in a different file, SwiftFormat cannot detect it, and may incorrectly remove a `==` implementation that intentionally overrides the default one provided by `Strideable`. As a workaround you can use the `// swiftformat:disable:next redundantEquatable` comment directive to disable the rule for the affected type (or just disable the `redundantEquatable` rule completely).
Copy link
Collaborator

Choose a reason for hiding this comment

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

@copilot: This also applies to types other than Strideable that potentially add defaulted == implementations. Broaden this, and mention that we do handle it for Stridable conformance in the same file as the ==.

Copy link
Author

Choose a reason for hiding this comment

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

Updated in a29f15e. The Known Issues entry now mentions that any protocol providing a default == can cause this problem, with Strideable as the concrete example, and explicitly states that SwiftFormat handles the Strideable-in-same-file case correctly.

…ension

Co-authored-by: calda <1811727+calda@users.noreply.github.com>
…er protocols beyond Strideable

Co-authored-by: calda <1811727+calda@users.noreply.github.com>
Copy link
Author

Copilot AI commented Feb 24, 2026

@copilot, also add a mention about this to Known Issues in the README. We can only detect this if the Equatable and Strideable conformances are in the same file.

Already addressed in bf4ccf1, and the entry was broadened in a29f15e to cover protocols other than Strideable as well.

@calda calda marked this pull request as ready for review February 24, 2026 02:35
@calda calda changed the base branch from main to develop February 24, 2026 02:36
@calda calda merged commit 64999c1 into develop Feb 24, 2026
21 of 24 checks passed
@calda calda deleted the copilot/fix-redundant-equatable-issue branch February 24, 2026 02:44
@codecov
Copy link

codecov bot commented Feb 24, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 95.20%. Comparing base (22a472c) to head (a29f15e).
⚠️ Report is 52 commits behind head on develop.

Additional details and impacted files
@@             Coverage Diff             @@
##           develop    #2417      +/-   ##
===========================================
- Coverage    95.22%   95.20%   -0.02%     
===========================================
  Files          161      161              
  Lines        24181    24188       +7     
===========================================
+ Hits         23026    23029       +3     
- Misses        1155     1159       +4     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

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.

redundantEquatable removes non-redundant ==

2 participants