Skip to content

[WIP][Sema][stdlib] Emit a deprecation warning when a Hashable type only implements hashValue #20685

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

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
8dfcd9c
[Sema] Emit a deprecation warning when hashValue is provided by hash(…
lorentey Nov 20, 2018
5cb3ba6
[ObjectiveC] Selector.hash(into:): Add explicit definition
lorentey Nov 20, 2018
70446a1
[CoreGraphics] CGFloat.hash(into:): Add explicit definition
lorentey Nov 20, 2018
ddeda9e
[Foundation] Provide “straightforward” explicit implementations of ha…
lorentey Nov 20, 2018
9d38b23
[Foundation] AffineTransform.hash(into:): Add an explicit implementation
lorentey Nov 20, 2018
4f9bca1
[Foundation] Data: Add explicit hash(into:) implementation, hashing a…
lorentey Nov 20, 2018
c3f8d09
[Foundation] Date: Add explicit hash(into:) implementation
lorentey Nov 20, 2018
dd48aad
[Foundation] DateInterval: Add explicit hash(into:) implementation
lorentey Nov 20, 2018
ea10a31
[Foundation] Decimal: Add explicit hash(into:) implementation
lorentey Nov 20, 2018
c5bd0ab
[Foundation] IndexPath: Add explicit hash(into:) definition, hashing …
lorentey Nov 20, 2018
e8d9ca6
[Foundation] NSRange: Add explicit hash(into:) definition
lorentey Nov 20, 2018
c7e2da4
[Foundation] UUID: Add an explicit hash(into:) definition
lorentey Nov 20, 2018
40c6b62
[Foundation] Measurement: Add a correct hash(into:) implementation
lorentey Nov 20, 2018
74b80a6
[test] StdlibUnittest: Add missing hash(into:) implementations
lorentey Nov 21, 2018
44a0ee7
[benchmark] DictTest4Legacy: Add dummy hash(into:)
lorentey Nov 21, 2018
14f9626
[SourceKit] SourceKitdUID: Implement hash(into:) rather than hashValue
lorentey Nov 21, 2018
9bc479a
[test] Modernize hashing throughout the test suite
lorentey Nov 21, 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
4 changes: 4 additions & 0 deletions benchmark/single-source/DictTest4Legacy.swift
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ struct LargeKey: Hashable {
return hash
}

func hash(into hasher: inout Hasher) {
hasher.combine(self.hashValue)
}

init(_ value: Int) {
self.i = value
self.j = 2 * value
Expand Down
4 changes: 4 additions & 0 deletions include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -4260,6 +4260,10 @@ WARNING(non_exhaustive_switch_warn,none, "switch must be exhaustive", ())
WARNING(override_nsobject_hashvalue,none,
"override of 'NSObject.hashValue' is deprecated; "
"override 'NSObject.hash' to get consistent hashing behavior", ())
WARNING(hashvalue_implementation,none,
"'Hashable.hashValue' is deprecated as a protocol requirement; "
"conform type %0 to 'Hashable' by implementing 'hash(into:)' instead",
(Type))

#ifndef DIAG_NO_UNDEF
# if defined(DIAG)
Expand Down
9 changes: 6 additions & 3 deletions lib/Sema/DerivedConformanceEquatableHashable.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1175,7 +1175,7 @@ ValueDecl *DerivedConformance::deriveHashable(ValueDecl *requirement) {
// The hashValue failure will produce a diagnostic elsewhere.
return nullptr;
}
if (hashValueDecl && hashValueDecl->isImplicit()) {
if (hashValueDecl->isImplicit()) {
// Neither hashValue nor hash(into:) is explicitly defined; we need to do
// a full Hashable derivation.

Expand Down Expand Up @@ -1209,8 +1209,11 @@ ValueDecl *DerivedConformance::deriveHashable(ValueDecl *requirement) {
llvm_unreachable("Attempt to derive Hashable for a type other "
"than a struct or enum");
} else {
// We can always derive hash(into:) if hashValue has an explicit
// implementation.
// hashValue has an explicit implementation, but hash(into:) doesn't.
// Emit a deprecation warning, then derive hash(into:) in terms of
// hashValue.
TC.diagnose(hashValueDecl->getLoc(), diag::hashvalue_implementation,
Nominal->getDeclaredType());
return deriveHashable_hashInto(*this,
&deriveBodyHashable_compat_hashInto);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -305,8 +305,8 @@ public struct _CollectionState : Equatable, Hashable {
}
}

public var hashValue: Int {
return _id.hashValue
public func hash(into hasher: inout Hasher) {
hasher.combine(_id)
}
}

Expand Down
39 changes: 4 additions & 35 deletions stdlib/private/StdlibUnicodeUnittest/Collation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -181,42 +181,11 @@ let ducetExtractData: [CollationTableEntry] = [
CollationTableEntry([0xE01EF], [0x0000_0000_0000], "VARIATION SELECTOR-256"),
]

public struct HashableArray<Element : Hashable> : Hashable {
internal var _elements: [Element]

public init(_ elements: [Element]) {
_elements = elements
}

public var hashValue: Int {
// FIXME: this is a bad approach to combining hash values.
var result = 0
for x in _elements {
result ^= x.hashValue
result = result &* 997
}
return result
}
}

public func == <Element>(
lhs: HashableArray<Element>,
rhs: HashableArray<Element>
) -> Bool {
return lhs._elements.elementsEqual(rhs._elements)
}

extension HashableArray : ExpressibleByArrayLiteral {
public init(arrayLiteral elements: Element...) {
self._elements = elements
}
}

let ducetExtract: [HashableArray<Unicode.Scalar> : CollationTableEntry] = {
let ducetExtract: [[Unicode.Scalar]: CollationTableEntry] = {
() in
var result: [HashableArray<Unicode.Scalar> : CollationTableEntry] = [:]
var result: [[Unicode.Scalar]: CollationTableEntry] = [:]
for entry in ducetExtractData {
result[HashableArray(entry.scalars)] = entry
result[entry.scalars] = entry
}
return result
}()
Expand All @@ -232,7 +201,7 @@ extension String {
internal var _collationElements: [UInt64] {
var result: [UInt64] = []
for us in self.unicodeScalars {
let scalars: HashableArray<Unicode.Scalar> = [us]
let scalars: [Unicode.Scalar] = [us]
let collationElements = ducetExtract[scalars]!.collationElements
if collationElements[0] != 0 {
result += collationElements
Expand Down
3 changes: 3 additions & 0 deletions stdlib/private/StdlibUnittest/LifetimeTracked.swift
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ extension LifetimeTracked : Hashable {
public var hashValue: Int {
return value
}
public func hash(into hasher: inout Hasher) {
hasher.combine(value)
}
}

extension LifetimeTracked : Strideable {
Expand Down
12 changes: 9 additions & 3 deletions stdlib/private/StdlibUnittest/StdlibCoreExtras.swift
Original file line number Diff line number Diff line change
Expand Up @@ -110,14 +110,20 @@ public func <=> <T: Comparable>(lhs: T, rhs: T) -> ExpectedComparisonResult {
}

public struct TypeIdentifier : Hashable, Comparable {
public var value: Any.Type

public init(_ value: Any.Type) {
self.value = value
}

public var hashValue: Int { return objectID.hashValue }
public var value: Any.Type

internal var objectID : ObjectIdentifier { return ObjectIdentifier(value) }
public func hash(into hasher: inout Hasher) {
hasher.combine(objectID)
}

internal var objectID : ObjectIdentifier {
return ObjectIdentifier(value)
}
}

public func < (lhs: TypeIdentifier, rhs: TypeIdentifier) -> Bool {
Expand Down
4 changes: 4 additions & 0 deletions stdlib/private/StdlibUnittest/StringConvertible.swift
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,10 @@ public struct CustomPrintableValue
return value.hashValue
}

public func hash(into hasher: inout Hasher) {
hasher.combine(value)
}

public typealias Stride = Int

public func distance(to other: CustomPrintableValue) -> Stride {
Expand Down
10 changes: 10 additions & 0 deletions stdlib/public/SDK/CoreGraphics/CGFloat.swift.gyb
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,16 @@ extension CGFloat : Hashable {
public var hashValue: Int {
return native.hashValue
}

/// Hashes the essential components of this value by feeding them into the
/// given hasher.
///
/// - Parameter hasher: The hasher to use when combining the components
/// of this instance.
@inlinable @_transparent
public func hash(into hasher: inout Hasher) {
hasher.combine(native)
}
}

% for dst_ty in all_integer_types(word_bits):
Expand Down
11 changes: 10 additions & 1 deletion stdlib/public/SDK/Foundation/AffineTransform.swift
Original file line number Diff line number Diff line change
Expand Up @@ -276,9 +276,18 @@ public struct AffineTransform : ReferenceConvertible, Hashable, CustomStringConv
return newSize
}

public var hashValue : Int {
public var hashValue : Int { // FIXME(hashValue): Remove
return Int(m11 + m12 + m21 + m22 + tX + tY)
}

public func hash(into hasher: inout Hasher) {
hasher.combine(m11)
hasher.combine(m12)
hasher.combine(m21)
hasher.combine(m22)
hasher.combine(tX)
hasher.combine(tY)
}

public var description: String {
return "{m11:\(m11), m12:\(m12), m21:\(m21), m22:\(m22), tX:\(tX), tY:\(tY)}"
Expand Down
11 changes: 10 additions & 1 deletion stdlib/public/SDK/Foundation/Calendar.swift
Original file line number Diff line number Diff line change
Expand Up @@ -899,14 +899,23 @@ public struct Calendar : Hashable, Equatable, ReferenceConvertible, _MutableBoxi

// MARK: -

public var hashValue : Int {
public var hashValue : Int { // FIXME(hashValue): Remove
// We implement hash ourselves, because we need to make sure autoupdating calendars have the same hash
if _autoupdating {
return 1
} else {
return _handle.map { $0.hash }
}
}

public func hash(into hasher: inout Hasher) {
// We need to make sure autoupdating calendars have the same hash
if _autoupdating {
hasher.combine(1)
} else {
hasher.combine(_handle._uncopiedReference())
}
}

// MARK: -
// MARK: Conversion Functions
Expand Down
19 changes: 16 additions & 3 deletions stdlib/public/SDK/Foundation/CharacterSet.swift
Original file line number Diff line number Diff line change
Expand Up @@ -49,15 +49,24 @@ fileprivate final class _CharacterSetStorage : Hashable {

// MARK: -

fileprivate var hashValue : Int {
fileprivate var hashValue : Int { // FIXME(hashValue): Remove
switch _backing {
case .immutable(let cs):
return Int(CFHash(cs))
case .mutable(let cs):
return Int(CFHash(cs))
}
}


fileprivate func hash(into hasher: inout Hasher) {
switch _backing {
case .immutable(let cs):
hasher.combine(CFHash(cs))
case .mutable(let cs):
hasher.combine(CFHash(cs))
}
}

fileprivate static func ==(lhs : _CharacterSetStorage, rhs : _CharacterSetStorage) -> Bool {
switch (lhs._backing, rhs._backing) {
case (.immutable(let cs1), .immutable(let cs2)):
Expand Down Expand Up @@ -751,10 +760,14 @@ public struct CharacterSet : ReferenceConvertible, Equatable, Hashable, SetAlgeb

// MARK: -

public var hashValue: Int {
public var hashValue: Int { // FIXME(hashValue): Remove
return _storage.hashValue
}

public func hash(into hasher: inout Hasher) {
hasher.combine(_storage)
}

/// Returns true if the two `CharacterSet`s are equal.
public static func ==(lhs : CharacterSet, rhs: CharacterSet) -> Bool {
return lhs._storage == rhs._storage
Expand Down
10 changes: 8 additions & 2 deletions stdlib/public/SDK/Foundation/Data.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1644,7 +1644,7 @@ public struct Data : ReferenceConvertible, Equatable, Hashable, RandomAccessColl
//

/// The hash value for the data.
public var hashValue: Int {
public var hashValue: Int { // FIXME(hashValue): Remove
var hashValue = 0
let hashRange: Range<Int> = _sliceRange.lowerBound..<Swift.min(_sliceRange.lowerBound + 80, _sliceRange.upperBound)
_withStackOrHeapBuffer(hashRange.count + 1) { buffer in
Expand All @@ -1657,7 +1657,13 @@ public struct Data : ReferenceConvertible, Equatable, Hashable, RandomAccessColl
}
return hashValue
}


public func hash(into hasher: inout Hasher) {
_backing.withUnsafeBytes(in: _sliceRange) { buffer in
hasher.combine(bytes: buffer)
}
}

public func advanced(by amount: Int) -> Data {
_validateIndex(startIndex + amount)
let length = count - amount
Expand Down
8 changes: 6 additions & 2 deletions stdlib/public/SDK/Foundation/Date.swift
Original file line number Diff line number Diff line change
Expand Up @@ -142,14 +142,18 @@ public struct Date : ReferenceConvertible, Comparable, Equatable {
*/
public static let distantPast = Date(timeIntervalSinceReferenceDate: -63114076800.0)

public var hashValue: Int {
public var hashValue: Int { // FIXME(hashValue): Remove
if #available(macOS 10.12, iOS 10.0, *) {
return Int(bitPattern: __CFHashDouble(_time))
} else { // 10.11 and previous behavior fallback; this must allocate a date to reference the hash value and then throw away the reference
return NSDate(timeIntervalSinceReferenceDate: _time).hash
}
}


public func hash(into hasher: inout Hasher) {
hasher.combine(_time)
}

/// Compare two `Date` values.
public func compare(_ other: Date) -> ComparisonResult {
if _time < other.timeIntervalSinceReferenceDate {
Expand Down
8 changes: 6 additions & 2 deletions stdlib/public/SDK/Foundation/DateComponents.swift
Original file line number Diff line number Diff line change
Expand Up @@ -263,10 +263,14 @@ public struct DateComponents : ReferenceConvertible, Hashable, Equatable, _Mutab

// MARK: -

public var hashValue : Int {
public var hashValue : Int { // FIXME(hashValue): Remove
return _handle.map { $0.hash }
}


public func hash(into hasher: inout Hasher) {
hasher.combine(_handle._uncopiedReference())
}

// MARK: - Bridging Helpers

fileprivate init(reference: __shared NSDateComponents) {
Expand Down
9 changes: 7 additions & 2 deletions stdlib/public/SDK/Foundation/DateInterval.swift
Original file line number Diff line number Diff line change
Expand Up @@ -155,15 +155,20 @@ public struct DateInterval : ReferenceConvertible, Comparable, Hashable, Codable
return false
}

public var hashValue: Int {
public var hashValue: Int { // FIXME(hashValue): Remove
var buf: (UInt, UInt) = (UInt(start.timeIntervalSinceReferenceDate), UInt(end.timeIntervalSinceReferenceDate))
return withUnsafeMutablePointer(to: &buf) {
$0.withMemoryRebound(to: UInt8.self, capacity: 2 * MemoryLayout<UInt>.size / MemoryLayout<UInt8>.size) {
return Int(bitPattern: CFHashBytes($0, CFIndex(MemoryLayout<UInt>.size * 2)))
}
}
}


public func hash(into hasher: inout Hasher) {
hasher.combine(start)
hasher.combine(duration)
}

@available(macOS 10.12, iOS 10.0, watchOS 3.0, tvOS 10.0, *)
public static func ==(lhs: DateInterval, rhs: DateInterval) -> Bool {
return lhs.start == rhs.start && lhs.duration == rhs.duration
Expand Down
11 changes: 10 additions & 1 deletion stdlib/public/SDK/Foundation/Decimal.swift
Original file line number Diff line number Diff line change
Expand Up @@ -177,10 +177,19 @@ extension Decimal : Hashable, Comparable {
return _isNegative != 0 ? -d : d
}

public var hashValue: Int {
public var hashValue: Int { // FIXME(hashValue): Remove
return Int(bitPattern: __CFHashDouble(doubleValue))
}

public func hash(into hasher: inout Hasher) {
// FIXME: This is a lossy hash encoding; it may generate collisions.
//
// We should instead feed hasher with the actual components of a
// normalized version of self. Note that any such normalization must
// match the equivalence classes defined by NSDecimalNormalize.
hasher.combine(doubleValue)
}

public static func ==(lhs: Decimal, rhs: Decimal) -> Bool {
var lhsVal = lhs
var rhsVal = rhs
Expand Down
Loading