Skip to content
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

[WIP] Conditionally conform stdlib types to Hashable #14527

Closed
wants to merge 22 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
d80bea6
[WIP] Conditional conformance to Hashable for Optional, Dictionary an…
mortenbekditlevsen Feb 10, 2018
86ffc42
Silence warning and break to 80 characters
xwu Feb 10, 2018
bd88afc
[Foundation] NSArray, NSDictionary: Add custom AnyHashable representa…
lorentey Feb 10, 2018
8d146e9
Merge pull request #14528 from xwu/conditional-hashable-xwu-patch
xwu Feb 10, 2018
6e6a16f
[stdlib] Describe availability of new conformances and members
lorentey Feb 11, 2018
ef9319e
[ClangImporter] Don’t import Array/Dictionary as such in Hashable con…
lorentey Feb 11, 2018
1f39896
[stdlib] Add availability annotation to other new conformances and me…
xwu Feb 11, 2018
0582667
[stdlib] ClosedRange: Describe availability of Hashable conformance
lorentey Feb 11, 2018
b89a9ff
[docs] Update CHANGELOG.md for conditional conformance
xwu Feb 11, 2018
d03f0b3
Merge branch 'conditional-hashable' of github.com:apple/swift into co…
xwu Feb 11, 2018
b0f38e0
Use @_implements to avoid source breakage in conditional conformances
xwu Feb 11, 2018
b5a1d4b
Merge pull request #14536 from xwu/conditional-hashable-at-implements
xwu Feb 11, 2018
9b5c23c
Revert "Merge pull request #14536 from xwu/conditional-hashable-at-im…
xwu Feb 11, 2018
6120d56
[stdlib] Add @_inlineable attribute with FIXME to hashValue implement…
xwu Feb 11, 2018
3efd628
[stdlib] Remove unnecessary doc comments from hashValue implementations
xwu Feb 11, 2018
a9b1e9a
[stdlib] Remove unnecessary type annotations in hashValue
xwu Feb 11, 2018
0e0dbc0
[stdlib] Mix lowerBound a little in Range.hashValue and ClosedRange.h…
xwu Feb 11, 2018
427d771
[stdlib] Remove unnecessary attributes and clarify comments
xwu Feb 11, 2018
08302c6
[stdlib] Restore an attribute for now
xwu Feb 11, 2018
3d3ed0a
[stdlib] Update FlattenCollection.Index hashValue
xwu Feb 11, 2018
7b3bc6c
[stdlib] Attempt alternative design for conditional conformance to Eq…
xwu Feb 11, 2018
81d3aac
[ClangImporter] Break to 80 columns (NFC)
lorentey Feb 12, 2018
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
21 changes: 15 additions & 6 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,15 @@ Swift 5.0
Swift 4.1
---------

* [SE-0143][]

The standard library types `Optional`, `Array`, `ArraySlice`,
`ContiguousArray`, `Dictionary`, `DictionaryLiteral`, `Range`, `ClosedRange`,
`AnyCollection`, and `CollectionOfOne` now conform to the `Hashable` protocol
when their element or bound types (as the case may be) conform to `Hashable`.
This makes synthesized `Hashable` implementations available for types that
include stored properties of these types.

* [SE-0189][]

If an initializer is declared in a different module from a struct, it must
Expand Down Expand Up @@ -74,12 +83,12 @@ Swift 4.1
s[keyPath: p] // "H"
```

* [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-0143][] The standard library types `Optional`, `Array`, `ArraySlice`,
`ContiguousArray`, 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
Expand Down
10 changes: 9 additions & 1 deletion lib/ClangImporter/ImportType.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -998,7 +998,15 @@ namespace {
if (unboundDecl == Impl.SwiftContext.getDictionaryDecl() ||
unboundDecl == Impl.SwiftContext.getSetDecl()) {
auto &keyType = importedTypeArgs[0];
if (!Impl.matchesHashableBound(keyType)) {
auto keyStructDecl = keyType->getStructOrBoundGenericStruct();
if (!Impl.matchesHashableBound(keyType) ||
// Dictionary and Array conditionally conform to Hashable,
// but the conformance doesn't necessarily apply with the
// imported versions of their type arguments.
// FIXME: Import their non-Hashable type parameters as
// AnyHashable in this context.
keyStructDecl == Impl.SwiftContext.getDictionaryDecl() ||
keyStructDecl == Impl.SwiftContext.getArrayDecl()) {
if (auto anyHashable = Impl.SwiftContext.getAnyHashableDecl())
keyType = anyHashable->getDeclaredType();
else
Expand Down
8 changes: 8 additions & 0 deletions stdlib/public/SDK/Foundation/NSArray.swift
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,14 @@ extension Array : _ObjectiveCBridgeable {
}
}

extension NSArray : _HasCustomAnyHashableRepresentation {
// Must be @nonobjc to avoid infinite recursion during bridging
@nonobjc
public func _toCustomAnyHashable() -> AnyHashable? {
return AnyHashable(self as! Array<AnyHashable>)
}
}

extension NSArray : Sequence {
/// Return an *iterator* over the elements of this *sequence*.
///
Expand Down
8 changes: 8 additions & 0 deletions stdlib/public/SDK/Foundation/NSDictionary.swift
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,14 @@ extension Dictionary : _ObjectiveCBridgeable {
}
}

extension NSDictionary : _HasCustomAnyHashableRepresentation {
// Must be @nonobjc to avoid infinite recursion during bridging
@nonobjc
public func _toCustomAnyHashable() -> AnyHashable? {
return AnyHashable(self as! Dictionary<AnyHashable, AnyHashable>)
}
}

extension NSDictionary : Sequence {
// FIXME: A class because we can't pass a struct with class fields through an
// [objc] interface without prematurely destroying the references.
Expand Down
12 changes: 12 additions & 0 deletions stdlib/public/core/Arrays.swift.gyb
Original file line number Diff line number Diff line change
Expand Up @@ -2250,6 +2250,18 @@ extension ${Self} : Equatable where Element : Equatable {
}
}

extension ${Self} : Hashable where Element : Hashable {
@_inlineable // FIXME(sil-serialize-all)
public var hashValue: Int {
// FIXME(ABI)#177: <rdar://problem/18915294> Issue applies to Array too
var result = 0
for element in self {
result = _combineHashValues(result, element.hashValue)
}
return result
}
}

extension ${Self} {
/// Calls the given closure with a pointer to the underlying bytes of the
/// array's mutable contiguous storage.
Expand Down
17 changes: 14 additions & 3 deletions stdlib/public/core/ClosedRange.swift
Original file line number Diff line number Diff line change
Expand Up @@ -167,8 +167,9 @@ extension ClosedRange.Index : Comparable {
}
}

extension ClosedRange.Index: Hashable
where Bound: Strideable, Bound.Stride: SignedInteger, Bound: Hashable {
extension ClosedRange.Index: Hashable
where Bound: Strideable, Bound.Stride: SignedInteger, Bound: Hashable {
@_inlineable // FIXME(sil-serialize-all)
public var hashValue: Int {
switch self {
case .inRange(let value):
Expand Down Expand Up @@ -376,9 +377,19 @@ extension ClosedRange: Equatable {
}
}

extension ClosedRange : Hashable where Bound : Hashable {
@_inlineable // FIXME(sil-serialize-all)
public var hashValue: Int {
var result = 0
result = _combineHashValues(result, lowerBound.hashValue)
result = _combineHashValues(result, upperBound.hashValue)
return result
}
}

extension ClosedRange : CustomStringConvertible {
/// A textual representation of the range.
@_inlineable // FIXME(sil-serialize-all)...\(
@_inlineable // FIXME(sil-serialize-all)
public var description: String {
return "\(lowerBound)...\(upperBound)"
}
Expand Down
14 changes: 14 additions & 0 deletions stdlib/public/core/CollectionOfOne.swift
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,20 @@ extension CollectionOfOne: RandomAccessCollection, MutableCollection {
}
}

extension CollectionOfOne : Equatable, _Equatable where Element : Equatable {
@_inlineable // FIXME(sil-serialize-all)
public func _isEqual(to other: CollectionOfOne) -> Bool {
return _element == other._element
}
}

extension CollectionOfOne : Hashable where Element : Hashable {
@_inlineable // FIXME(sil-serialize-all)
public var hashValue: Int {
return _element.hashValue
}
}

extension CollectionOfOne : CustomDebugStringConvertible {
/// A textual representation of `self`, suitable for debugging.
@_inlineable // FIXME(sil-serialize-all)
Expand Down
1 change: 1 addition & 0 deletions stdlib/public/core/DropWhile.swift.gyb
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ public struct LazyDropWhileIndex<Base : Collection> : Comparable {
}

extension LazyDropWhileIndex : Hashable where Base.Index : Hashable {
@_inlineable // FIXME(sil-serialize-all)
public var hashValue: Int {
return base.hashValue
}
Expand Down
7 changes: 7 additions & 0 deletions stdlib/public/core/EmptyCollection.swift
Original file line number Diff line number Diff line change
Expand Up @@ -173,5 +173,12 @@ extension EmptyCollection : Equatable {
}
}

extension EmptyCollection : Hashable {
@_inlineable // FIXME(sil-serialize-all)
public var hashValue: Int {
return 0
}
}

// @available(*, deprecated, renamed: "EmptyCollection.Iterator")
public typealias EmptyIterator<T> = EmptyCollection<T>.Iterator
40 changes: 40 additions & 0 deletions stdlib/public/core/Equatable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,46 @@ extension Equatable {
}
}

// FIXME(ABI): Remove this protocol when `@_implements` is supported in
// conditional conformances.
//
// Ordinarily, we don't have to worry about adding new methods to types in the
// standard library. If end users have added identically named methods in their
// own extensions, those will seamlessly shadow our methods without any source
// breakage.
//
// However, if we add new methods that are spelled as _operators_ to types in
// the standard library, then any identically named methods implemented by end
// users will cause ambiguity at the call site. This prevents the addition of
// conditional conformance to `Equatable` to types where users may have already
// defined `==` for themselves.
//
// The underscored attribute `@_implements` was created to work around these
// limitations for derived conformances. However, at present, we can't use the
// same attribute for conditional conformances because the attribute doesn't
// work inside extensions (SR-NNNN).
//
// What _does_ work, however, is a conceptually similar design where we add only
// methods that aren't spelled as operators to the concrete type. Meanwhile, the
// protocol itself would provide a default implementation of the method that
// _is_ spelled as an operator, which in turn calls the non-operator method.
// With such a design, any operator method of the same name defined by the end
// user is selected preferentially over the protocol's default implementation.
//
// Such is the purpose of the following underscored protocol.

public protocol _Equatable: Equatable {
func _isEqual(to other: Self) -> Bool
}

extension _Equatable {
@_inlineable // FIXME(sil-serialize-all)
@_transparent
public static func == (lhs: Self, rhs: Self) -> Bool {
return lhs._isEqual(to: rhs)
}
}

//===----------------------------------------------------------------------===//
// Reference comparison
//===----------------------------------------------------------------------===//
Expand Down
20 changes: 20 additions & 0 deletions stdlib/public/core/ExistentialCollection.swift.gyb
Original file line number Diff line number Diff line change
Expand Up @@ -1243,3 +1243,23 @@ extension ${Self}: _AnyCollectionProtocol {
}
}
% end

extension AnyCollection : Equatable, _Equatable where Element : Equatable {
@_inlineable // FIXME(sil-serialize-all)
public func _isEqual(to other: AnyCollection) -> Bool {
if count != other.count { return false }
return elementsEqual(other)
}
}

extension AnyCollection : Hashable where Element : Hashable {
@_inlineable // FIXME(sil-serialize-all)
public var hashValue: Int {
// FIXME(ABI)#177: <rdar://problem/18915294> Cache AnyCollection<T> hashValue
var result = 0
for element in self {
result = _combineHashValues(result, element.hashValue)
}
return result
}
}
6 changes: 5 additions & 1 deletion stdlib/public/core/Flatten.swift
Original file line number Diff line number Diff line change
Expand Up @@ -232,8 +232,12 @@ extension FlattenCollection.Index : Comparable {

extension FlattenCollection.Index : Hashable
where Base.Index : Hashable, Base.Element.Index : Hashable {
@_inlineable // FIXME(sil-serialize-all)
public var hashValue: Int {
return _mixInt(_inner?.hashValue ?? 0) ^ _outer.hashValue
var result = 0
result = _combineHashValues(result, _inner.hashValue)
result = _combineHashValues(result, _outer.hashValue)
return result
}
}

Expand Down
27 changes: 23 additions & 4 deletions stdlib/public/core/HashedCollections.swift.gyb
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,9 @@ import SwiftShims
//
// Dictionary<K,V> (a struct)
// +------------------------------------------------+
// | _VariantDictionaryBuffer<K,V> (an enum) |
// | _VariantDictionaryBuffer<K,V> (an enum) |
// | +--------------------------------------------+ |
// | | [_NativeDictionaryBuffer<K,V> (a struct)] | |
// | | [_NativeDictionaryBuffer<K,V> (a struct)] | |
// | +---|----------------------------------------+ |
// +----/-------------------------------------------+
// /
Expand All @@ -61,9 +61,9 @@ import SwiftShims
//
// Dictionary<K,V> (a struct)
// +----------------------------------------------+
// | _VariantDictionaryBuffer<K,V> (an enum) |
// | _VariantDictionaryBuffer<K,V> (an enum) |
// | +----------------------------------------+ |
// | | [ _CocoaDictionaryBuffer (a struct) ] | |
// | | [ _CocoaDictionaryBuffer (a struct) ] | |
// | +---|------------------------------------+ |
// +-----|----------------------------------------+
// |
Expand Down Expand Up @@ -2786,6 +2786,25 @@ extension Dictionary : Equatable where Value : Equatable {
}
}

extension Dictionary : Hashable where Value : Hashable {
/// The hash value for the dictionary.
///
/// Two dictionaries that are equal will always have equal hash values.
///
/// Hash values are not guaranteed to be equal across different executions of
/// your program. Do not save hash values to use during a future execution.
@_inlineable // FIXME(sil-serialize-all)
public var hashValue: Int {
// FIXME(ABI)#177: <rdar://problem/18915294> Cache Dictionary<T> hashValue
var result = 0
for (k, v) in self {
let combined = _combineHashValues(k.hashValue, v.hashValue)
result ^= _mixInt(combined)
}
return result
}
}

extension Dictionary : CustomStringConvertible, CustomDebugStringConvertible {
@_inlineable // FIXME(sil-serialize-all)
@_versioned // FIXME(sil-serialize-all)
Expand Down
23 changes: 23 additions & 0 deletions stdlib/public/core/Mirror.swift
Original file line number Diff line number Diff line change
Expand Up @@ -905,6 +905,29 @@ extension DictionaryLiteral : RandomAccessCollection {
}
}

extension DictionaryLiteral : Equatable, _Equatable
where Key : Equatable, Value : Equatable {
@_inlineable // FIXME(sil-serialize-all)
public func _isEqual(to other: DictionaryLiteral) -> Bool {
if count != other.count { return false }
return elementsEqual(other, by: ==)
}
}

extension DictionaryLiteral : Hashable where Key : Hashable, Value : Hashable {
@_inlineable // FIXME(sil-serialize-all)
public var hashValue: Int {
// FIXME(ABI)#177: <rdar://problem/18915294> Issue applies to DictionaryLiteral too
var result = 0
for element in self {
let elementHashValue =
_combineHashValues(element.key.hashValue, element.value.hashValue)
result = _combineHashValues(result, elementHashValue)
}
return result
}
}

extension String {
/// Creates a string representing the given value.
///
Expand Down
21 changes: 21 additions & 0 deletions stdlib/public/core/Optional.swift
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,27 @@ extension Optional : Equatable where Wrapped : Equatable {
}
}

extension Optional : Hashable where Wrapped : Hashable {
/// The hash value for the optional instance.
///
/// Two optionals that are equal will always have equal hash values.
///
/// Hash values are not guaranteed to be equal across different executions of
/// your program. Do not save hash values to use during a future execution.
@_inlineable // FIXME(sil-serialize-all)
public var hashValue: Int {
var result: Int
switch self {
case .none:
result = 0
case .some(let wrapped):
result = 1
result = _combineHashValues(result, wrapped.hashValue)
}
return result
}
}

// Enable pattern matching against the nil literal, even if the element type
// isn't equatable.
@_fixed_layout
Expand Down
10 changes: 10 additions & 0 deletions stdlib/public/core/Range.swift
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,16 @@ extension Range: Equatable {
}
}

extension Range : Hashable where Bound : Hashable {
@_inlineable // FIXME(sil-serialize-all)
public var hashValue: Int {
var result = 0
result = _combineHashValues(result, lowerBound.hashValue)
result = _combineHashValues(result, upperBound.hashValue)
return result
}
}

/// A partial half-open interval up to, but not including, an upper bound.
///
/// You create `PartialRangeUpTo` instances by using the prefix half-open range
Expand Down
1 change: 1 addition & 0 deletions stdlib/public/core/Reverse.swift
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,7 @@ extension ReversedCollection.Index: Comparable {
}

extension ReversedCollection.Index: Hashable where Base.Index: Hashable {
@_inlineable // FIXME(sil-serialize-all)
public var hashValue: Int {
return base.hashValue
}
Expand Down
Loading