From 94ea46dc865fb61181e0767f76dd3facb4eb1cd7 Mon Sep 17 00:00:00 2001 From: Saul Rennison Date: Mon, 20 Nov 2017 21:56:58 +0000 Subject: [PATCH 1/6] Add debug visualiser for 'dict' --- .../FSharp.Core/fslib-extra-pervasives.fs | 115 ++++++++++-------- 1 file changed, 64 insertions(+), 51 deletions(-) diff --git a/src/fsharp/FSharp.Core/fslib-extra-pervasives.fs b/src/fsharp/FSharp.Core/fslib-extra-pervasives.fs index 25aab8048a..c394db0f06 100644 --- a/src/fsharp/FSharp.Core/fslib-extra-pervasives.fs +++ b/src/fsharp/FSharp.Core/fslib-extra-pervasives.fs @@ -40,59 +40,72 @@ module ExtraTopLevelOperators = let inline ICollection_Contains<'collection,'item when 'collection :> ICollection<'item>> (collection:'collection) (item:'item) = collection.Contains item - let inline dictImpl (comparer:IEqualityComparer<'SafeKey>) (makeSafeKey:'Key->'SafeKey) (getKey:'SafeKey->'Key) (l:seq<'Key*'T>) = + [] + [>)>] + type DictImpl<'SafeKey,'Key,'T>(t : Dictionary<'SafeKey,'T>, makeSafeKey : 'Key->'SafeKey, getKey : 'SafeKey->'Key) = + + member x.Count = t.Count + + // Give a read-only view of the dictionary + interface IDictionary<'Key, 'T> with + member s.Item + with get x = dont_tail_call (fun () -> t.[makeSafeKey x]) + and set _ _ = raise (NotSupportedException(SR.GetString(SR.thisValueCannotBeMutated))) + member s.Keys = + let keys = t.Keys + { new ICollection<'Key> with + member s.Add(x) = raise (NotSupportedException(SR.GetString(SR.thisValueCannotBeMutated))); + member s.Clear() = raise (NotSupportedException(SR.GetString(SR.thisValueCannotBeMutated))); + member s.Remove(x) = raise (NotSupportedException(SR.GetString(SR.thisValueCannotBeMutated))); + member s.Contains(x) = t.ContainsKey (makeSafeKey x) + member s.CopyTo(arr,i) = + let mutable n = 0 + for k in keys do + arr.[i+n] <- getKey k + n <- n + 1 + member s.IsReadOnly = true + member s.Count = keys.Count + interface IEnumerable<'Key> with + member s.GetEnumerator() = (keys |> Seq.map getKey).GetEnumerator() + interface System.Collections.IEnumerable with + member s.GetEnumerator() = ((keys |> Seq.map getKey) :> System.Collections.IEnumerable).GetEnumerator() } + + member s.Values = upcast t.Values + member s.Add(_,_) = raise (NotSupportedException(SR.GetString(SR.thisValueCannotBeMutated))) + member s.ContainsKey(k) = dont_tail_call (fun () -> t.ContainsKey(makeSafeKey k)) + member s.TryGetValue(k,r) = + let safeKey = makeSafeKey k + if t.ContainsKey(safeKey) then (r <- t.[safeKey]; true) else false + member s.Remove(_ : 'Key) = (raise (NotSupportedException(SR.GetString(SR.thisValueCannotBeMutated))) : bool) + + interface ICollection> with + member s.Add(_) = raise (NotSupportedException(SR.GetString(SR.thisValueCannotBeMutated))); + member s.Clear() = raise (NotSupportedException(SR.GetString(SR.thisValueCannotBeMutated))); + member s.Remove(_) = raise (NotSupportedException(SR.GetString(SR.thisValueCannotBeMutated))); + member s.Contains(KeyValue(k,v)) = ICollection_Contains t (KeyValuePair<_,_>(makeSafeKey k,v)) + member s.CopyTo(arr,i) = + let mutable n = 0 + for (KeyValue(k,v)) in t do + arr.[i+n] <- KeyValuePair<_,_>(getKey k,v) + n <- n + 1 + member s.IsReadOnly = true + member s.Count = t.Count + interface IEnumerable> with + member s.GetEnumerator() = + (t |> Seq.map (fun (KeyValue(k,v)) -> KeyValuePair<_,_>(getKey k,v))).GetEnumerator() + interface System.Collections.IEnumerable with + member s.GetEnumerator() = + ((t |> Seq.map (fun (KeyValue(k,v)) -> KeyValuePair<_,_>(getKey k,v))) :> System.Collections.IEnumerable).GetEnumerator() + + and DictDebugView<'SafeKey,'Key,'T>(d:DictImpl<'SafeKey,'Key,'T>) = + [] + member x.Items = Array.ofSeq d + + let dictImpl (comparer:IEqualityComparer<'SafeKey>) (makeSafeKey : 'Key->'SafeKey) (getKey : 'SafeKey->'Key) (l:seq<'Key*'T>) : IDictionary<'Key,'T> = let t = Dictionary comparer - for (k,v) in l do + for (k,v) in l do t.[makeSafeKey k] <- v - // Give a read-only view of the dictionary - { new IDictionary<'Key, 'T> with - member s.Item - with get x = dont_tail_call (fun () -> t.[makeSafeKey x]) - and set x v = raise (NotSupportedException(SR.GetString(SR.thisValueCannotBeMutated))) - member s.Keys = - let keys = t.Keys - { new ICollection<'Key> with - member s.Add(x) = raise (NotSupportedException(SR.GetString(SR.thisValueCannotBeMutated))); - member s.Clear() = raise (NotSupportedException(SR.GetString(SR.thisValueCannotBeMutated))); - member s.Remove(x) = raise (NotSupportedException(SR.GetString(SR.thisValueCannotBeMutated))); - member s.Contains(x) = t.ContainsKey (makeSafeKey x) - member s.CopyTo(arr,i) = - let mutable n = 0 - for k in keys do - arr.[i+n] <- getKey k - n <- n + 1 - member s.IsReadOnly = true - member s.Count = keys.Count - interface IEnumerable<'Key> with - member s.GetEnumerator() = (keys |> Seq.map getKey).GetEnumerator() - interface System.Collections.IEnumerable with - member s.GetEnumerator() = ((keys |> Seq.map getKey) :> System.Collections.IEnumerable).GetEnumerator() } - - member s.Values = upcast t.Values - member s.Add(k,v) = raise (NotSupportedException(SR.GetString(SR.thisValueCannotBeMutated))) - member s.ContainsKey(k) = dont_tail_call (fun () -> t.ContainsKey(makeSafeKey k)) - member s.TryGetValue(k,r) = - let safeKey = makeSafeKey k - if t.ContainsKey(safeKey) then (r <- t.[safeKey]; true) else false - member s.Remove(k : 'Key) = (raise (NotSupportedException(SR.GetString(SR.thisValueCannotBeMutated))) : bool) - interface ICollection> with - member s.Add(x) = raise (NotSupportedException(SR.GetString(SR.thisValueCannotBeMutated))); - member s.Clear() = raise (NotSupportedException(SR.GetString(SR.thisValueCannotBeMutated))); - member s.Remove(x) = raise (NotSupportedException(SR.GetString(SR.thisValueCannotBeMutated))); - member s.Contains(KeyValue(k,v)) = ICollection_Contains t (KeyValuePair<_,_>(makeSafeKey k,v)) - member s.CopyTo(arr,i) = - let mutable n = 0 - for (KeyValue(k,v)) in t do - arr.[i+n] <- KeyValuePair<_,_>(getKey k,v) - n <- n + 1 - member s.IsReadOnly = true - member s.Count = t.Count - interface IEnumerable> with - member s.GetEnumerator() = - (t |> Seq.map (fun (KeyValue(k,v)) -> KeyValuePair<_,_>(getKey k,v))).GetEnumerator() - interface System.Collections.IEnumerable with - member s.GetEnumerator() = - ((t |> Seq.map (fun (KeyValue(k,v)) -> KeyValuePair<_,_>(getKey k,v))) :> System.Collections.IEnumerable).GetEnumerator() } + DictImpl(t, makeSafeKey, getKey) :> _ // We avoid wrapping a StructBox, because under 64 JIT we get some "hard" tailcalls which affect performance let dictValueType (l:seq<'Key*'T>) = dictImpl HashIdentity.Structural<'Key> id id l From 7ea473e996efdbf7a4c0bdd82539be39854db917 Mon Sep 17 00:00:00 2001 From: Saul Rennison Date: Sat, 25 Nov 2017 12:27:52 +0000 Subject: [PATCH 2/6] Review comments + implement IReadOnlyDictionary and IReadOnlyCollection --- src/fsharp/FSharp.Core/fslib-extra-pervasives.fs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/fsharp/FSharp.Core/fslib-extra-pervasives.fs b/src/fsharp/FSharp.Core/fslib-extra-pervasives.fs index c394db0f06..e4abbcd6f2 100644 --- a/src/fsharp/FSharp.Core/fslib-extra-pervasives.fs +++ b/src/fsharp/FSharp.Core/fslib-extra-pervasives.fs @@ -58,7 +58,7 @@ module ExtraTopLevelOperators = member s.Clear() = raise (NotSupportedException(SR.GetString(SR.thisValueCannotBeMutated))); member s.Remove(x) = raise (NotSupportedException(SR.GetString(SR.thisValueCannotBeMutated))); member s.Contains(x) = t.ContainsKey (makeSafeKey x) - member s.CopyTo(arr,i) = + member s.CopyTo(arr,i) = let mutable n = 0 for k in keys do arr.[i+n] <- getKey k @@ -78,6 +78,13 @@ module ExtraTopLevelOperators = if t.ContainsKey(safeKey) then (r <- t.[safeKey]; true) else false member s.Remove(_ : 'Key) = (raise (NotSupportedException(SR.GetString(SR.thisValueCannotBeMutated))) : bool) + interface IReadOnlyDictionary<'Key, 'T> with + member __.Item with get key = t.[makeSafeKey key] + member __.Keys = t.Keys |> Seq.map getKey + member __.TryGetValue(key, value) = t.TryGetValue(makeSafeKey key, ref value) + member __.Values = (t :> IReadOnlyDictionary<_,_>).Values + member __.ContainsKey k = t.ContainsKey (makeSafeKey k) + interface ICollection> with member s.Add(_) = raise (NotSupportedException(SR.GetString(SR.thisValueCannotBeMutated))); member s.Clear() = raise (NotSupportedException(SR.GetString(SR.thisValueCannotBeMutated))); @@ -90,9 +97,14 @@ module ExtraTopLevelOperators = n <- n + 1 member s.IsReadOnly = true member s.Count = t.Count + + interface IReadOnlyCollection> with + member __.Count = t.Count + interface IEnumerable> with member s.GetEnumerator() = (t |> Seq.map (fun (KeyValue(k,v)) -> KeyValuePair<_,_>(getKey k,v))).GetEnumerator() + interface System.Collections.IEnumerable with member s.GetEnumerator() = ((t |> Seq.map (fun (KeyValue(k,v)) -> KeyValuePair<_,_>(getKey k,v))) :> System.Collections.IEnumerable).GetEnumerator() @@ -101,7 +113,7 @@ module ExtraTopLevelOperators = [] member x.Items = Array.ofSeq d - let dictImpl (comparer:IEqualityComparer<'SafeKey>) (makeSafeKey : 'Key->'SafeKey) (getKey : 'SafeKey->'Key) (l:seq<'Key*'T>) : IDictionary<'Key,'T> = + let inline dictImpl (comparer:IEqualityComparer<'SafeKey>) (makeSafeKey : 'Key->'SafeKey) (getKey : 'SafeKey->'Key) (l:seq<'Key*'T>) : IDictionary<'Key,'T> = let t = Dictionary comparer for (k,v) in l do t.[makeSafeKey k] <- v From 5fa181e689ee7d9aaf8ac361b4901625b9e61fe1 Mon Sep 17 00:00:00 2001 From: Saul Rennison Date: Sun, 17 Dec 2017 11:23:33 +0000 Subject: [PATCH 3/6] Add `readOnlyDict` to ExtraTopLevelOperators --- .../FSharp.Core.Unittests.fsproj | 9 +- .../ExtraTopLevelOperatorsTests.fs | 213 ++++++++++++++++++ .../FSharp.Core.Unittests/LibraryTestFx.fs | 17 +- .../FSharp.Core/fslib-extra-pervasives.fs | 34 ++- .../FSharp.Core/fslib-extra-pervasives.fsi | 4 + 5 files changed, 253 insertions(+), 24 deletions(-) create mode 100644 src/fsharp/FSharp.Core.Unittests/FSharp.Core/Microsoft.FSharp.Core/ExtraTopLevelOperatorsTests.fs diff --git a/src/fsharp/FSharp.Core.Unittests/FSharp.Core.Unittests.fsproj b/src/fsharp/FSharp.Core.Unittests/FSharp.Core.Unittests.fsproj index 9a655b05f7..b71d94617d 100644 --- a/src/fsharp/FSharp.Core.Unittests/FSharp.Core.Unittests.fsproj +++ b/src/fsharp/FSharp.Core.Unittests/FSharp.Core.Unittests.fsproj @@ -39,7 +39,7 @@ prompt 3 - + true @@ -52,8 +52,8 @@ $(FsCheckLibDir)\net45\FsCheck.dll - ..\..\..\packages\System.ValueTuple.4.3.1\lib\portable-net40+sl4+win8+wp8\System.ValueTuple.dll - True + ..\..\..\packages\System.ValueTuple.4.3.1\lib\portable-net40+sl4+win8+wp8\System.ValueTuple.dll + True @@ -105,6 +105,7 @@ + @@ -125,4 +126,4 @@ - + \ No newline at end of file diff --git a/src/fsharp/FSharp.Core.Unittests/FSharp.Core/Microsoft.FSharp.Core/ExtraTopLevelOperatorsTests.fs b/src/fsharp/FSharp.Core.Unittests/FSharp.Core/Microsoft.FSharp.Core/ExtraTopLevelOperatorsTests.fs new file mode 100644 index 0000000000..ccfe6fc88f --- /dev/null +++ b/src/fsharp/FSharp.Core.Unittests/FSharp.Core/Microsoft.FSharp.Core/ExtraTopLevelOperatorsTests.fs @@ -0,0 +1,213 @@ +namespace FSharp.Core.Unittests.FSharp_Core.Microsoft_FSharp_Core + +open NUnit.Framework +open FSharp.Core.Unittests.LibraryTestFx +open System.Collections +open System.Collections.Generic + +[] +type DictTests () = + + [] + member this.IEnumerable() = + // Legit IE + let ie = (dict [|(1,1);(2,4);(3,9)|]) :> IEnumerable + let enum = ie.GetEnumerator() + + let testStepping() = + CheckThrowsInvalidOperationExn(fun () -> enum.Current |> ignore) + Assert.AreEqual(enum.MoveNext(), true) + Assert.AreEqual(enum.Current, new KeyValuePair(1,1)) + + Assert.AreEqual(enum.MoveNext(), true) + Assert.AreEqual(enum.Current, new KeyValuePair(2,4)) + Assert.AreEqual(enum.MoveNext(), true) + Assert.AreEqual(enum.Current, new KeyValuePair(3,9)) + Assert.AreEqual(enum.MoveNext(), false) + CheckThrowsInvalidOperationExn(fun () -> enum.Current |> ignore) + + testStepping() + enum.Reset() + testStepping() + + // Empty IE + let ie = [] |> dict :> IEnumerable // Note no type args + let enum = ie.GetEnumerator() + + CheckThrowsInvalidOperationExn(fun () -> enum.Current |> ignore) + Assert.AreEqual(enum.MoveNext(), false) + CheckThrowsInvalidOperationExn(fun () -> enum.Current |> ignore) + + [] + member this.IEnumerable_T() = + // Legit IE + let ie = (dict [|(1,1);(2,4);(3,9)|]) :> IEnumerable> + let enum = ie.GetEnumerator() + + let testStepping() = + CheckThrowsInvalidOperationExn(fun () -> enum.Current |> ignore) + Assert.AreEqual(enum.MoveNext(), true) + Assert.AreEqual(enum.Current, new KeyValuePair(1,1)) + + Assert.AreEqual(enum.MoveNext(), true) + Assert.AreEqual(enum.Current, new KeyValuePair(2,4)) + Assert.AreEqual(enum.MoveNext(), true) + Assert.AreEqual(enum.Current, new KeyValuePair(3,9)) + Assert.AreEqual(enum.MoveNext(), false) + CheckThrowsInvalidOperationExn(fun () -> enum.Current |> ignore) + + testStepping() + enum.Reset() + testStepping() + + // Empty IE + let ie = [] |> dict :> IEnumerable // Note no type args + let enum = ie.GetEnumerator() + + CheckThrowsInvalidOperationExn(fun () -> enum.Current |> ignore) + Assert.AreEqual(enum.MoveNext(), false) + CheckThrowsInvalidOperationExn(fun () -> enum.Current |> ignore) + + + [] + member this.IDictionary() = + // Legit ID + let id = (dict [|(1,1);(2,4);(3,9)|]) :> IDictionary<_,_> + + Assert.IsTrue(id.ContainsKey(1)) + Assert.IsFalse(id.ContainsKey(5)) + Assert.AreEqual(id.[1], 1) + Assert.AreEqual(id.[3], 9) + Assert.AreEqual(id.Keys, [| 1; 2; 3|]) + Assert.AreEqual(id.Values, [| 1; 4; 9|]) + + CheckThrowsNotSupportedException(fun () -> id.[2] <-88) + + CheckThrowsNotSupportedException(fun () -> id.Add(new KeyValuePair(4,16))) + Assert.IsTrue(id.TryGetValue(1, ref 1)) + Assert.IsFalse(id.TryGetValue(100, ref 1)) + CheckThrowsNotSupportedException(fun () -> id.Remove(1) |> ignore) + + // Empty ID + let id = dict [] :> IDictionary // Note no type args + Assert.IsFalse(id.ContainsKey(5)) + CheckThrowsKeyNotFoundException(fun () -> id.[1] |> ignore) + Assert.AreEqual(id.Keys, [| |] ) + Assert.AreEqual(id.Values, [| |] ) + + [] + member this.``IReadOnlyDictionary on readOnlyDict``() = + let irod = (readOnlyDict [|(1,1);(2,4);(3,9)|]) :> IReadOnlyDictionary<_,_> + + Assert.IsTrue(irod.ContainsKey(1)) + Assert.IsFalse(irod.ContainsKey(5)) + Assert.AreEqual(irod.[1], 1) + Assert.AreEqual(irod.[3], 9) + Assert.AreEqual(irod.Keys, [| 1; 2; 3|]) + Assert.AreEqual(irod.Values, [| 1; 4; 9|]) + + Assert.IsTrue(irod.TryGetValue(1, ref 1)) + Assert.IsFalse(irod.TryGetValue(100, ref 1)) + + // Empty IROD + let irod = readOnlyDict [] :> IReadOnlyDictionary // Note no type args + Assert.IsFalse(irod.ContainsKey(5)) + CheckThrowsKeyNotFoundException(fun () -> irod.[1] |> ignore) + Assert.AreEqual(irod.Keys, [| |] ) + Assert.AreEqual(irod.Values, [| |] ) + + [] + member this.ICollection() = + // Legit IC + let ic = (dict [|(1,1);(2,4);(3,9)|]) :> ICollection> + + Assert.AreEqual(ic.Count, 3) + Assert.IsTrue(ic.Contains(new KeyValuePair(3,9))) + let newArr = Array.create 5 (new KeyValuePair(3,9)) + ic.CopyTo(newArr,0) + Assert.IsTrue(ic.IsReadOnly) + + + // raise ReadOnlyCollection exception + CheckThrowsNotSupportedException(fun () -> ic.Add(new KeyValuePair(3,9)) |> ignore) + CheckThrowsNotSupportedException(fun () -> ic.Clear() |> ignore) + CheckThrowsNotSupportedException(fun () -> ic.Remove(new KeyValuePair(3,9)) |> ignore) + + + // Empty IC + let ic = dict [] :> ICollection> + Assert.IsFalse(ic.Contains(new KeyValuePair(3,9))) + let newArr = Array.create 5 (new KeyValuePair(0,0)) + ic.CopyTo(newArr,0) + + [] + member this.``IReadOnlyCollection on readOnlyDict``() = + // Legit IROC + let iroc = (readOnlyDict [|(1,1);(2,4);(3,9)|]) :> IReadOnlyCollection> + + Assert.AreEqual(iroc.Count, 3) + + // Empty IROC + let iroc = readOnlyDict [] :> IReadOnlyCollection> + + Assert.AreEqual(iroc.Count, 0) + + [] + member this.``IEnumerable on readOnlyDict``() = + // Legit IE + let ie = (readOnlyDict [|(1,1);(2,4);(3,9)|]) :> IEnumerable + let enum = ie.GetEnumerator() + + let testStepping() = + CheckThrowsInvalidOperationExn(fun () -> enum.Current |> ignore) + Assert.AreEqual(enum.MoveNext(), true) + Assert.AreEqual(enum.Current, new KeyValuePair(1,1)) + + Assert.AreEqual(enum.MoveNext(), true) + Assert.AreEqual(enum.Current, new KeyValuePair(2,4)) + Assert.AreEqual(enum.MoveNext(), true) + Assert.AreEqual(enum.Current, new KeyValuePair(3,9)) + Assert.AreEqual(enum.MoveNext(), false) + CheckThrowsInvalidOperationExn(fun () -> enum.Current |> ignore) + + testStepping() + enum.Reset() + testStepping() + + // Empty IE + let ie = [] |> readOnlyDict :> IEnumerable // Note no type args + let enum = ie.GetEnumerator() + + CheckThrowsInvalidOperationExn(fun () -> enum.Current |> ignore) + Assert.AreEqual(enum.MoveNext(), false) + CheckThrowsInvalidOperationExn(fun () -> enum.Current |> ignore) + + [] + member this.``IEnumerable_T on readOnlyDict``() = + // Legit IE + let ie = (readOnlyDict [|(1,1);(2,4);(3,9)|]) :> IEnumerable> + let enum = ie.GetEnumerator() + + let testStepping() = + CheckThrowsInvalidOperationExn(fun () -> enum.Current |> ignore) + Assert.AreEqual(enum.MoveNext(), true) + Assert.AreEqual(enum.Current, new KeyValuePair(1,1)) + + Assert.AreEqual(enum.MoveNext(), true) + Assert.AreEqual(enum.Current, new KeyValuePair(2,4)) + Assert.AreEqual(enum.MoveNext(), true) + Assert.AreEqual(enum.Current, new KeyValuePair(3,9)) + Assert.AreEqual(enum.MoveNext(), false) + CheckThrowsInvalidOperationExn(fun () -> enum.Current |> ignore) + + testStepping() + enum.Reset() + testStepping() + + // Empty IE + let ie = [] |> readOnlyDict :> IEnumerable // Note no type args + let enum = ie.GetEnumerator() + + CheckThrowsInvalidOperationExn(fun () -> enum.Current |> ignore) + Assert.AreEqual(enum.MoveNext(), false) + CheckThrowsInvalidOperationExn(fun () -> enum.Current |> ignore) \ No newline at end of file diff --git a/src/fsharp/FSharp.Core.Unittests/LibraryTestFx.fs b/src/fsharp/FSharp.Core.Unittests/LibraryTestFx.fs index b86112c5f7..7e2f02f06b 100644 --- a/src/fsharp/FSharp.Core.Unittests/LibraryTestFx.fs +++ b/src/fsharp/FSharp.Core.Unittests/LibraryTestFx.fs @@ -13,17 +13,12 @@ open NUnit.Framework /// Check that the lambda throws an exception of the given type. Otherwise /// calls Assert.Fail() let CheckThrowsExn<'a when 'a :> exn> (f : unit -> unit) = - let funcThrowsAsExpected = - try - let _ = f () - false // Did not throw! - with - | :? 'a - -> true // Thew null ref, OK - | _ -> false // Did now throw a null ref exception! - if funcThrowsAsExpected - then () - else Assert.Fail() + try + let _ = f () + sprintf "Expected %O exception, got no exception" typeof<'a> |> Assert.Fail + with + | :? 'a -> () + | e -> sprintf "Expected %O exception, got: %O" typeof<'a> e |> Assert.Fail let private CheckThrowsExn2<'a when 'a :> exn> s (f : unit -> unit) = let funcThrowsAsExpected = diff --git a/src/fsharp/FSharp.Core/fslib-extra-pervasives.fs b/src/fsharp/FSharp.Core/fslib-extra-pervasives.fs index e4abbcd6f2..d6e0eae8c0 100644 --- a/src/fsharp/FSharp.Core/fslib-extra-pervasives.fs +++ b/src/fsharp/FSharp.Core/fslib-extra-pervasives.fs @@ -102,22 +102,28 @@ module ExtraTopLevelOperators = member __.Count = t.Count interface IEnumerable> with - member s.GetEnumerator() = - (t |> Seq.map (fun (KeyValue(k,v)) -> KeyValuePair<_,_>(getKey k,v))).GetEnumerator() + member s.GetEnumerator() = + // We use an array comprehension here instead of seq {} as otherwise we get incorrect + // IEnumerator.Reset() and IEnumerator.Current semantics. + let kvps = [| for (KeyValue (k,v)) in t -> KeyValuePair (getKey k, v) |] :> seq<_> + kvps.GetEnumerator() interface System.Collections.IEnumerable with - member s.GetEnumerator() = - ((t |> Seq.map (fun (KeyValue(k,v)) -> KeyValuePair<_,_>(getKey k,v))) :> System.Collections.IEnumerable).GetEnumerator() + member s.GetEnumerator() = + // We use an array comprehension here instead of seq {} as otherwise we get incorrect + // IEnumerator.Reset() and IEnumerator.Current semantics. + let kvps = [| for (KeyValue (k,v)) in t -> KeyValuePair (getKey k, v) |] :> System.Collections.IEnumerable + kvps.GetEnumerator() and DictDebugView<'SafeKey,'Key,'T>(d:DictImpl<'SafeKey,'Key,'T>) = [] member x.Items = Array.ofSeq d - let inline dictImpl (comparer:IEqualityComparer<'SafeKey>) (makeSafeKey : 'Key->'SafeKey) (getKey : 'SafeKey->'Key) (l:seq<'Key*'T>) : IDictionary<'Key,'T> = + let inline dictImpl (comparer:IEqualityComparer<'SafeKey>) (makeSafeKey : 'Key->'SafeKey) (getKey : 'SafeKey->'Key) (l:seq<'Key*'T>) = let t = Dictionary comparer for (k,v) in l do t.[makeSafeKey k] <- v - DictImpl(t, makeSafeKey, getKey) :> _ + DictImpl(t, makeSafeKey, getKey) // We avoid wrapping a StructBox, because under 64 JIT we get some "hard" tailcalls which affect performance let dictValueType (l:seq<'Key*'T>) = dictImpl HashIdentity.Structural<'Key> id id l @@ -126,14 +132,24 @@ module ExtraTopLevelOperators = let dictRefType (l:seq<'Key*'T>) = dictImpl RuntimeHelpers.StructBox<'Key>.Comparer (fun k -> RuntimeHelpers.StructBox k) (fun sb -> sb.Value) l [] - let dict (keyValuePairs:seq<'Key*'T>) = + let dict (keyValuePairs:seq<'Key*'T>) : IDictionary<'Key,'T> = #if FX_RESHAPED_REFLECTION if (typeof<'Key>).GetTypeInfo().IsValueType #else if typeof<'Key>.IsValueType #endif - then dictValueType keyValuePairs - else dictRefType keyValuePairs + then dictValueType keyValuePairs :> _ + else dictRefType keyValuePairs :> _ + + [] + let readOnlyDict (keyValuePairs:seq<'Key*'T>) : IReadOnlyDictionary<'Key,'T> = +#if FX_RESHAPED_REFLECTION + if (typeof<'Key>).GetTypeInfo().IsValueType +#else + if typeof<'Key>.IsValueType +#endif + then dictValueType keyValuePairs :> _ + else dictRefType keyValuePairs :> _ let getArray (vals : seq<'T>) = match vals with diff --git a/src/fsharp/FSharp.Core/fslib-extra-pervasives.fsi b/src/fsharp/FSharp.Core/fslib-extra-pervasives.fsi index 6c96eda32a..6cfbd860c4 100644 --- a/src/fsharp/FSharp.Core/fslib-extra-pervasives.fsi +++ b/src/fsharp/FSharp.Core/fslib-extra-pervasives.fsi @@ -122,6 +122,10 @@ module ExtraTopLevelOperators = [] val dict : keyValuePairs:seq<'Key * 'Value> -> System.Collections.Generic.IDictionary<'Key,'Value> when 'Key : equality + /// Builds a read-only lookup table from a sequence of key/value pairs. The key objects are indexed using generic hashing and equality. + [] + val readOnlyDict : keyValuePairs:seq<'Key * 'Value> -> System.Collections.Generic.IReadOnlyDictionary<'Key,'Value> when 'Key : equality + /// Builds a 2D array from a sequence of sequences of elements. [] val array2D : rows:seq<#seq<'T>> -> 'T[,] From f584b3f18348901d8bbb939d9801891f8451847b Mon Sep 17 00:00:00 2001 From: Saul Rennison Date: Fri, 2 Feb 2018 18:47:48 +0000 Subject: [PATCH 4/6] Move extra top level operator tests --- .../ExtraTopLevelOperatorsTests.fs | 171 +++++++++--------- 1 file changed, 90 insertions(+), 81 deletions(-) rename {src/fsharp/FSharp.Core.Unittests => tests/FSharp.Core.UnitTests}/FSharp.Core/Microsoft.FSharp.Core/ExtraTopLevelOperatorsTests.fs (79%) diff --git a/src/fsharp/FSharp.Core.Unittests/FSharp.Core/Microsoft.FSharp.Core/ExtraTopLevelOperatorsTests.fs b/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Core/ExtraTopLevelOperatorsTests.fs similarity index 79% rename from src/fsharp/FSharp.Core.Unittests/FSharp.Core/Microsoft.FSharp.Core/ExtraTopLevelOperatorsTests.fs rename to tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Core/ExtraTopLevelOperatorsTests.fs index ccfe6fc88f..f861189ae8 100644 --- a/src/fsharp/FSharp.Core.Unittests/FSharp.Core/Microsoft.FSharp.Core/ExtraTopLevelOperatorsTests.fs +++ b/tests/FSharp.Core.UnitTests/FSharp.Core/Microsoft.FSharp.Core/ExtraTopLevelOperatorsTests.fs @@ -1,213 +1,222 @@ -namespace FSharp.Core.Unittests.FSharp_Core.Microsoft_FSharp_Core +namespace FSharp.Core.UnitTests.FSharp_Core.Microsoft_FSharp_Core open NUnit.Framework -open FSharp.Core.Unittests.LibraryTestFx +open FSharp.Core.UnitTests.LibraryTestFx open System.Collections open System.Collections.Generic [] type DictTests () = - + [] - member this.IEnumerable() = + member this.IEnumerable() = // Legit IE let ie = (dict [|(1,1);(2,4);(3,9)|]) :> IEnumerable let enum = ie.GetEnumerator() - + let testStepping() = CheckThrowsInvalidOperationExn(fun () -> enum.Current |> ignore) Assert.AreEqual(enum.MoveNext(), true) Assert.AreEqual(enum.Current, new KeyValuePair(1,1)) - + Assert.AreEqual(enum.MoveNext(), true) Assert.AreEqual(enum.Current, new KeyValuePair(2,4)) Assert.AreEqual(enum.MoveNext(), true) Assert.AreEqual(enum.Current, new KeyValuePair(3,9)) Assert.AreEqual(enum.MoveNext(), false) CheckThrowsInvalidOperationExn(fun () -> enum.Current |> ignore) - + testStepping() enum.Reset() testStepping() - + // Empty IE let ie = [] |> dict :> IEnumerable // Note no type args let enum = ie.GetEnumerator() - + CheckThrowsInvalidOperationExn(fun () -> enum.Current |> ignore) Assert.AreEqual(enum.MoveNext(), false) - CheckThrowsInvalidOperationExn(fun () -> enum.Current |> ignore) - + CheckThrowsInvalidOperationExn(fun () -> enum.Current |> ignore) + [] - member this.IEnumerable_T() = + member this.IEnumerable_T() = // Legit IE let ie = (dict [|(1,1);(2,4);(3,9)|]) :> IEnumerable> let enum = ie.GetEnumerator() - + let testStepping() = CheckThrowsInvalidOperationExn(fun () -> enum.Current |> ignore) Assert.AreEqual(enum.MoveNext(), true) Assert.AreEqual(enum.Current, new KeyValuePair(1,1)) - + Assert.AreEqual(enum.MoveNext(), true) Assert.AreEqual(enum.Current, new KeyValuePair(2,4)) Assert.AreEqual(enum.MoveNext(), true) Assert.AreEqual(enum.Current, new KeyValuePair(3,9)) Assert.AreEqual(enum.MoveNext(), false) CheckThrowsInvalidOperationExn(fun () -> enum.Current |> ignore) - + testStepping() enum.Reset() testStepping() - + // Empty IE let ie = [] |> dict :> IEnumerable // Note no type args let enum = ie.GetEnumerator() - + CheckThrowsInvalidOperationExn(fun () -> enum.Current |> ignore) Assert.AreEqual(enum.MoveNext(), false) - CheckThrowsInvalidOperationExn(fun () -> enum.Current |> ignore) - - + CheckThrowsInvalidOperationExn(fun () -> enum.Current |> ignore) + + [] - member this.IDictionary() = + member this.IDictionary() = // Legit ID - let id = (dict [|(1,1);(2,4);(3,9)|]) :> IDictionary<_,_> - - Assert.IsTrue(id.ContainsKey(1)) - Assert.IsFalse(id.ContainsKey(5)) - Assert.AreEqual(id.[1], 1) - Assert.AreEqual(id.[3], 9) - Assert.AreEqual(id.Keys, [| 1; 2; 3|]) + let id = (dict [|(1,1);(2,4);(3,9)|]) :> IDictionary<_,_> + + Assert.IsTrue(id.ContainsKey(1)) + Assert.IsFalse(id.ContainsKey(5)) + Assert.AreEqual(id.[1], 1) + Assert.AreEqual(id.[3], 9) + Assert.AreEqual(id.Keys, [| 1; 2; 3|]) Assert.AreEqual(id.Values, [| 1; 4; 9|]) - + CheckThrowsNotSupportedException(fun () -> id.[2] <-88) CheckThrowsNotSupportedException(fun () -> id.Add(new KeyValuePair(4,16))) - Assert.IsTrue(id.TryGetValue(1, ref 1)) - Assert.IsFalse(id.TryGetValue(100, ref 1)) + let mutable value = 0 + Assert.IsTrue(id.TryGetValue(2, &value)) + Assert.AreEqual(4, value) + Assert.IsFalse(id.TryGetValue(100, &value)) + Assert.AreEqual(4, value) CheckThrowsNotSupportedException(fun () -> id.Remove(1) |> ignore) - + // Empty ID - let id = dict [] :> IDictionary // Note no type args + let id = dict [] :> IDictionary // Note no type args Assert.IsFalse(id.ContainsKey(5)) - CheckThrowsKeyNotFoundException(fun () -> id.[1] |> ignore) + CheckThrowsKeyNotFoundException(fun () -> id.[1] |> ignore) Assert.AreEqual(id.Keys, [| |] ) - Assert.AreEqual(id.Values, [| |] ) + Assert.AreEqual(id.Values, [| |] ) [] member this.``IReadOnlyDictionary on readOnlyDict``() = - let irod = (readOnlyDict [|(1,1);(2,4);(3,9)|]) :> IReadOnlyDictionary<_,_> - - Assert.IsTrue(irod.ContainsKey(1)) - Assert.IsFalse(irod.ContainsKey(5)) - Assert.AreEqual(irod.[1], 1) - Assert.AreEqual(irod.[3], 9) - Assert.AreEqual(irod.Keys, [| 1; 2; 3|]) + let irod = (readOnlyDict [|(1,1);(2,4);(3,9)|]) :> IReadOnlyDictionary<_,_> + + Assert.IsTrue(irod.ContainsKey(1)) + Assert.IsFalse(irod.ContainsKey(5)) + Assert.AreEqual(irod.[1], 1) + Assert.AreEqual(irod.[3], 9) + Assert.AreEqual(irod.Keys, [| 1; 2; 3|]) Assert.AreEqual(irod.Values, [| 1; 4; 9|]) - - Assert.IsTrue(irod.TryGetValue(1, ref 1)) - Assert.IsFalse(irod.TryGetValue(100, ref 1)) - + + let mutable value = 0 + Assert.IsTrue(irod.TryGetValue(2, &value)) + Assert.AreEqual(4, value) + + Assert.IsFalse(irod.TryGetValue(100, &value)) + + // value should not have been modified + Assert.AreEqual(4, value) + // Empty IROD - let irod = readOnlyDict [] :> IReadOnlyDictionary // Note no type args + let irod = readOnlyDict [] :> IReadOnlyDictionary // Note no type args Assert.IsFalse(irod.ContainsKey(5)) - CheckThrowsKeyNotFoundException(fun () -> irod.[1] |> ignore) + CheckThrowsKeyNotFoundException(fun () -> irod.[1] |> ignore) Assert.AreEqual(irod.Keys, [| |] ) - Assert.AreEqual(irod.Values, [| |] ) - + Assert.AreEqual(irod.Values, [| |] ) + [] - member this.ICollection() = + member this.ICollection() = // Legit IC let ic = (dict [|(1,1);(2,4);(3,9)|]) :> ICollection> - + Assert.AreEqual(ic.Count, 3) - Assert.IsTrue(ic.Contains(new KeyValuePair(3,9))) + Assert.IsTrue(ic.Contains(new KeyValuePair(3,9))) let newArr = Array.create 5 (new KeyValuePair(3,9)) - ic.CopyTo(newArr,0) + ic.CopyTo(newArr,0) Assert.IsTrue(ic.IsReadOnly) - - + + // raise ReadOnlyCollection exception CheckThrowsNotSupportedException(fun () -> ic.Add(new KeyValuePair(3,9)) |> ignore) CheckThrowsNotSupportedException(fun () -> ic.Clear() |> ignore) - CheckThrowsNotSupportedException(fun () -> ic.Remove(new KeyValuePair(3,9)) |> ignore) - - + CheckThrowsNotSupportedException(fun () -> ic.Remove(new KeyValuePair(3,9)) |> ignore) + + // Empty IC - let ic = dict [] :> ICollection> - Assert.IsFalse(ic.Contains(new KeyValuePair(3,9))) + let ic = dict [] :> ICollection> + Assert.IsFalse(ic.Contains(new KeyValuePair(3,9))) let newArr = Array.create 5 (new KeyValuePair(0,0)) - ic.CopyTo(newArr,0) - + ic.CopyTo(newArr,0) + [] member this.``IReadOnlyCollection on readOnlyDict``() = // Legit IROC let iroc = (readOnlyDict [|(1,1);(2,4);(3,9)|]) :> IReadOnlyCollection> - + Assert.AreEqual(iroc.Count, 3) - + // Empty IROC let iroc = readOnlyDict [] :> IReadOnlyCollection> Assert.AreEqual(iroc.Count, 0) - + [] - member this.``IEnumerable on readOnlyDict``() = + member this.``IEnumerable on readOnlyDict``() = // Legit IE let ie = (readOnlyDict [|(1,1);(2,4);(3,9)|]) :> IEnumerable let enum = ie.GetEnumerator() - + let testStepping() = CheckThrowsInvalidOperationExn(fun () -> enum.Current |> ignore) Assert.AreEqual(enum.MoveNext(), true) Assert.AreEqual(enum.Current, new KeyValuePair(1,1)) - + Assert.AreEqual(enum.MoveNext(), true) Assert.AreEqual(enum.Current, new KeyValuePair(2,4)) Assert.AreEqual(enum.MoveNext(), true) Assert.AreEqual(enum.Current, new KeyValuePair(3,9)) Assert.AreEqual(enum.MoveNext(), false) CheckThrowsInvalidOperationExn(fun () -> enum.Current |> ignore) - + testStepping() enum.Reset() testStepping() - + // Empty IE let ie = [] |> readOnlyDict :> IEnumerable // Note no type args let enum = ie.GetEnumerator() - + CheckThrowsInvalidOperationExn(fun () -> enum.Current |> ignore) Assert.AreEqual(enum.MoveNext(), false) - CheckThrowsInvalidOperationExn(fun () -> enum.Current |> ignore) - + CheckThrowsInvalidOperationExn(fun () -> enum.Current |> ignore) + [] - member this.``IEnumerable_T on readOnlyDict``() = + member this.``IEnumerable_T on readOnlyDict``() = // Legit IE let ie = (readOnlyDict [|(1,1);(2,4);(3,9)|]) :> IEnumerable> let enum = ie.GetEnumerator() - + let testStepping() = CheckThrowsInvalidOperationExn(fun () -> enum.Current |> ignore) Assert.AreEqual(enum.MoveNext(), true) Assert.AreEqual(enum.Current, new KeyValuePair(1,1)) - + Assert.AreEqual(enum.MoveNext(), true) Assert.AreEqual(enum.Current, new KeyValuePair(2,4)) Assert.AreEqual(enum.MoveNext(), true) Assert.AreEqual(enum.Current, new KeyValuePair(3,9)) Assert.AreEqual(enum.MoveNext(), false) CheckThrowsInvalidOperationExn(fun () -> enum.Current |> ignore) - + testStepping() enum.Reset() testStepping() - + // Empty IE let ie = [] |> readOnlyDict :> IEnumerable // Note no type args let enum = ie.GetEnumerator() - + CheckThrowsInvalidOperationExn(fun () -> enum.Current |> ignore) Assert.AreEqual(enum.MoveNext(), false) - CheckThrowsInvalidOperationExn(fun () -> enum.Current |> ignore) \ No newline at end of file + CheckThrowsInvalidOperationExn(fun () -> enum.Current |> ignore) From 240f5d5c71591d4441628910bbc9ddce7dce0840 Mon Sep 17 00:00:00 2001 From: Saul Rennison Date: Sun, 18 Feb 2018 10:41:07 +0000 Subject: [PATCH 5/6] Fix broken tests --- src/fsharp/FSharp.Core/fslib-extra-pervasives.fs | 7 ++++++- tests/FSharp.Core.UnitTests/SurfaceArea.net40.fs | 1 + 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/fsharp/FSharp.Core/fslib-extra-pervasives.fs b/src/fsharp/FSharp.Core/fslib-extra-pervasives.fs index d6e0eae8c0..63cb2d8071 100644 --- a/src/fsharp/FSharp.Core/fslib-extra-pervasives.fs +++ b/src/fsharp/FSharp.Core/fslib-extra-pervasives.fs @@ -81,7 +81,12 @@ module ExtraTopLevelOperators = interface IReadOnlyDictionary<'Key, 'T> with member __.Item with get key = t.[makeSafeKey key] member __.Keys = t.Keys |> Seq.map getKey - member __.TryGetValue(key, value) = t.TryGetValue(makeSafeKey key, ref value) + member __.TryGetValue(key, r) = + match t.TryGetValue (makeSafeKey key) with + | false, _ -> false + | true, value -> + r <- value + true member __.Values = (t :> IReadOnlyDictionary<_,_>).Values member __.ContainsKey k = t.ContainsKey (makeSafeKey k) diff --git a/tests/FSharp.Core.UnitTests/SurfaceArea.net40.fs b/tests/FSharp.Core.UnitTests/SurfaceArea.net40.fs index 87ed5adff4..4e1bb00423 100644 --- a/tests/FSharp.Core.UnitTests/SurfaceArea.net40.fs +++ b/tests/FSharp.Core.UnitTests/SurfaceArea.net40.fs @@ -1179,6 +1179,7 @@ Microsoft.FSharp.Core.ExtraTopLevelOperators: Microsoft.FSharp.Linq.QueryBuilder Microsoft.FSharp.Core.ExtraTopLevelOperators: SByte ToSByte[T](T) Microsoft.FSharp.Core.ExtraTopLevelOperators: Single ToSingle[T](T) Microsoft.FSharp.Core.ExtraTopLevelOperators: System.Collections.Generic.IDictionary`2[TKey,TValue] CreateDictionary[TKey,TValue](System.Collections.Generic.IEnumerable`1[System.Tuple`2[TKey,TValue]]) +Microsoft.FSharp.Core.ExtraTopLevelOperators: System.Collections.Generic.IReadOnlyDictionary`2[TKey,TValue] CreateReadOnlyDictionary[TKey,TValue](System.Collections.Generic.IEnumerable`1[System.Tuple`2[TKey,TValue]]) Microsoft.FSharp.Core.ExtraTopLevelOperators: System.String ToString() Microsoft.FSharp.Core.ExtraTopLevelOperators: System.Type GetType() Microsoft.FSharp.Core.ExtraTopLevelOperators: T LazyPattern[T](System.Lazy`1[T]) From 9b575ddcc0b0b0de765639864672cb8a30dcb1e1 Mon Sep 17 00:00:00 2001 From: Saul Rennison Date: Sat, 17 Mar 2018 10:49:51 +0000 Subject: [PATCH 6/6] Add CreateReadOnlyDictionary to coreclr surface area --- tests/FSharp.Core.UnitTests/SurfaceArea.coreclr.fs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/FSharp.Core.UnitTests/SurfaceArea.coreclr.fs b/tests/FSharp.Core.UnitTests/SurfaceArea.coreclr.fs index 1c9d7c18c1..7ab7de96ee 100644 --- a/tests/FSharp.Core.UnitTests/SurfaceArea.coreclr.fs +++ b/tests/FSharp.Core.UnitTests/SurfaceArea.coreclr.fs @@ -1106,6 +1106,7 @@ Microsoft.FSharp.Core.ExtraTopLevelOperators: Microsoft.FSharp.Linq.QueryBuilder Microsoft.FSharp.Core.ExtraTopLevelOperators: SByte ToSByte[T](T) Microsoft.FSharp.Core.ExtraTopLevelOperators: Single ToSingle[T](T) Microsoft.FSharp.Core.ExtraTopLevelOperators: System.Collections.Generic.IDictionary`2[TKey,TValue] CreateDictionary[TKey,TValue](System.Collections.Generic.IEnumerable`1[System.Tuple`2[TKey,TValue]]) +Microsoft.FSharp.Core.ExtraTopLevelOperators: System.Collections.Generic.IReadOnlyDictionary`2[TKey,TValue] CreateReadOnlyDictionary[TKey,TValue](System.Collections.Generic.IEnumerable`1[System.Tuple`2[TKey,TValue]]) Microsoft.FSharp.Core.ExtraTopLevelOperators: System.String ToString() Microsoft.FSharp.Core.ExtraTopLevelOperators: System.Type GetType() Microsoft.FSharp.Core.ExtraTopLevelOperators: T LazyPattern[T](System.Lazy`1[T])