Skip to content

Commit 5b80e90

Browse files
committed
Fix regression in regards to hash code on fast-path covariant arrays (+ comments)
1 parent 4dc1a79 commit 5b80e90

File tree

1 file changed

+24
-6
lines changed

1 file changed

+24
-6
lines changed

src/fsharp/FSharp.Core/prim-types.fs

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -839,6 +839,12 @@ namespace Microsoft.FSharp.Core
839839
match x with
840840
| null -> 0
841841
| (:? System.Array as a) ->
842+
// due to the rules of the CLI type system, array casts are "assignment compatible"
843+
// see: https://blogs.msdn.microsoft.com/ericlippert/2009/09/24/why-is-covariance-of-value-typed-arrays-inconsistent/
844+
// this means that the cast and comparison for byte will also handle sbyte, int32 handle uint32,
845+
// and int64 handle uint64. The hash code of an individual array element is different for the different
846+
// types, but it is irrelevant for the creation of the hash code - but this is to be replicated in
847+
// the tryGetFSharpArrayEqualityComparer function.
842848
match a with
843849
| :? (obj[]) as oa -> GenericHashObjArray iec oa
844850
| :? (byte[]) as ba -> GenericHashByteArray ba
@@ -1383,10 +1389,12 @@ namespace Microsoft.FSharp.Core
13831389
| (:? string as xs),(:? string as ys) -> System.String.Equals(xs,ys)
13841390
// Permit structural equality on arrays
13851391
| (:? System.Array as arr1),_ ->
1392+
// due to the rules of the CLI type system, array casts are "assignment compatible"
1393+
// see: https://blogs.msdn.microsoft.com/ericlippert/2009/09/24/why-is-covariance-of-value-typed-arrays-inconsistent/
1394+
// this means that the cast and comparison for byte will also handle sbyte, int32 handle uint32,
1395+
// and int64 handle uint64. Equality will still be correct.
13861396
match arr1,yobj with
1387-
// Fast path
13881397
| (:? (obj[]) as arr1), (:? (obj[]) as arr2) -> GenericEqualityObjArray er iec arr1 arr2
1389-
// Fast path
13901398
| (:? (byte[]) as arr1), (:? (byte[]) as arr2) -> GenericEqualityByteArray arr1 arr2
13911399
| (:? (int32[]) as arr1), (:? (int32[]) as arr2) -> GenericEqualityInt32Array arr1 arr2
13921400
| (:? (int64[]) as arr1), (:? (int64[]) as arr2) -> GenericEqualityInt64Array arr1 arr2
@@ -1604,11 +1612,21 @@ namespace Microsoft.FSharp.Core
16041612
| null -> 0
16051613
| _ -> getHashCode x }
16061614

1615+
let inline castNullableEqualityComparer<'fromType, 'toType when 'toType : null and 'fromType : null> (equals:'toType->'toType->bool) (getHashCode:'toType->int) =
1616+
let castEquals (lhs:'fromType) (rhs:'fromType) = equals (unboxPrim lhs) (unboxPrim rhs)
1617+
let castGetHashCode (o:'fromType) = getHashCode (unboxPrim o)
1618+
nullableEqualityComparer castEquals castGetHashCode
1619+
16071620
let tryGetFSharpArrayEqualityComparer (ty:Type) er comparer : obj =
1608-
if ty.Equals typeof<obj[]> then nullableEqualityComparer (fun x y -> GenericEqualityObjArray er comparer x y) (GenericHashObjArray fsEqualityComparerUnlimitedHashingPER)
1609-
elif ty.Equals typeof<byte[]> then nullableEqualityComparer GenericEqualityByteArray GenericHashByteArray
1610-
elif ty.Equals typeof<int32[]> then nullableEqualityComparer GenericEqualityInt32Array GenericHashInt32Array
1611-
elif ty.Equals typeof<int64[]> then nullableEqualityComparer GenericEqualityInt64Array GenericHashInt64Array
1621+
// the casts here between byte+sbyte, int32+uint32 and int64+uint64 are here to replicate the behaviour
1622+
// in GenericHashParamObj
1623+
if ty.Equals typeof<obj[]> then nullableEqualityComparer (fun x y -> GenericEqualityObjArray er comparer x y) (GenericHashObjArray fsEqualityComparerUnlimitedHashingPER)
1624+
elif ty.Equals typeof<byte[]> then nullableEqualityComparer GenericEqualityByteArray GenericHashByteArray
1625+
elif ty.Equals typeof<sbyte[]> then castNullableEqualityComparer<sbyte[],_> GenericEqualityByteArray GenericHashByteArray
1626+
elif ty.Equals typeof<int32[]> then nullableEqualityComparer GenericEqualityInt32Array GenericHashInt32Array
1627+
elif ty.Equals typeof<uint32[]> then castNullableEqualityComparer<uint32[],_> GenericEqualityInt32Array GenericHashInt32Array
1628+
elif ty.Equals typeof<int64[]> then nullableEqualityComparer GenericEqualityInt64Array GenericHashInt64Array
1629+
elif ty.Equals typeof<uint64[]> then castNullableEqualityComparer<uint64[],_> GenericEqualityInt64Array GenericHashInt64Array
16121630
else null
16131631

16141632
let arrayEqualityComparer<'T> er comparer =

0 commit comments

Comments
 (0)