Renaming hash to hashValue in the overlay would have worked before, but given that 4.2 emits the warning forcing people to migrate to hash, renaming it now would just add to the confusion. 
There are two deeper issues I suggest we should talk about.
- In Swift 4.2+, types should conform to
Hashable by implementing hash(into:), not hashValue. This is not currently possible for NSObject subclasses.
- There is a related, possibly even more serious confusion between
isEqual(_:) and == that remains unresolved.
Consider the code below. Similar code anecdotally exists in some production codebases today.
class Foo: NSObject {
var i: Int
init(_ value: Int) {
self.i = value
super.init()
}
override var hashValue: Int { // THIS IS BROKEN, DO NOT USE
return i.hashValue
}
static func ==(left: HashClass, right: HashClass) -> Bool { // THIS IS BROKEN, DO NOT USE
return left.i == right.i
}
}
First, let's recap the problem with that hashValue override: while it works okay in Swift, it does not get picked up by Objective-C code, which calls hash. Foo leaves hash with its original NSObject definition, which is based on object identity.
As we've seen, the Swift 4.2 compiler emits the following warning for this: (line break added for clarity)
warning: override of 'NSObject.hashValue' is deprecated;
override 'NSObject.hash' to get consistent hashing behavior
This warning spells out the correct solution, hopefully resolving the confusion. (We expect to turn this into a hard error by 5.0.) Sadly the warning doesn't come with a fix-it yet. Adding one would make a good starter bug.
Now let's turn to the == implementation, which is even more problematic. Never mind Objective-C compatibility -- it doesn't even work in Swift!
Foo(42) == Foo(42) // false
This is because it's not Foo that implements Equatable, but rather it's NSObject. And NSObject's definition of == is non-overridable by definition -- you cannot override operators in Swift.
The == above just sits there and confuses anyone reading the code. It looks like a valid definition, but it actually does nothing. I think it may be a good idea to warn about such decoy operator definitions in any context.
(Frustrating note: NSObject also has an isEqual(to:) method, which is confusingly similar to isEqual(_:), but it is a different thing. Mistaking one for the other can lead to long debugging sessions.)
I'm not yet sure how we can enable hash(into:) overrides on NSObject subclasses. It would be possible to do it by adding extra complications to the compiler's Hashable synthesis:
extension NSObject {
open func hash(into hasher: inout Hasher) {
hasher.combine(self.hash)
}
}
class Foo: NSObject {
let i: Int = 0
override func hash(into hasher: inout Hasher) { // Supplied manually
hasher.combine(i)
}
override var hash: Int { // Automatically force-synthesized
var hasher = Hasher()
hasher.combine(self)
return hasher.finalize()
}
override func isEqual(_ other: Any) -> Bool {
return i == (other as? Foo)?.i
}
}
However, this is probably unwise -- Hashable synthesis is already far too complicated, and this scheme could cause problems for subclasses of Swift classes defined in Objective-C code.