diff --git a/src/Fable.Transforms/Replacements.fs b/src/Fable.Transforms/Replacements.fs index ae22db94b1..f3ac4c94e5 100644 --- a/src/Fable.Transforms/Replacements.fs +++ b/src/Fable.Transforms/Replacements.fs @@ -717,6 +717,8 @@ let identityHash r (arg: Expr) = Helper.CoreCall("Util", "structuralHash", Number Int32, [arg], ?loc=r) | DeclaredType(ent,_) when ent.IsFSharpUnion || ent.IsFSharpRecord || ent.IsValueType -> Helper.CoreCall("Util", "structuralHash", Number Int32, [arg], ?loc=r) + | DeclaredType(ent,_) -> + Helper.InstanceCall(arg, "GetHashCode", Number Int32, [], ?loc=r) | _ -> Helper.CoreCall("Util", "identityHash", Number Int32, [arg], ?loc=r) diff --git a/src/fable-library/List.fs b/src/fable-library/List.fs index dd1df15954..2997eba6b4 100644 --- a/src/fable-library/List.fs +++ b/src/fable-library/List.fs @@ -8,58 +8,72 @@ open Fable.Core let msgListWasEmpty = "List was empty" let msgListNoMatch = "List did not contain any matching elements" -[] -type List<'T> when 'T : comparison = - { Count: int; Values: ResizeArray<'T> } +type List<'T when 'T: comparison>(Count: int, Values: ResizeArray<'T>) = + let mutable hashCode = None - static member Empty = - { Count = 0; Values = ResizeArray<'T>() } - static member Cons (x: 'T, xs: 'T list) = + static member Empty = new List<'T>(0, ResizeArray<'T>()) + static member Cons (x: 'T, xs: 'T list) = xs.Add(x) + + member _.Add(x: 'T) = let values = - if xs.Count = xs.Values.Count - then xs.Values - else xs.Values.GetRange(0, xs.Count) + if Count = Values.Count + then Values + else Values.GetRange(0, Count) values.Add(x) - { Count = values.Count; Values = values } + new List<'T>(values.Count, values) + + member _.IsEmpty = Count <= 0 + member _.Length = Count + + member _.Head = + if Count > 0 + then Values.[Count - 1] + else failwith msgListWasEmpty + + member _.Tail = + if Count > 0 + then new List<'T>(Count - 1, Values) + else failwith msgListWasEmpty + + member _.Item with get(index) = + Values.[Count - 1 - index] override xs.ToString() = "[" + System.String.Join("; ", xs) + "]" - override xs.GetHashCode() = - let inline combineHash x y = (x <<< 1) + y + 631 - let mutable res = 0 - for i = xs.Count - 1 downto 0 do - res <- combineHash res (hash xs.Values.[i]) - res + override xs.Equals(other: obj) = + let ys = other :?> 'T list + if xs.Length <> ys.Length then false + elif xs.GetHashCode() <> ys.GetHashCode() then false + else Seq.forall2 (Unchecked.equals) xs ys + // (xs :> System.IComparable).CompareTo(other) = 0 - override xs.Equals(that: obj) = - (xs :> System.IComparable).CompareTo(that) = 0 + override xs.GetHashCode() = + match hashCode with + | Some h -> h + | None -> + let inline combineHash i x y = (x <<< 1) + y + 631 * i + let len = min (xs.Length - 1) 18 // limit the hash count + let mutable h = 0 + for i = 0 to len do + h <- combineHash i h (hash xs.[i]) + hashCode <- Some h + h interface System.IComparable with - member xs.CompareTo(that: obj) = - List.CompareWith compare xs (that :?> 'T list) + member xs.CompareTo(other: obj) = + List.CompareWith compare xs (other :?> 'T list) interface System.Collections.Generic.IEnumerable<'T> with member xs.GetEnumerator(): System.Collections.Generic.IEnumerator<'T> = - let elems = seq { for i = xs.Count - 1 downto 0 do yield xs.Values.[i] } + let len = xs.Length - 1 + let elems = seq { for i = 0 to len do yield xs.[i] } elems.GetEnumerator() + interface System.Collections.IEnumerable with member this.GetEnumerator(): System.Collections.IEnumerator = ((this :> System.Collections.Generic.IEnumerable<'T>).GetEnumerator() :> System.Collections.IEnumerator) - member xs.Length = xs.Count - member xs.Head = - if xs.Count > 0 - then xs.Values.[xs.Count - 1] - else failwith msgListWasEmpty - member xs.Tail = - if xs.Count > 0 - then { Count = xs.Count - 1; Values = xs.Values } - else failwith msgListWasEmpty - member xs.IsEmpty = xs.Count <= 0 - member xs.Item with get(index) = - xs.Values.[xs.Count - 1 - index] - static member CompareWith (comparer: 'T -> 'T -> int) (xs: 'T list) (ys: 'T list): int = if obj.ReferenceEquals(xs, ys) then 0 @@ -80,9 +94,7 @@ type List<'T> when 'T : comparison = and 'T list when 'T: comparison = List<'T> -open System.Collections.Generic - -let newList (values: ResizeArray<'T>) = { Count = values.Count; Values = values } +let newList (values: ResizeArray<'T>) = new List<'T>(values.Count, values) let empty () = List.Empty @@ -213,7 +225,7 @@ let iterateIndexed f xs = let iterateIndexed2 f xs ys = fold2 (fun i x y -> f i x y; i + 1) 0 xs ys |> ignore -let ofArray (xs: IList<'T>) = +let ofArray (xs: System.Collections.Generic.IList<'T>) = let mutable res = List.Empty for i = xs.Count - 1 downto 0 do res <- cons xs.[i] res @@ -315,13 +327,13 @@ let choose f xs = | Some y -> cons y acc | None -> acc) List.Empty xs |> reverse -let contains (value: 'T) (xs: 'T list) ([] eq: IEqualityComparer<'T>) = +let contains (value: 'T) (xs: 'T list) ([] eq: System.Collections.Generic.IEqualityComparer<'T>) = tryFindIndex (fun v -> eq.Equals (value, v)) xs |> Option.isSome -let except (itemsToExclude: seq<'t>) (xs: 't list) ([] eq: IEqualityComparer<'t>): 't list = +let except (itemsToExclude: seq<'t>) (xs: 't list) ([] eq: System.Collections.Generic.IEqualityComparer<'t>): 't list = if isEmpty xs then xs else - let cached = HashSet(itemsToExclude, eq) + let cached = System.Collections.Generic.HashSet(itemsToExclude, eq) xs |> filter cached.Add let initialize n f = @@ -373,16 +385,16 @@ let sortWith (comparison: 'T -> 'T -> int) (xs: 'T list): 'T list = values.Sort(System.Comparison<_>(comparison)) values |> ofSeq -let sort (xs: 'T list) ([] comparer: IComparer<'T>): 'T list = +let sort (xs: 'T list) ([] comparer: System.Collections.Generic.IComparer<'T>): 'T list = sortWith (fun x y -> comparer.Compare(x, y)) xs -let sortBy (projection: 'a -> 'b) (xs: 'a list) ([] comparer: IComparer<'b>): 'a list = +let sortBy (projection: 'a -> 'b) (xs: 'a list) ([] comparer: System.Collections.Generic.IComparer<'b>): 'a list = sortWith (fun x y -> comparer.Compare(projection x, projection y)) xs -let sortDescending (xs: 'T list) ([] comparer: IComparer<'T>): 'T list = +let sortDescending (xs: 'T list) ([] comparer: System.Collections.Generic.IComparer<'T>): 'T list = sortWith (fun x y -> comparer.Compare(x, y) * -1) xs -let sortByDescending (projection: 'a -> 'b) (xs: 'a list) ([] comparer: IComparer<'b>): 'a list = +let sortByDescending (projection: 'a -> 'b) (xs: 'a list) ([] comparer: System.Collections.Generic.IComparer<'b>): 'a list = sortWith (fun x y -> comparer.Compare(projection x, projection y) * -1) xs let sum (xs: 'T list) ([] adder: IGenericAdder<'T>): 'T = @@ -391,16 +403,16 @@ let sum (xs: 'T list) ([] adder: IGenericAdder<'T>): 'T = let sumBy (f: 'T -> 'T2) (xs: 'T list) ([] adder: IGenericAdder<'T2>): 'T2 = fold (fun acc x -> adder.Add(acc, f x)) (adder.GetZero()) xs -let maxBy (projection: 'a -> 'b) (xs: 'a list) ([] comparer: IComparer<'b>): 'a = +let maxBy (projection: 'a -> 'b) (xs: 'a list) ([] comparer: System.Collections.Generic.IComparer<'b>): 'a = reduce (fun x y -> if comparer.Compare(projection y, projection x) > 0 then y else x) xs -let max (li:'a list) ([] comparer: IComparer<'a>): 'a = +let max (li:'a list) ([] comparer: System.Collections.Generic.IComparer<'a>): 'a = reduce (fun x y -> if comparer.Compare(y, x) > 0 then y else x) li -let minBy (projection: 'a -> 'b) (xs: 'a list) ([] comparer: IComparer<'b>): 'a = +let minBy (projection: 'a -> 'b) (xs: 'a list) ([] comparer: System.Collections.Generic.IComparer<'b>): 'a = reduce (fun x y -> if comparer.Compare(projection y, projection x) > 0 then x else y) xs -let min (xs: 'a list) ([] comparer: IComparer<'a>): 'a = +let min (xs: 'a list) ([] comparer: System.Collections.Generic.IComparer<'a>): 'a = reduce (fun x y -> if comparer.Compare(y, x) > 0 then x else y) xs let average (xs: 'T list) ([] averager: IGenericAverager<'T>): 'T = @@ -452,11 +464,11 @@ let splitAt i (xs: 'T list) = if i > xs.Length then invalidArg "index" "The input sequence has an insufficient number of elements." take i xs, skip i xs -let distinctBy (projection: 'T -> 'Key) (xs: 'T list) ([] eq: IEqualityComparer<'Key>) = - let hashSet = HashSet<'Key>(eq) +let distinctBy (projection: 'T -> 'Key) (xs: 'T list) ([] eq: System.Collections.Generic.IEqualityComparer<'Key>) = + let hashSet = System.Collections.Generic.HashSet<'Key>(eq) xs |> filter (projection >> hashSet.Add) -let distinct (xs: 'T list) ([] eq: IEqualityComparer<'T>) = +let distinct (xs: 'T list) ([] eq: System.Collections.Generic.IEqualityComparer<'T>) = distinctBy id xs eq let exactlyOne (xs: 'T list) = @@ -465,8 +477,8 @@ let exactlyOne (xs: 'T list) = | 0 -> invalidArg "list" LanguagePrimitives.ErrorStrings.InputSequenceEmptyString | _ -> invalidArg "list" "Input list too long" -let groupBy (projection: 'T -> 'Key) (xs: 'T list)([] eq: IEqualityComparer<'Key>): ('Key * 'T list) list = - let dict = Dictionary<'Key, 'T list>(eq) +let groupBy (projection: 'T -> 'Key) (xs: 'T list)([] eq: System.Collections.Generic.IEqualityComparer<'Key>): ('Key * 'T list) list = + let dict = System.Collections.Generic.Dictionary<'Key, 'T list>(eq) let mutable keys = List.Empty xs |> iterate (fun v -> let key = projection v @@ -480,8 +492,8 @@ let groupBy (projection: 'T -> 'Key) (xs: 'T list)([] eq: IEqualityCompa keys |> iterate (fun key -> result <- cons (key, reverse dict.[key]) result) result -let countBy (projection: 'T -> 'Key) (xs: 'T list)([] eq: IEqualityComparer<'Key>) = - let dict = Dictionary<'Key, int>(eq) +let countBy (projection: 'T -> 'Key) (xs: 'T list)([] eq: System.Collections.Generic.IEqualityComparer<'Key>) = + let dict = System.Collections.Generic.Dictionary<'Key, int>(eq) let mutable keys = List.Empty xs |> iterate (fun v -> let key = projection v diff --git a/tests/Main/ComparisonTests.fs b/tests/Main/ComparisonTests.fs index a9d1b23fd2..190858f800 100644 --- a/tests/Main/ComparisonTests.fs +++ b/tests/Main/ComparisonTests.fs @@ -434,6 +434,10 @@ let tests = (OTest(1).GetHashCode(), OTest(1).GetHashCode()) ||> notEqual (OTest(2).GetHashCode(), OTest(1).GetHashCode()) ||> notEqual + testCase "GetHashCode with objects that overwrite it works" <| fun () -> + (Test(1).GetHashCode(), Test(1).GetHashCode()) ||> equal + (Test(2).GetHashCode(), Test(1).GetHashCode()) ||> notEqual + testCase "GetHashCode with same object works" <| fun () -> let o = OTest(1) let h1 = o.GetHashCode()