Skip to content
Merged
Show file tree
Hide file tree
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
5 changes: 4 additions & 1 deletion src/Fable.Transforms/Python/Replacements.fs
Original file line number Diff line number Diff line change
Expand Up @@ -558,7 +558,10 @@ let identityHash com r (arg: Expr) =
| Char
| String
| Builtin BclGuid -> "stringHash"
| Number((Decimal | BigInt | Int64 | UInt64), _) -> "safeHash"
// Every numeric width reaches the same `GetHashCode`, so routing the wider
// ones through `safeHash` only added a frame and its protocol check. This
// now matches `structuralHash`, which has always sent all of them to
// `numberHash`.
| Number _
| Builtin BclTimeSpan -> "numberHash"
| List _ -> "safeHash"
Expand Down
8 changes: 5 additions & 3 deletions src/fable-library-py/src/native_array.rs
Original file line number Diff line number Diff line change
Expand Up @@ -167,9 +167,11 @@ impl Clone for NativeArray {

// Helper macro for filling storage
//
// The wrapper type is tried first so a same-type value costs no conversion dunder;
// the primitive fallback accepts a plain Python int/float, which is how Int32 and
// Float64 values are represented.
// The wrapper type is tried first, which hits for the widths that are still pyo3
// wrappers. Int32 and Float64 are plain Python ints and floats, so for those two
// the wrapper arm always fails and the primitive fallback is the normal path —
// which costs nothing measurable, because a failed `extract` is a Rust-level
// `Err` rather than a constructed Python exception.
macro_rules! fill_typed_vec {
($vec:expr, $value:expr, $target_index:expr, $count:expr, $type:ty, $prim:ty) => {{
let typed_value: $prim = match $value.extract::<$type>() {
Expand Down
41 changes: 41 additions & 0 deletions tests/Python/TestComparison.fs
Original file line number Diff line number Diff line change
Expand Up @@ -656,3 +656,44 @@ let ``CompareTo with primitives works`` () =
(1.).CompareTo(1.) |> equal 0
(1.m).CompareTo(1.m) |> equal 0
("1").CompareTo("1") |> equal 0

[<Fact>]
let ``test GetHashCode with wide numeric types works`` () =
// These route through identity hashing, which used to take a different path
// than structural hashing for the widths below.
(5L).GetHashCode() |> equal ((5L).GetHashCode())
(5UL).GetHashCode() |> equal ((5UL).GetHashCode())
(5M).GetHashCode() |> equal ((5M).GetHashCode())
(5I).GetHashCode() |> equal ((5I).GetHashCode())
(5L).GetHashCode() = (6L).GetHashCode() |> equal false
(5UL).GetHashCode() = (6UL).GetHashCode() |> equal false
(5M).GetHashCode() = (6M).GetHashCode() |> equal false
(5I).GetHashCode() = (6I).GetHashCode() |> equal false

[<Fact>]
let ``test hashing wide numeric types agrees with GetHashCode`` () =
hash 5L |> equal ((5L).GetHashCode())
hash 5UL |> equal ((5UL).GetHashCode())
hash 5M |> equal ((5M).GetHashCode())
hash 5I |> equal ((5I).GetHashCode())

[<Fact>]
let ``test wide numeric types work as hash keys`` () =
// Added one at a time rather than from a sequence: constructing a HashSet from
// an IEnumerable trips a known Pyright gap, the one that keeps test_hash_set.py
// in the CI exclude list.
let longs = System.Collections.Generic.HashSet<int64>()
longs.Add 1L |> ignore
longs.Add 2L |> ignore
longs.Add 2L |> equal false
longs.Contains 2L |> equal true
longs.Contains 9L |> equal false
let ulongs = System.Collections.Generic.HashSet<uint64>()
ulongs.Add 2UL |> ignore
ulongs.Contains 2UL |> equal true
let decimals = System.Collections.Generic.HashSet<decimal>()
decimals.Add 2M |> ignore
decimals.Contains 2M |> equal true
let bigs = System.Collections.Generic.HashSet<bigint>()
bigs.Add 2I |> ignore
bigs.Contains 2I |> equal true
Loading