Skip to content

Add default Hashable implementations for RawRepresentable types #20705

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

Merged
merged 3 commits into from
Nov 30, 2018

Conversation

lorentey
Copy link
Member

RawRepresentable currently provides a custom generic definition of == that gets picked up as the Equatable.== implementation whenever a concrete RawRepresentable type conforms to Equatable without providing its own implementation. The custom == compares the rawValue.

Unfortunately, there are no such definitions provided for hashing, so Hashable raw-representable types get compiler-synthesized hashing, which is based directly on the components of the type. For types with a computed rawValue, this can lead to a mismatch between equality and hashing, leading to broken hashes.

Add the missing hashing members as extension methods on RawRepresentable.

This is a surprisingly far-reaching change: it replaces compiler-generated members synthesized directly under the type with stdlib-provided protocol extensions.

rdar://problem/45308741

@lorentey
Copy link
Member Author

@swift-ci test

@lorentey lorentey changed the title Fix default Hashable implementations for RawRepresentable types Add default Hashable implementations for RawRepresentable types Nov 22, 2018
@lorentey
Copy link
Member Author

@nkcsgexi This wasn't flagged by the api-digester tests as an ABI-impacting change, but I think it is one. It adds protocol extensions that prevent the compiler from synthesizing Hashable members directly under conforming types.

@slavapestov
Copy link
Contributor

@lorentey @jrose-apple The ABI digester only flags changes which would break backward compatibility with existing code. It does not flag changes that prevent backward deployment.

@slavapestov
Copy link
Contributor

slavapestov commented Nov 26, 2018

Actually, I see what you mean. A downstream dependency could call a synthesized member, and it will break once a new build removes the synthesized member. However, the ABI digester won't flag that.

@jrose-apple We already had a similar issue with synthesized == because of @_implements. Maybe we can re-consider calling synthesized members via witness method dispatch?

@lorentey @nkcsgexi I would have expected the ABI digester to flag the removal of the synthesized members themselves. Does it skip implicit decls for some reason? Or are there no types with synthesized hashValue in the stdlib?

@lorentey
Copy link
Member Author

Note that the checker correctly flagged FloatingPointSign and CanonicalCombiningClass when their synthesized members were removed by the RawRepresentable change.

I supplied explicit definitions in 545fa5e. (Then I promptly forgot I did so.)

@lorentey lorentey merged commit e94361e into swiftlang:master Nov 30, 2018
@lorentey lorentey deleted the rawrepresentable-hashing branch November 30, 2018 14:24
@nkcsgexi
Copy link
Contributor

@lorentey , If i understand the problem correctly, you're suggesting we should flag adding decls as potential ABI breakages if they can prevent compiler's synthesizing members for types in down-streaming modules.

This can be a problem when we deploy ABI checker for module A but not for module B, right?

@lorentey
Copy link
Member Author

@nkcsgexi That’s exactly right! If module B isn’t covered by the ABI checker (or if a check is only triggered when there is a change in module B itself), then these additions may cause silent breakage.

@nkcsgexi
Copy link
Contributor

Thanks for clarification! The good news is we have deployed ABI checker for both stdlib and all SDK overlays, and they happen to be the entire set of modules whose ABI we care about now. So i don't think in practice this will be a problem.

@akyrtzi
Copy link
Contributor

akyrtzi commented Nov 30, 2018

@nkcsgexi That’s exactly right! If module B isn’t covered by the ABI checker (or if a check is only triggered when there is a change in module B itself), then these additions may cause silent breakage.

I'd argue that this is a problem for module B to deal with, we mainly need to be able to flag it for module B, not flag it for module A.

lorentey added a commit to lorentey/swift that referenced this pull request Sep 3, 2021
…out implementing `hashValue`

Before this change, `RawRepresentable`'s custom `hashValue` implementation used to forward to `rawValue.hashValue`. (See swiftlang#20705.) This sort of made sense at the time (`hash(into:)` was very new and `hashValue` was still in heavy use): it allowed raw representable values to return the exact same hash value as their `rawValue`, which some code used to (mistakenly) rely on. The big drawback of this is that to customize the Hashable implementation of a RawRepresentable type, people need to implement both `hashValue` and `hash(into:)` -- the former does not otherwise pick up customizations to the latter.

This change makes the default `RawRepresentable.hashValue` implementation call `self.hash(into:)`, just like it would on non-RawRepresentable types.

rdar://82651116
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants