Skip to content

Commit fa1d07c

Browse files
authored
Merge pull request #13046 from DougGregor/conditional-conformance-equatable
Use conditional conformances to implement Equatable for Optional, Array, and Dictionary
2 parents f5989d2 + 887ad4a commit fa1d07c

15 files changed

+50
-417
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,13 @@ CHANGELOG
2222
Swift 4.1
2323
---------
2424

25+
* [SE-0143][] The standard library types `Optional`, `Array`, and
26+
`Dictionary` now conform to the `Equatable` protocol when their element types
27+
conform to `Equatable`. This allows the `==` operator to compose (e.g., one
28+
can compare two values of type `[Int : [Int?]?]` with `==`), as well as use
29+
various algorthims defined for `Equatable` element types, such as
30+
`index(of:)`.
31+
2532
* [SE-0157][] is implemented. Associated types can now declare "recursive"
2633
constraints, which require that the associated type conform to the enclosing
2734
protocol. The standard library protocols have been updated to make use of

stdlib/public/SDK/XCTest/XCTest.swift

Lines changed: 0 additions & 394 deletions
Large diffs are not rendered by default.

stdlib/public/core/Arrays.swift.gyb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2187,7 +2187,7 @@ extension _ArrayBufferProtocol {
21872187

21882188
% for (Self, a_Self) in arrayTypes:
21892189

2190-
extension ${Self} where Element : Equatable {
2190+
extension ${Self} : Equatable where Element : Equatable {
21912191
/// Returns `true` if these arrays contain the same elements.
21922192
@_inlineable
21932193
public static func ==(lhs: ${Self}<Element>, rhs: ${Self}<Element>) -> Bool {

stdlib/public/core/HashedCollections.swift.gyb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2709,7 +2709,7 @@ extension Dictionary {
27092709
}
27102710
}
27112711

2712-
extension Dictionary where Value : Equatable {
2712+
extension Dictionary : Equatable where Value : Equatable {
27132713
@_inlineable // FIXME(sil-serialize-all)
27142714
public static func == (lhs: [Key : Value], rhs: [Key : Value]) -> Bool {
27152715
switch (lhs._variantBuffer, rhs._variantBuffer) {

stdlib/public/core/Optional.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -313,7 +313,7 @@ func _diagnoseUnexpectedNilOptional(_filenameStart: Builtin.RawPointer,
313313
line: UInt(_line))
314314
}
315315

316-
extension Optional where Wrapped : Equatable {
316+
extension Optional : Equatable where Wrapped : Equatable {
317317
/// Returns a Boolean value indicating whether two optional instances are
318318
/// equal.
319319
///

test/Constraints/bridging.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -181,8 +181,10 @@ func dictionaryToNSDictionary() {
181181
struct NotEquatable {}
182182
func notEquatableError(_ d: Dictionary<Int, NotEquatable>) -> Bool {
183183
// FIXME: Another awful diagnostic.
184-
return d == d // expected-error{{binary operator '==' cannot be applied to two 'Dictionary<Int, NotEquatable>' operands}}
185-
// expected-note @-1 {{overloads for '==' exist with these partially matching parameter lists: }}
184+
return d == d // expected-error{{'<Self where Self : Equatable> (Self.Type) -> (Self, Self) -> Bool' requires that 'NotEquatable' conform to 'Equatable'}}
185+
// expected-note @-1 {{requirement from conditional conformance of 'Dictionary<Int, NotEquatable>' to 'Equatable'}}
186+
// expected-error @-2 {{type 'NotEquatable' does not conform to protocol 'Equatable'}}
187+
// expected-note @-3{{requirement specified as 'NotEquatable' : 'Equatable'}}
186188
}
187189

188190
// NSString -> String

test/Constraints/patterns.swift

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -163,9 +163,8 @@ case nil?: break // expected-warning {{case is already handled by previous patte
163163
default: break
164164
}
165165

166-
// <rdar://problem/21995744> QoI: Binary operator '~=' cannot be applied to operands of type 'String' and 'String?'
167166
switch ("foo" as String?) {
168-
case "what": break // expected-error{{expression pattern of type 'String' cannot match values of type 'String?'}} {{12-12=?}}
167+
case "what": break
169168
default: break
170169
}
171170

test/Parse/enum.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ enum RawTypeNotFirst : RawTypeNotFirstProtocol, Int { // expected-error {{raw ty
144144
}
145145

146146
enum ExpressibleByRawTypeNotLiteral : Array<Int> { // expected-error {{raw type 'Array<Int>' is not expressible by any literal}}
147-
// expected-error@-1{{'ExpressibleByRawTypeNotLiteral' declares raw type 'Array<Int>', but does not conform to RawRepresentable and conformance could not be synthesized}} expected-error@-1 {{RawRepresentable conformance cannot be synthesized because raw type 'Array<Int>' is not Equatable}}
147+
// expected-error@-1{{'ExpressibleByRawTypeNotLiteral' declares raw type 'Array<Int>', but does not conform to RawRepresentable and conformance could not be synthesized}}
148148
case Ladd, Elliott, Sixteenth, Harrison
149149
}
150150

test/Parse/pointer_conversion.swift.gyb

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -291,8 +291,10 @@ struct NotEquatable {}
291291
func arrayComparison(_ x: [NotEquatable], y: [NotEquatable], p: UnsafeMutablePointer<NotEquatable>) {
292292
var x = x
293293
// Don't allow implicit array-to-pointer conversions in operators.
294-
let a: Bool = x == y // expected-error{{binary operator '==' cannot be applied to two '[NotEquatable]' operands}}
295-
// expected-note @-1 {{overloads for '==' exist with these partially matching parameter lists:}}
294+
let a: Bool = x == y // expected-error{{'<Self where Self : Equatable> (Self.Type) -> (Self, Self) -> Bool' requires that 'NotEquatable' conform to 'Equatable'}}
295+
// expected-error @-1 {{type 'NotEquatable' does not conform to protocol 'Equatable'}}
296+
// expected-note @-2 {{requirement from conditional conformance of '[NotEquatable]' to 'Equatable'}}
297+
// expected-note @-3 {{requirement specified as 'NotEquatable' : 'Equatable'}}
296298

297299
let _: Bool = p == &x // Allowed!
298300
}

test/Sema/enum_equatable_conditional.swift

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
// RUN: %target-typecheck-verify-swift
22

3-
// FIXME: Should be part of the standard library.
4-
extension Array: Equatable where Element: Equatable { }
5-
63
struct NotEquatable { }
74

85
enum WithArrayOfNotEquatables : Equatable { // expected-error{{type 'WithArrayOfNotEquatables' does not conform to protocol 'Equatable'}}

test/Sema/enum_equatable_hashable.swift

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ enum CustomHashable {
3535

3636
var hashValue: Int { return 0 }
3737
}
38-
func ==(x: CustomHashable, y: CustomHashable) -> Bool { // expected-note 3 {{non-matching type}}
38+
func ==(x: CustomHashable, y: CustomHashable) -> Bool { // expected-note 4 {{non-matching type}}
3939
return true
4040
}
4141

@@ -50,7 +50,7 @@ enum InvalidCustomHashable {
5050

5151
var hashValue: String { return "" } // expected-note{{previously declared here}}
5252
}
53-
func ==(x: InvalidCustomHashable, y: InvalidCustomHashable) -> String { // expected-note 3 {{non-matching type}}
53+
func ==(x: InvalidCustomHashable, y: InvalidCustomHashable) -> String { // expected-note 4 {{non-matching type}}
5454
return ""
5555
}
5656
if InvalidCustomHashable.A == .B { }
@@ -172,7 +172,7 @@ public enum Medicine {
172172

173173
extension Medicine : Equatable {}
174174

175-
public func ==(lhs: Medicine, rhs: Medicine) -> Bool { // expected-note 2 {{non-matching type}}
175+
public func ==(lhs: Medicine, rhs: Medicine) -> Bool { // expected-note 3 {{non-matching type}}
176176
return true
177177
}
178178

@@ -189,7 +189,7 @@ extension NotExplicitlyHashableAndCannotDerive : Hashable {} // expected-error 2
189189
// Verify that conformance (albeit manually implemented) can still be added to
190190
// a type in a different file.
191191
extension OtherFileNonconforming: Hashable {
192-
static func ==(lhs: OtherFileNonconforming, rhs: OtherFileNonconforming) -> Bool { // expected-note 2 {{non-matching type}}
192+
static func ==(lhs: OtherFileNonconforming, rhs: OtherFileNonconforming) -> Bool { // expected-note 3 {{non-matching type}}
193193
return true
194194
}
195195
var hashValue: Int { return 0 }
@@ -226,6 +226,17 @@ indirect enum TotallyIndirect: Hashable {
226226
case end(Int)
227227
}
228228

229+
// Check the use of conditional conformances.
230+
enum ArrayOfEquatables : Equatable {
231+
case only([Int])
232+
}
233+
234+
struct NotEquatable { }
235+
236+
enum ArrayOfNotEquatables : Equatable { // expected-error{{type 'ArrayOfNotEquatables' does not conform to protocol 'Equatable'}}
237+
case only([NotEquatable])
238+
}
239+
229240
// FIXME: Remove -verify-ignore-unknown.
230241
// <unknown>:0: error: unexpected error produced: invalid redeclaration of 'hashValue'
231242
// <unknown>:0: error: unexpected note produced: candidate has non-matching type '(Foo, Foo) -> Bool'

test/Sema/enum_raw_representable.swift

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,6 @@ class Outer {
7878
let a: Int = E.a // expected-error {{cannot convert value of type 'Outer.E' to specified type 'Int'}}
7979

8080
enum E : Array<Int> { // expected-error {{raw type 'Array<Int>' is not expressible by any literal}}
81-
// expected-error@-1 {{'Outer.E' declares raw type 'Array<Int>', but does not conform to RawRepresentable and conformance could not be synthesized}}
82-
// expected-error@-2 {{RawRepresentable conformance cannot be synthesized because raw type 'Array<Int>' is not Equatable}}
8381
case a
8482
}
8583
}
@@ -146,3 +144,11 @@ func rdar32432253(_ condition: Bool = false) {
146144
let _ = choice == "bar"
147145
// expected-error@-1 {{cannot convert value of type 'E_32431165' to expected argument type 'String'}} {{11-11=}} {{17-17=.rawValue}}
148146
}
147+
148+
149+
struct NotEquatable { }
150+
151+
enum ArrayOfNewEquatable : Array<NotEquatable> { }
152+
// expected-error@-1{{raw type 'Array<NotEquatable>' is not expressible by any literal}}
153+
// expected-error@-2{{'ArrayOfNewEquatable' declares raw type 'Array<NotEquatable>', but does not conform to RawRepresentable and conformance could not be synthesized}}
154+
// expected-error@-3{{RawRepresentable conformance cannot be synthesized because raw type 'Array<NotEquatable>' is not Equatable}}

test/expr/unary/keypath/salvage-with-other-type-errors.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,8 @@ protocol Bindable: class { }
5151

5252
extension Bindable {
5353
func test<Value>(to targetKeyPath: ReferenceWritableKeyPath<Self, Value>, change: Value?) {
54-
if self[keyPath:targetKeyPath] != change { // expected-error{{cannot convert value of type 'Value' to expected argument type '_OptionalNilComparisonType'}}
54+
if self[keyPath:targetKeyPath] != change { // expected-error{{}}
55+
// expected-note@-1{{overloads for '!=' exist with these partially matching parameter lists: (Self, Self), (_OptionalNilComparisonType, Wrapped?)}}
5556
self[keyPath: targetKeyPath] = change!
5657
}
5758
}

test/expr/unary/selector/selector.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ default:
134134
}
135135

136136
switch optionalSel {
137-
case #selector(SR1827.bar): // expected-error{{expression pattern of type 'Selector' cannot match values of type 'Selector?'}} {{27-27=?}}
137+
case #selector(SR1827.bar):
138138
break
139139
case #selector(SR1827.bar)!: // expected-error{{cannot force unwrap value of non-optional type 'Selector'}}
140140
break

test/stdlib/ArrayDiagnostics.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ class NotEquatable {}
55
func test_ArrayOfNotEquatableIsNotEquatable() {
66
var a = [ NotEquatable(), NotEquatable() ]
77
// FIXME: This is an awful error.
8-
if a == a {} // expected-error {{binary operator '==' cannot be applied to two '[NotEquatable]' operands}}
9-
// expected-note @-1 {{overloads for '==' exist with these partially matching parameter lists: }}
8+
if a == a {} // expected-error {{'<Self where Self : Equatable> (Self.Type) -> (Self, Self) -> Bool' requires that 'NotEquatable' conform to 'Equatable'}}
9+
// expected-error@-1{{type 'NotEquatable' does not conform to protocol 'Equatable'}}
10+
// expected-note @-2 {{requirement specified as 'NotEquatable' : 'Equatable'}}
11+
// expected-note@-3{{requirement from conditional conformance of '[NotEquatable]' to 'Equatable'}}
1012
}
1113

0 commit comments

Comments
 (0)