[Accepted] SE-0206: Hashable Enhancements

Thank you for bringing this up; ease of migration is a hugely important issue.

While the compiler would produce a deprecation warning in 4.2 mode, synthesis would continue to work, and no code would need to be actually changed until/unless hashValue is actually removed from the protocol, which is not going to happen soon (if ever).

(It may also be a good idea to defer hashValue's formal deprecation till a future language version. We'd like to encourage people to implement hash(into:), but there may be gentler ways of doing that than formally deprecating hashValue. However, it seems like a bad idea to support both requirements indefinitely -- we do want to get rid of the legacy interface at some point.)

I agree wholeheartedly. It may be helpful to provide it as an interactive fix-it attached to the deprecation warning, though.

The proposal doesn't cover fully automated migration of existing hashValue implementations; it seems Difficult to do that correctly in the most general case (with complications like caching, partial hashing, etc.). However, automatic migration may be possible for the simpler cases.

For example, (in theory at least) the migrator could recognize that the hashValue implementation below calls the getters of all stored properties without touching anything else, and it could replace it with a hash(into:) implementation or remove it altogether:

struct Foo: Hashable {
  var a: Int
  var b: Double

  var hashValue: Int { return a.hashValue ^ 31 &* b.hashValue }
  static func ==(l: Foo, r: Foo) -> Bool { return l.a == r.a && l.b == r.b }
}

(The same goes for the == implementation.) Of course, the "without touching anything else" part may not be easy to formally verify. (The code does call the hashValue properties along with a bunch of Int operations. Do we implement a whitelist?)

This migration would still change the meaning of the code in evil cases like the one below, where a.hashValue is canceled out from the returned hash value:

var hashValue: Int { 
  var hash = 0
  hash ^= a.hashValue
  hash = 31 &* b.hashValue
  return hash
}

I'd argue a human migrator would probably not fair any better, and I'd also argue the original intention behind this code was probably to hash both fields; the second xor was simply lost somewhere along the way.

As an improvement to this, the migrator could recognize partial hashing, and try to do the right thing there, too.