Skip to content

Commit bf872ec

Browse files
committed
[stdlib][SE-0206] Use distinct hash encodings for standard integer types
Fix Hashable conformance of standard integer types so that the number of bits they feed into hasher is exactly Self.bitWidth. This was intended to be part of SE-0206. However, it would have introduced additional issues with AnyHashable. The custom AnyHashable representations introduced in the previous commit unify hashing for numeric types, eliminating the problem.
1 parent ff91f36 commit bf872ec

File tree

2 files changed

+19
-32
lines changed

2 files changed

+19
-32
lines changed

stdlib/public/core/Integers.swift.gyb

Lines changed: 8 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -3885,37 +3885,20 @@ extension ${Self} : Hashable {
38853885
/// of this instance.
38863886
@inlinable
38873887
public func hash(into hasher: inout Hasher) {
3888-
// FIXME(hasher): To correctly bridge `Set`s/`Dictionary`s containing
3889-
// `AnyHashable`-boxed integers, all integer values are currently required
3890-
// to hash exactly the same way as the corresponding (U)Int64 value. To fix
3891-
// this, we should introduce a custom AnyHashable box for integer values
3892-
// that sign-extends values to 64 bits.
3893-
% if bits <= word_bits:
3894-
hasher._combine(_lowWord)
3895-
% elif bits == 2 * word_bits:
3896-
if let word = ${"" if signed else "U"}Int(exactly: self) {
3897-
hasher._combine(word._lowWord)
3898-
} else {
3899-
hasher._combine(UInt64(_value))
3900-
}
3901-
% else:
3902-
fatalError("Unsupported integer width")
3903-
% end
3888+
hasher._combine(${U}${Self}(_value))
39043889
}
39053890

39063891
@inlinable
39073892
public func _rawHashValue(seed: (UInt64, UInt64)) -> Int {
3908-
// FIXME(hasher): Note that the AnyHashable concern applies here too,
3909-
// because hashValue uses top-level hashing.
3910-
% if bits <= word_bits:
3911-
return Hasher._hash(seed: seed, _lowWord)
3912-
% elif bits == 2 * word_bits:
3913-
if let word = ${"" if signed else "U"}Int(exactly: self) {
3914-
return Hasher._hash(seed: seed, word._lowWord)
3915-
}
3893+
% if bits == 64:
39163894
return Hasher._hash(seed: seed, UInt64(_value))
3895+
% elif bits == word_bits:
3896+
return Hasher._hash(seed: seed, UInt(_value))
39173897
% else:
3918-
fatalError("Unsupported integer width")
3898+
return Hasher._hash(
3899+
seed: seed,
3900+
bytes: UInt64(truncatingIfNeeded: ${U}${Self}(_value)),
3901+
count: ${bits / 8})
39193902
% end
39203903
}
39213904
}

validation-test/stdlib/FixedPoint.swift.gyb

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -233,22 +233,26 @@ hash_value_test_template = gyb.parse_template("hash_value",
233233
% for self_ty in all_integer_types(word_bits):
234234
% Self = self_ty.stdlib_name
235235

236-
FixedPoint.test("${Self}.hashValue") {
236+
FixedPoint.test("${Self}.hash(into:)") {
237237

238238
% for bit_pattern in test_bit_patterns:
239239

240240
do {
241241
% input = prepare_bit_pattern(bit_pattern, self_ty.bits, self_ty.is_signed)
242242
let input = get${Self}(${input})
243-
let output = getInt(input.hashValue)
243+
var hasher = Hasher()
244+
input.hash(into: &hasher)
245+
let output = getInt(hasher.finalize())
244246

245-
var hasher = _SipHash13(_seed: Hasher._seed)
246-
% if prepare_bit_pattern(input, word_bits, self_ty.is_signed) == input:
247-
hasher._combine(UInt(truncatingIfNeeded: ${input} as ${"" if self_ty.is_signed else "U"}Int))
247+
% reference = prepare_bit_pattern(bit_pattern, self_ty.bits, False)
248+
% if self_ty.bits == 64:
249+
let expected = Hasher._hash(seed: Hasher._seed, ${reference} as UInt64)
248250
% else:
249-
hasher._combine(UInt64(truncatingIfNeeded: input))
251+
let expected = Hasher._hash(
252+
seed: Hasher._seed,
253+
bytes: ${reference},
254+
count: ${self_ty.bits / 8})
250255
% end
251-
let expected = getInt(Int(truncatingIfNeeded: hasher.finalize()))
252256
expectEqual(expected, output, "input: \(input)")
253257
}
254258

0 commit comments

Comments
 (0)