Skip to content

[stdlib] Make sure _SwiftNewtypeWrapper hashes the same way as its RawValue #15990

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 1 commit into from
Apr 17, 2018
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion stdlib/public/core/NewtypeWrapper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,16 @@
/// attribute.
public protocol _SwiftNewtypeWrapper : RawRepresentable { }

extension _SwiftNewtypeWrapper where Self.RawValue : Hashable {
extension _SwiftNewtypeWrapper where Self: Hashable, Self.RawValue : Hashable {
@inlinable // FIXME(sil-serialize-all)
public var hashValue: Int {
return rawValue.hashValue
}

@inlinable // FIXME(sil-serialize-all)
public func _hash(into hasher: inout _Hasher) {
hasher.append(rawValue)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice catch. Is this something that users are likely to run into, or is it because of the special relationship between newtype'd imports and bridging?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The crash was caused by a particularly weird interplay between _hash(into:), newtype, AnyHashable and bridging.

Luckily, I don't see how it could apply to user code. For example, struct Foo below produces the same hashValue as its label, but it doesn't hash the same way through _hash(into:). This isn't an issue, though, because the only way to put them in the same hash table is through AnyHashable, and since they don't have the same AnyHashable representation, they wouldn't compare equal even if they had matching hashes:

struct Foo: Hashable {
  var label: String
  var hashValue: Int { return label.hashValue }
  static func ==(left: Foo, right: Foo) -> Bool { return left.label == right.label }
}

Foo(label: "foo").hashValue == "foo".hashValue // ==> true
(Foo(label: "foo") as AnyHashable) == ("foo" as AnyHashable) // ==> false
let s: Set<AnyHashable> = [Foo(label: "foo"), "foo"]
s.count // ==> 2

The only visible effect seems to be that Dictionary/Set ordering is different for Foo and String, but hash table ordering is explicitly unstable, so code that depends on that is already broken.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay. Imported newtypes are specifically weird because we need hashing consistency for the newtype, the type it wraps, and potentially the bridged type (SomeNewType/String/NSString), so yeah--unlikely to affect anyone else.

}
}

#if _runtime(_ObjC)
Expand Down