Skip to content

Commit 6241356

Browse files
authored
Merge pull request #21057 from lorentey/actually-deprecate-hashValue
[Sema] Emit a deprecation warning if a Hashable type only implements hashValue
2 parents fad03f6 + 646849e commit 6241356

File tree

4 files changed

+67
-3
lines changed

4 files changed

+67
-3
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4220,6 +4220,10 @@ WARNING(non_exhaustive_switch_warn,none, "switch must be exhaustive", ())
42204220
WARNING(override_nsobject_hashvalue,none,
42214221
"override of 'NSObject.hashValue' is deprecated; "
42224222
"override 'NSObject.hash' to get consistent hashing behavior", ())
4223+
WARNING(hashvalue_implementation,none,
4224+
"'Hashable.hashValue' is deprecated as a protocol requirement; "
4225+
"conform type %0 to 'Hashable' by implementing 'hash(into:)' instead",
4226+
(Type))
42234227

42244228
#ifndef DIAG_NO_UNDEF
42254229
# if defined(DIAG)

lib/Sema/DerivedConformanceEquatableHashable.cpp

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1176,7 +1176,7 @@ ValueDecl *DerivedConformance::deriveHashable(ValueDecl *requirement) {
11761176
// The hashValue failure will produce a diagnostic elsewhere.
11771177
return nullptr;
11781178
}
1179-
if (hashValueDecl && hashValueDecl->isImplicit()) {
1179+
if (hashValueDecl->isImplicit()) {
11801180
// Neither hashValue nor hash(into:) is explicitly defined; we need to do
11811181
// a full Hashable derivation.
11821182

@@ -1210,8 +1210,11 @@ ValueDecl *DerivedConformance::deriveHashable(ValueDecl *requirement) {
12101210
llvm_unreachable("Attempt to derive Hashable for a type other "
12111211
"than a struct or enum");
12121212
} else {
1213-
// We can always derive hash(into:) if hashValue has an explicit
1214-
// implementation.
1213+
// hashValue has an explicit implementation, but hash(into:) doesn't.
1214+
// Emit a deprecation warning, then derive hash(into:) in terms of
1215+
// hashValue.
1216+
TC.diagnose(hashValueDecl->getLoc(), diag::hashvalue_implementation,
1217+
Nominal->getDeclaredType());
12151218
return deriveHashable_hashInto(*this,
12161219
&deriveBodyHashable_compat_hashInto);
12171220
}

stdlib/public/core/Hashable.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,9 @@ public protocol Hashable : Equatable {
106106
///
107107
/// Hash values are not guaranteed to be equal across different executions of
108108
/// your program. Do not save hash values to use during a future execution.
109+
///
110+
/// - Important: `hashValue` is deprecated as a `Hashable` requirement. To
111+
/// conform to `Hashable`, implement the `hash(into:)` requirement instead.
109112
var hashValue: Int { get }
110113

111114
/// Hashes the essential components of this value by feeding them into the

test/Sema/struct_equatable_hashable.swift

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,60 @@ protocol ImplierMain: Equatable {}
261261
struct ImpliedMain: ImplierMain {}
262262
extension ImpliedOther: ImplierMain {}
263263

264+
265+
// Hashable conformances that rely on a manual implementation of `hashValue`
266+
// should produce a deprecation warning.
267+
struct OldSchoolStruct: Hashable {
268+
static func ==(left: OldSchoolStruct, right: OldSchoolStruct) -> Bool {
269+
return true
270+
}
271+
var hashValue: Int { return 42 }
272+
// expected-warning@-1{{'Hashable.hashValue' is deprecated as a protocol requirement; conform type 'OldSchoolStruct' to 'Hashable' by implementing 'hash(into:)' instead}}
273+
}
274+
enum OldSchoolEnum: Hashable {
275+
case foo
276+
case bar
277+
278+
static func ==(left: OldSchoolEnum, right: OldSchoolEnum) -> Bool {
279+
return true
280+
}
281+
var hashValue: Int { return 23 }
282+
// expected-warning@-1{{'Hashable.hashValue' is deprecated as a protocol requirement; conform type 'OldSchoolEnum' to 'Hashable' by implementing 'hash(into:)' instead}}
283+
}
284+
class OldSchoolClass: Hashable {
285+
static func ==(left: OldSchoolClass, right: OldSchoolClass) -> Bool {
286+
return true
287+
}
288+
var hashValue: Int { return -9000 }
289+
// expected-warning@-1{{'Hashable.hashValue' is deprecated as a protocol requirement; conform type 'OldSchoolClass' to 'Hashable' by implementing 'hash(into:)' instead}}
290+
}
291+
292+
// However, it's okay to implement `hashValue` as long as `hash(into:)` is also
293+
// provided.
294+
struct MixedStruct: Hashable {
295+
static func ==(left: MixedStruct, right: MixedStruct) -> Bool {
296+
return true
297+
}
298+
func hash(into hasher: inout Hasher) {}
299+
var hashValue: Int { return 42 }
300+
}
301+
enum MixedEnum: Hashable {
302+
case foo
303+
case bar
304+
static func ==(left: MixedEnum, right: MixedEnum) -> Bool {
305+
return true
306+
}
307+
func hash(into hasher: inout Hasher) {}
308+
var hashValue: Int { return 23 }
309+
}
310+
class MixedClass: Hashable {
311+
static func ==(left: MixedClass, right: MixedClass) -> Bool {
312+
return true
313+
}
314+
func hash(into hasher: inout Hasher) {}
315+
var hashValue: Int { return -9000 }
316+
}
317+
264318
// FIXME: Remove -verify-ignore-unknown.
265319
// <unknown>:0: error: unexpected error produced: invalid redeclaration of 'hashValue'
266320
// <unknown>:0: error: unexpected note produced: candidate has non-matching type '(Foo, Foo) -> Bool'

0 commit comments

Comments
 (0)