Skip to content

Use conditional conformances to implement Equatable for Optional, Array, and Dictionary #13046

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,13 @@ CHANGELOG
Swift 4.1
---------

* [SE-0143][] The standard library types `Optional`, `Array`, and
`Dictionary` now conform to the `Equatable` protocol when their element types
conform to `Equatable`. This allows the `==` operator to compose (e.g., one
can compare two values of type `[Int : [Int?]?]` with `==`), as well as use
various algorthims defined for `Equatable` element types, such as
`index(of:)`.

* [SE-0157][] is implemented. Associated types can now declare "recursive"
constraints, which require that the associated type conform to the enclosing
protocol. The standard library protocols have been updated to make use of
Expand Down
394 changes: 0 additions & 394 deletions stdlib/public/SDK/XCTest/XCTest.swift

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion stdlib/public/core/Arrays.swift.gyb
Original file line number Diff line number Diff line change
Expand Up @@ -2187,7 +2187,7 @@ extension _ArrayBufferProtocol {

% for (Self, a_Self) in arrayTypes:

extension ${Self} where Element : Equatable {
extension ${Self} : Equatable where Element : Equatable {
/// Returns `true` if these arrays contain the same elements.
@_inlineable
public static func ==(lhs: ${Self}<Element>, rhs: ${Self}<Element>) -> Bool {
Expand Down
2 changes: 1 addition & 1 deletion stdlib/public/core/HashedCollections.swift.gyb
Original file line number Diff line number Diff line change
Expand Up @@ -2709,7 +2709,7 @@ extension Dictionary {
}
}

extension Dictionary where Value : Equatable {
extension Dictionary : Equatable where Value : Equatable {
@_inlineable // FIXME(sil-serialize-all)
public static func == (lhs: [Key : Value], rhs: [Key : Value]) -> Bool {
switch (lhs._variantBuffer, rhs._variantBuffer) {
Expand Down
2 changes: 1 addition & 1 deletion stdlib/public/core/Optional.swift
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,7 @@ func _diagnoseUnexpectedNilOptional(_filenameStart: Builtin.RawPointer,
line: UInt(_line))
}

extension Optional where Wrapped : Equatable {
extension Optional : Equatable where Wrapped : Equatable {
/// Returns a Boolean value indicating whether two optional instances are
/// equal.
///
Expand Down
6 changes: 4 additions & 2 deletions test/Constraints/bridging.swift
Original file line number Diff line number Diff line change
Expand Up @@ -181,8 +181,10 @@ func dictionaryToNSDictionary() {
struct NotEquatable {}
func notEquatableError(_ d: Dictionary<Int, NotEquatable>) -> Bool {
// FIXME: Another awful diagnostic.
return d == d // expected-error{{binary operator '==' cannot be applied to two 'Dictionary<Int, NotEquatable>' operands}}
// expected-note @-1 {{overloads for '==' exist with these partially matching parameter lists: }}
return d == d // expected-error{{'<Self where Self : Equatable> (Self.Type) -> (Self, Self) -> Bool' requires that 'NotEquatable' conform to 'Equatable'}}
// expected-note @-1 {{requirement from conditional conformance of 'Dictionary<Int, NotEquatable>' to 'Equatable'}}
// expected-error @-2 {{type 'NotEquatable' does not conform to protocol 'Equatable'}}
// expected-note @-3{{requirement specified as 'NotEquatable' : 'Equatable'}}
}

// NSString -> String
Expand Down
3 changes: 1 addition & 2 deletions test/Constraints/patterns.swift
Original file line number Diff line number Diff line change
Expand Up @@ -163,9 +163,8 @@ case nil?: break // expected-warning {{case is already handled by previous patte
default: break
}

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

Expand Down
2 changes: 1 addition & 1 deletion test/Parse/enum.swift
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ enum RawTypeNotFirst : RawTypeNotFirstProtocol, Int { // expected-error {{raw ty
}

enum ExpressibleByRawTypeNotLiteral : Array<Int> { // expected-error {{raw type 'Array<Int>' is not expressible by any literal}}
// 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}}
// expected-error@-1{{'ExpressibleByRawTypeNotLiteral' declares raw type 'Array<Int>', but does not conform to RawRepresentable and conformance could not be synthesized}}
case Ladd, Elliott, Sixteenth, Harrison
}

Expand Down
6 changes: 4 additions & 2 deletions test/Parse/pointer_conversion.swift.gyb
Original file line number Diff line number Diff line change
Expand Up @@ -291,8 +291,10 @@ struct NotEquatable {}
func arrayComparison(_ x: [NotEquatable], y: [NotEquatable], p: UnsafeMutablePointer<NotEquatable>) {
var x = x
// Don't allow implicit array-to-pointer conversions in operators.
let a: Bool = x == y // expected-error{{binary operator '==' cannot be applied to two '[NotEquatable]' operands}}
// expected-note @-1 {{overloads for '==' exist with these partially matching parameter lists:}}
let a: Bool = x == y // expected-error{{'<Self where Self : Equatable> (Self.Type) -> (Self, Self) -> Bool' requires that 'NotEquatable' conform to 'Equatable'}}
// expected-error @-1 {{type 'NotEquatable' does not conform to protocol 'Equatable'}}
// expected-note @-2 {{requirement from conditional conformance of '[NotEquatable]' to 'Equatable'}}
// expected-note @-3 {{requirement specified as 'NotEquatable' : 'Equatable'}}

let _: Bool = p == &x // Allowed!
}
Expand Down
3 changes: 0 additions & 3 deletions test/Sema/enum_equatable_conditional.swift
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
// RUN: %target-typecheck-verify-swift

// FIXME: Should be part of the standard library.
extension Array: Equatable where Element: Equatable { }

struct NotEquatable { }

enum WithArrayOfNotEquatables : Equatable { // expected-error{{type 'WithArrayOfNotEquatables' does not conform to protocol 'Equatable'}}
Expand Down
19 changes: 15 additions & 4 deletions test/Sema/enum_equatable_hashable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ enum CustomHashable {

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

Expand All @@ -50,7 +50,7 @@ enum InvalidCustomHashable {

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

extension Medicine : Equatable {}

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

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

// Check the use of conditional conformances.
enum ArrayOfEquatables : Equatable {
case only([Int])
}

struct NotEquatable { }

enum ArrayOfNotEquatables : Equatable { // expected-error{{type 'ArrayOfNotEquatables' does not conform to protocol 'Equatable'}}
case only([NotEquatable])
}

// FIXME: Remove -verify-ignore-unknown.
// <unknown>:0: error: unexpected error produced: invalid redeclaration of 'hashValue'
// <unknown>:0: error: unexpected note produced: candidate has non-matching type '(Foo, Foo) -> Bool'
Expand Down
10 changes: 8 additions & 2 deletions test/Sema/enum_raw_representable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,6 @@ class Outer {
let a: Int = E.a // expected-error {{cannot convert value of type 'Outer.E' to specified type 'Int'}}

enum E : Array<Int> { // expected-error {{raw type 'Array<Int>' is not expressible by any literal}}
// expected-error@-1 {{'Outer.E' declares raw type 'Array<Int>', but does not conform to RawRepresentable and conformance could not be synthesized}}
// expected-error@-2 {{RawRepresentable conformance cannot be synthesized because raw type 'Array<Int>' is not Equatable}}
case a
}
}
Expand Down Expand Up @@ -146,3 +144,11 @@ func rdar32432253(_ condition: Bool = false) {
let _ = choice == "bar"
// expected-error@-1 {{cannot convert value of type 'E_32431165' to expected argument type 'String'}} {{11-11=}} {{17-17=.rawValue}}
}


struct NotEquatable { }

enum ArrayOfNewEquatable : Array<NotEquatable> { }
// expected-error@-1{{raw type 'Array<NotEquatable>' is not expressible by any literal}}
// expected-error@-2{{'ArrayOfNewEquatable' declares raw type 'Array<NotEquatable>', but does not conform to RawRepresentable and conformance could not be synthesized}}
// expected-error@-3{{RawRepresentable conformance cannot be synthesized because raw type 'Array<NotEquatable>' is not Equatable}}
3 changes: 2 additions & 1 deletion test/expr/unary/keypath/salvage-with-other-type-errors.swift
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@ protocol Bindable: class { }

extension Bindable {
func test<Value>(to targetKeyPath: ReferenceWritableKeyPath<Self, Value>, change: Value?) {
if self[keyPath:targetKeyPath] != change { // expected-error{{cannot convert value of type 'Value' to expected argument type '_OptionalNilComparisonType'}}
if self[keyPath:targetKeyPath] != change { // expected-error{{}}
// expected-note@-1{{overloads for '!=' exist with these partially matching parameter lists: (Self, Self), (_OptionalNilComparisonType, Wrapped?)}}
self[keyPath: targetKeyPath] = change!
}
}
Expand Down
2 changes: 1 addition & 1 deletion test/expr/unary/selector/selector.swift
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ default:
}

switch optionalSel {
case #selector(SR1827.bar): // expected-error{{expression pattern of type 'Selector' cannot match values of type 'Selector?'}} {{27-27=?}}
case #selector(SR1827.bar):
break
case #selector(SR1827.bar)!: // expected-error{{cannot force unwrap value of non-optional type 'Selector'}}
break
Expand Down
6 changes: 4 additions & 2 deletions test/stdlib/ArrayDiagnostics.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ class NotEquatable {}
func test_ArrayOfNotEquatableIsNotEquatable() {
var a = [ NotEquatable(), NotEquatable() ]
// FIXME: This is an awful error.
if a == a {} // expected-error {{binary operator '==' cannot be applied to two '[NotEquatable]' operands}}
// expected-note @-1 {{overloads for '==' exist with these partially matching parameter lists: }}
if a == a {} // expected-error {{'<Self where Self : Equatable> (Self.Type) -> (Self, Self) -> Bool' requires that 'NotEquatable' conform to 'Equatable'}}
// expected-error@-1{{type 'NotEquatable' does not conform to protocol 'Equatable'}}
// expected-note @-2 {{requirement specified as 'NotEquatable' : 'Equatable'}}
// expected-note@-3{{requirement from conditional conformance of '[NotEquatable]' to 'Equatable'}}
}