Skip to content

Commit af8423d

Browse files
[stdlib][SR-14665] add default == implementation for Comparable
Currently `Comparable` inherits from `Equatable`, but does not provide a default implementation for `==`, so the compiler synthesizes one composed of [member-wise `==`s](https://github.com/apple/swift-evolution/blob/main/proposals/0185-synthesize-equatable-hashable.md#implementation-details). This leads to a problem where if a type's `<` is not composed of member-wise inequalities, then `<`, `>`, and `==` can all evaluate to `false` for some pairs of values, contradicting `Comparable`'s documentation: > Types with Comparable conformance implement the less-than operator (`<`) and the equal-to operator (`==`). These two operations impose a strict total order on the values of a type, in which exactly one of the following must be true for any two values `a` and `b`: > * `a == b` > * `a < b` > * `b < a` For example: ```swift struct Length: Comparable { enum Unit: Double, Comparable { case mm = 0.001 case m = 1 case banana = 0.178 } let magnitude: Double let unit: Unit static func < (lhs: Self, rhs: Self) -> Bool { lhs.magnitude * lhs.unit.rawValue < rhs.magnitude * rhs.unit.rawValue } } let aBanana = Length(magnitude: 1, unit: .banana) let oneBanana = Length(magnitude: 0.178, unit: .m) print(aBanana < oneBanana) // prints "false", because Length's < says so. print(aBanana > oneBanana) // prints "false", because Comparable's default implementation of >(a,b) is <(b,a). print(aBanana == oneBanana) // prints "false", because the 2 Length instances are not member-wise equal. ``` Relevant forums discussion: https://forums.swift.org/t/add-default-implementation-of-to-comparable/48832 This bug has previously resulted in incorrect semantic version comparison in SwiftPM (swiftlang/swift-package-manager#3486 and swiftlang/swift-tools-support-core#214)
1 parent 17d3335 commit af8423d

File tree

1 file changed

+25
-0
lines changed

1 file changed

+25
-0
lines changed

stdlib/public/core/Comparable.swift

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,16 @@ public protocol Comparable: Equatable {
171171
/// - lhs: A value to compare.
172172
/// - rhs: Another value to compare.
173173
static func > (lhs: Self, rhs: Self) -> Bool
174+
175+
/// Returns a Boolean value indicating whether two values are equal.
176+
///
177+
/// Equality is the inverse of inequality. For any values `a` and `b`,
178+
/// `a == b` implies that `a != b` is `false`.
179+
///
180+
/// - Parameters:
181+
/// - lhs: A value to compare.
182+
/// - rhs: Another value to compare.
183+
static func == (lhs: Self, rhs: Self) -> Bool
174184
}
175185

176186
extension Comparable {
@@ -217,4 +227,19 @@ extension Comparable {
217227
public static func >= (lhs: Self, rhs: Self) -> Bool {
218228
return !(lhs < rhs)
219229
}
230+
231+
/// Returns a Boolean value indicating whether two values are equal.
232+
///
233+
/// Equality is the inverse of inequality. For any values `a` and `b`,
234+
/// `a == b` implies that `a != b` is `false`.
235+
///
236+
/// This is the default implementation of the equal-to operator (`!=`) for
237+
/// any type that conforms to `Comparable`.
238+
///
239+
/// - Parameters:
240+
/// - lhs: A value to compare.
241+
/// - rhs: Another value to compare.
242+
public static func == (lhs: Self, rhs: Self) -> Bool {
243+
return !(lhs < rhs || lhs > rhs)
244+
}
220245
}

0 commit comments

Comments
 (0)