mirror of
https://github.com/nicklockwood/SwiftFormat.git
synced 2026-06-16 10:34:34 +00:00
Update redundantEquatable to preserve == implementation in types with Strideable conformance (#2417)
Co-authored-by: calda <1811727+calda@users.noreply.github.com>
This commit is contained in:
committed by
Nick Lockwood
co-authored by
calda
parent
d6932a9c52
commit
147d0d6019
@@ -1086,6 +1086,8 @@ Known issues
|
||||
|
||||
* 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 may incorrectly remove a `==` implementation that intentionally overrides a default `==` provided by another protocol. For example, `Strideable` provides a default `==` implementation derived from `distance(to:)`, and a type may override it with a custom `==`. SwiftFormat handles this case when the `Strideable` conformance is defined in the same file, but it cannot detect conformances in a different file, nor default `==` implementations provided by other protocols. 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).
|
||||
|
||||
|
||||
Tip Jar
|
||||
-----------
|
||||
|
||||
@@ -153,6 +153,7 @@ extension Formatter {
|
||||
func manuallyImplementedEquatableTypes(in declarations: [Declaration]) -> [EquatableType] {
|
||||
var typeDeclarationsByFullyQualifiedName: [String: Declaration] = [:]
|
||||
var typesWithEquatableConformances: [(fullyQualifiedTypeName: String, declarationWithEquatableConformance: Declaration)] = []
|
||||
var typesWithStrideableConformances: Set<String> = []
|
||||
var equatableImplementationsByFullyQualifiedName: [String: Declaration] = [:]
|
||||
|
||||
declarations.forEachRecursiveDeclaration { declaration in
|
||||
@@ -178,6 +179,11 @@ extension Formatter {
|
||||
declarationWithEquatableConformance: declaration
|
||||
))
|
||||
}
|
||||
|
||||
// Strideable provides a default `==` implementation, so a custom `==` may not be redundant
|
||||
if conformances.contains(where: { $0.conformance.string == "Strideable" }) {
|
||||
typesWithStrideableConformances.insert(fullyQualifiedName)
|
||||
}
|
||||
}
|
||||
|
||||
if declaration.keyword == "func",
|
||||
@@ -229,6 +235,10 @@ extension Formatter {
|
||||
}
|
||||
|
||||
return typesWithEquatableConformances.compactMap { typeName, declarationWithEquatableConformance in
|
||||
// Types conforming to Strideable get a default `==` implementation via that protocol,
|
||||
// so a custom `==` on such a type may be intentionally overriding that default.
|
||||
guard !typesWithStrideableConformances.contains(typeName) else { return nil }
|
||||
|
||||
guard let typeDeclaration = typeDeclarationsByFullyQualifiedName[typeName],
|
||||
let equatableImplementation = equatableImplementationsByFullyQualifiedName[typeName]
|
||||
else { return nil }
|
||||
|
||||
@@ -649,4 +649,108 @@ final class RedundantEquatableTests: XCTestCase {
|
||||
|
||||
testFormatting(for: input, rule: .redundantEquatable)
|
||||
}
|
||||
|
||||
func testPreserveEquatableImplementationForStrideableType() {
|
||||
// `Strideable` provides a default `==` implementation via `distance(to:)`,
|
||||
// so a custom `==` on a Strideable type may be intentionally overriding that default.
|
||||
let input = """
|
||||
struct Foo: Strideable, Equatable {
|
||||
let value: Int
|
||||
|
||||
static func == (lhs: Foo, rhs: Foo) -> Bool {
|
||||
lhs.value == rhs.value
|
||||
}
|
||||
|
||||
func distance(to other: Foo) -> Int {
|
||||
other.value - value
|
||||
}
|
||||
|
||||
func advanced(by n: Int) -> Foo {
|
||||
Foo(value: value + n)
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
testFormatting(for: input, rule: .redundantEquatable)
|
||||
}
|
||||
|
||||
func testPreserveEquatableImplementationForStrideableTypeInExtension() {
|
||||
// `Strideable` provides a default `==` implementation via `distance(to:)`,
|
||||
// so a custom `==` on a Strideable type may be intentionally overriding that default.
|
||||
let input = """
|
||||
struct Foo {
|
||||
let value: Int
|
||||
}
|
||||
|
||||
extension Foo: Strideable {
|
||||
func distance(to other: Foo) -> Int {
|
||||
other.value - value
|
||||
}
|
||||
|
||||
func advanced(by n: Int) -> Foo {
|
||||
Foo(value: value + n)
|
||||
}
|
||||
}
|
||||
|
||||
extension Foo: Equatable {
|
||||
static func == (lhs: Foo, rhs: Foo) -> Bool {
|
||||
lhs.value == rhs.value
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
testFormatting(for: input, rule: .redundantEquatable)
|
||||
}
|
||||
|
||||
func testPreserveEquatableImplementationForStrideableTypeWithEquatableInExtension() {
|
||||
// `Strideable` provides a default `==` implementation via `distance(to:)`,
|
||||
// so a custom `==` on a Strideable type may be intentionally overriding that default.
|
||||
let input = """
|
||||
struct Foo: Strideable {
|
||||
let value: Int
|
||||
|
||||
func distance(to other: Foo) -> Int {
|
||||
other.value - value
|
||||
}
|
||||
|
||||
func advanced(by n: Int) -> Foo {
|
||||
Foo(value: value + n)
|
||||
}
|
||||
}
|
||||
|
||||
extension Foo: Equatable {
|
||||
static func == (lhs: Foo, rhs: Foo) -> Bool {
|
||||
lhs.value == rhs.value
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
testFormatting(for: input, rule: .redundantEquatable)
|
||||
}
|
||||
|
||||
func testPreserveEquatableImplementationForStrideableTypeWithStrideableInExtension() {
|
||||
// `Strideable` provides a default `==` implementation via `distance(to:)`,
|
||||
// so a custom `==` on a Strideable type may be intentionally overriding that default.
|
||||
let input = """
|
||||
struct Foo: Equatable {
|
||||
let value: Int
|
||||
|
||||
static func == (lhs: Foo, rhs: Foo) -> Bool {
|
||||
lhs.value == rhs.value
|
||||
}
|
||||
}
|
||||
|
||||
extension Foo: Strideable {
|
||||
func distance(to other: Foo) -> Int {
|
||||
other.value - value
|
||||
}
|
||||
|
||||
func advanced(by n: Int) -> Foo {
|
||||
Foo(value: value + n)
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
testFormatting(for: input, rule: .redundantEquatable)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user