Skip to content

Commit 6176442

Browse files
committed
[Type checker] Warn about overrides of NSObject.hashValue.
NSObject.hashValue is provided to satisfy the hashValue constraint of the Hashable protocol. However, it is not the correct customization point for interoperating with Objective-C, because Objective-C code will call through the -hash method. Warn about overrides of NSObject.hashValue; users should override NSObject.hash instead. Fixes rdar://problem/42780635. (cherry picked from commit cc4c992)
1 parent 68020bd commit 6176442

File tree

5 files changed

+32
-12
lines changed

5 files changed

+32
-12
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4022,6 +4022,10 @@ NOTE(redundant_particular_literal_case_here,none,
40224022

40234023
WARNING(non_exhaustive_switch_warn,none, "switch must be exhaustive", ())
40244024

4025+
WARNING(override_nsobject_hashvalue,none,
4026+
"override of 'NSObject.hashValue' is deprecated; "
4027+
"override 'NSObject.hash' to get consistent hashing behavior", ())
4028+
40254029
#ifndef DIAG_NO_UNDEF
40264030
# if defined(DIAG)
40274031
# undef DIAG

lib/Sema/TypeCheckDecl.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6119,6 +6119,18 @@ class DeclChecker : public DeclVisitor<DeclChecker> {
61196119
diagnoseOverrideForAvailability(TC, override, base);
61206120
}
61216121

6122+
// Overrides of NSObject.hashValue are deprecated; one should override
6123+
// NSObject.hash instead.
6124+
if (auto baseVar = dyn_cast<VarDecl>(base)) {
6125+
if (auto classDecl =
6126+
baseVar->getDeclContext()->getAsClassOrClassExtensionContext()) {
6127+
if (classDecl->getBaseName().userFacingName() == "NSObject" &&
6128+
baseVar->getBaseName().userFacingName() == "hashValue") {
6129+
override->diagnose(diag::override_nsobject_hashvalue);
6130+
}
6131+
}
6132+
}
6133+
61226134
/// Check attributes associated with the base; some may need to merged with
61236135
/// or checked against attributes in the overriding declaration.
61246136
AttributeOverrideChecker attrChecker(TC, base, override);

test/ClangImporter/objc_override.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,14 @@ class CallbackSubC : CallbackBase {
108108
override func perform(optNonescapingHandler: @escaping () -> Void) {} // expected-error {{method does not override any method from its superclass}}
109109
}
110110

111+
//
112+
class MyHashableNSObject: NSObject {
113+
override var hashValue: Int { // expected-warning{{override of 'NSObject.hashValue' is deprecated}}
114+
return 0
115+
}
116+
}
117+
118+
111119
// FIXME: Remove -verify-ignore-unknown.
112120
// <unknown>:0: error: unexpected note produced: overridden declaration is here
113121
// <unknown>:0: error: unexpected note produced: setter for 'boolProperty' declared here

test/Inputs/clang-importer-sdk/swift-modules/Foundation.swift

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,14 @@
22
@_exported import CoreGraphics
33
@_exported import Foundation
44

5-
public func == (lhs: NSObject, rhs: NSObject) -> Bool {
6-
return lhs.isEqual(rhs)
5+
extension NSObject : Equatable, Hashable {
6+
@objc open var hashValue: Int {
7+
return hash
8+
}
9+
10+
public static func == (lhs: NSObject, rhs: NSObject) -> Bool {
11+
return lhs.isEqual(rhs)
12+
}
713
}
814

915
public let NSUTF8StringEncoding: UInt = 8

test/Inputs/clang-importer-sdk/swift-modules/ObjectiveC.swift

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -86,13 +86,3 @@ public func _convertObjCBoolToBool(_ x: ObjCBool) -> Bool {
8686
public func ~=(x: NSObject, y: NSObject) -> Bool {
8787
return true
8888
}
89-
90-
extension NSObject : Equatable, Hashable {
91-
public var hashValue: Int {
92-
return hash
93-
}
94-
}
95-
96-
public func == (lhs: NSObject, rhs: NSObject) -> Bool {
97-
return lhs.isEqual(rhs)
98-
}

0 commit comments

Comments
 (0)