diff --git a/src/fsharp/FSharp.Core.Unittests/FSharp.Core/Microsoft.FSharp.Collections/MapType.fs b/src/fsharp/FSharp.Core.Unittests/FSharp.Core/Microsoft.FSharp.Collections/MapType.fs index f95ce3e47ce..cec730881e2 100644 --- a/src/fsharp/FSharp.Core.Unittests/FSharp.Core/Microsoft.FSharp.Collections/MapType.fs +++ b/src/fsharp/FSharp.Core.Unittests/FSharp.Core/Microsoft.FSharp.Collections/MapType.fs @@ -84,10 +84,37 @@ type MapType() = CheckThrowsInvalidOperationExn(fun () -> enum.Current |> ignore) Assert.AreEqual(enum.MoveNext(), false) CheckThrowsInvalidOperationExn(fun () -> enum.Current |> ignore) - + + [] + member __.IDictionary() = + // Legit ID + let id = (Map.ofArray [|(1,1);(2,4);(3,9)|]) :> IDictionary + + Assert.IsTrue(id.Contains(box 1)) + Assert.IsFalse(id.Contains(box 5)) + Assert.AreEqual(id.[box 1], 1) + Assert.AreEqual(id.[box 3], 9) + CollectionAssert.AreEqual(id.Keys, [| 1; 2; 3|]) + CollectionAssert.AreEqual(id.Values, [| 1; 4; 9|]) + + CheckThrowsNotSupportedException(fun () -> id.[box 2] <-88) + + CheckThrowsNotSupportedException(fun () -> id.Add(box 4, box 16)) + Assert.AreEqual(id.[box 1], 1) + Assert.IsNull(id.[box 100]) // IDictionary.[x] returns null when key isn't found + CheckThrowsNotSupportedException(fun () -> id.Remove(box 1) |> ignore) + + // Empty ID + let id = (Map.empty : Map) :> IDictionary + Assert.IsFalse(id.Contains(box 5)) + // IDictionary specifies .Item[x] should return null when the key isn't + // found, rather than raising KeyNotFoundException. + Assert.IsNull(id.[1]) + CollectionAssert.AreEqual(id.Keys, [| |] ) + CollectionAssert.AreEqual(id.Values, [| |] ) [] - member this.IDictionary() = + member __.IDictionary_T() = // Legit ID let id = (Map.ofArray [|(1,1);(2,4);(3,9)|]) :> IDictionary<_,_> @@ -113,7 +140,27 @@ type MapType() = Assert.AreEqual(id.Values, [| |] ) [] - member this.ICollection() = + member __.ICollection() = + // Legit IC + let ic = (Map.ofArray [|(1,1);(2,4);(3,9)|]) :> ICollection + + Assert.AreEqual (3, ic.Count) + + let newArr = Array.create 5 (new KeyValuePair(3,9)) + ic.CopyTo(newArr,0) + + Assert.IsTrue(ic.IsSynchronized) + Assert.IsNotNull(ic.SyncRoot) + + // Empty IC + let ic = (Map.empty : Map) :> ICollection + Assert.AreEqual (0, ic.Count) + + let newArr = Array.create 5 (new KeyValuePair(0,0)) + ic.CopyTo(newArr,0) + + [] + member __.ICollection_T() = // Legit IC let ic = (Map.ofArray [|(1,1);(2,4);(3,9)|]) :> ICollection> @@ -150,7 +197,62 @@ type MapType() = let ic = [] |> Map.ofList :> IComparable Assert.AreEqual(ic.CompareTo([]|> Map.ofList),0) - + [] + member __.IComparable_T() = + // Legit IC + let ic = (Map.ofArray [|(1,1);(2,4);(3,9)|]) :> IComparable<_> + Assert.AreEqual(ic.CompareTo([(1,1);(2,4);(3,9)]|> Map.ofList),0) + Assert.AreEqual(ic.CompareTo([(1,1);(3,9);(2,4)]|> Map.ofList),0) + Assert.AreEqual(ic.CompareTo([(1,1);(9,81);(2,4)]|> Map.ofList),-1) + Assert.AreEqual(ic.CompareTo([(1,1);(0,0);(2,4)]|> Map.ofList),1) + + // Empty IC + let ic = [] |> Map.ofList :> IComparable<_> + Assert.AreEqual(ic.CompareTo([]|> Map.ofList),0) + + [] + member __.IEquatable_T() = + // Legit IC + let ic = (Map.ofArray [|(1,1);(2,4);(3,9)|]) :> IComparable<_> + Assert.AreEqual(ic.CompareTo([(1,1);(2,4);(3,9)]|> Map.ofList),0) + Assert.AreEqual(ic.CompareTo([(1,1);(3,9);(2,4)]|> Map.ofList),0) + Assert.AreEqual(ic.CompareTo([(1,1);(9,81);(2,4)]|> Map.ofList),-1) + Assert.AreEqual(ic.CompareTo([(1,1);(0,0);(2,4)]|> Map.ofList),1) + + // Empty IC + let ic = [] |> Map.ofList :> IComparable<_> + Assert.AreEqual(ic.CompareTo([]|> Map.ofList),0) + +#if FX_ATLEAST_45 + [] + member __.IReadOnlyCollection_T () = + Assert.Ignore ("Test not yet implemented.") + + [] + member __.IReadOnlyDictionary_T () = + // Legit IROD + let id = (Map.ofArray [|(1,1);(2,4);(3,9)|]) :> IReadOnlyDictionary<_,_> + + Assert.IsTrue(id.ContainsKey(1)) + Assert.IsFalse(id.ContainsKey(5)) + + Assert.AreEqual(id.[1], 1) + Assert.AreEqual(id.[3], 9) + + CollectionAssert.AreEqual(id.Keys, [| 1; 2; 3|]) + CollectionAssert.AreEqual(id.Values, [| 1; 4; 9|] + + Assert.IsTrue(id.TryGetValue(1, ref 1)) + Assert.IsFalse(id.TryGetValue(100, ref 1)) + + // Empty ID + let id = Map.empty :> IReadOnlyDictionary // Note no type args + Assert.IsFalse(id.ContainsKey(5)) + CheckThrowsKeyNotFoundException(fun () -> id.[1] |> ignore) + CollectionAssert.AreEqual(id.Keys, [| |] ) + CollectionAssert.AreEqual(id.Values, [| |] ) +#endif + // Base class methods [] member this.ObjectGetHashCode() = @@ -265,12 +367,12 @@ type MapType() = for i = 0 to l.Count - 1 do Assert.AreEqual(i*i, l.[i]) Assert.AreEqual(i*i, l.Item(i)) - + // Invalid index let l = (Map.ofArray [|(1,1);(2,4);(3,9)|]) CheckThrowsKeyNotFoundException(fun () -> l.[ -1 ] |> ignore) CheckThrowsKeyNotFoundException(fun () -> l.[1000] |> ignore) - + [] member this.Remove() = diff --git a/src/fsharp/FSharp.Core/map.fs b/src/fsharp/FSharp.Core/map.fs index fd8012c1707..b765261d165 100644 --- a/src/fsharp/FSharp.Core/map.fs +++ b/src/fsharp/FSharp.Core/map.fs @@ -5,6 +5,7 @@ namespace Microsoft.FSharp.Collections open System open System.Collections.Generic open System.Diagnostics + open System.Reflection open Microsoft.FSharp.Core open Microsoft.FSharp.Core.LanguagePrimitives.IntrinsicOperators @@ -401,23 +402,35 @@ namespace Microsoft.FSharp.Collections i.started <- true (* The first call to MoveNext "starts" the enumeration. *) not i.stack.IsEmpty - let mkIEnumerator s = - let i = ref (mkIterator s) - { new IEnumerator<_> with + /// Enumerator for MapTree. + [] + type MapTreeEnumerator<'Key, 'Value when 'Key : comparison> (s : MapTree<'Key, 'Value>) = + let i = ref (mkIterator s) + + interface IEnumerator> with member __.Current = current !i interface System.Collections.IEnumerator with member __.Current = box (current !i) member __.MoveNext() = moveNext !i member __.Reset() = i := mkIterator s + interface System.Collections.IDictionaryEnumerator with + member __.Entry = + let kvp = current !i + System.Collections.DictionaryEntry (box kvp.Key, box kvp.Value) + member __.Key = box (current !i).Key + member __.Value = box (current !i).Value interface System.IDisposable with - member __.Dispose() = ()} + member __.Dispose() = () + let mkIEnumerator s = + new MapTreeEnumerator<_,_> (s) [>)>] [] [] [] + [] [] type Map<[]'Key,[]'Value when 'Key : comparison >(comparer: IComparer<'Key>, tree: MapTree<'Key,'Value>) = @@ -555,22 +568,25 @@ namespace Microsoft.FSharp.Collections res <- combineHash res (Unchecked.hash y) abs res - override this.Equals(that) = + member private this.EqualsImpl (that : Map<'Key,'Value>) = + use e1 = (this :> seq<_>).GetEnumerator() + use e2 = (that :> seq<_>).GetEnumerator() + let rec loop () = + let m1 = e1.MoveNext() + let m2 = e2.MoveNext() + (m1 = m2) && (not m1 || let e1c, e2c = e1.Current, e2.Current in ((e1c.Key = e2c.Key) && (Unchecked.equals e1c.Value e2c.Value) && loop())) + loop() + + override this.Equals (that) = match that with | :? Map<'Key,'Value> as that -> - use e1 = (this :> seq<_>).GetEnumerator() - use e2 = (that :> seq<_>).GetEnumerator() - let rec loop () = - let m1 = e1.MoveNext() - let m2 = e2.MoveNext() - (m1 = m2) && (not m1 || let e1c, e2c = e1.Current, e2.Current in ((e1c.Key = e2c.Key) && (Unchecked.equals e1c.Value e2c.Value) && loop())) - loop() + this.EqualsImpl that | _ -> false override this.GetHashCode() = this.ComputeHashCode() interface IEnumerable> with - member __.GetEnumerator() = MapTree.mkIEnumerator tree + member __.GetEnumerator() = upcast (MapTree.mkIEnumerator tree) interface System.Collections.IEnumerable with member __.GetEnumerator() = (MapTree.mkIEnumerator tree :> System.Collections.IEnumerator) @@ -580,7 +596,7 @@ namespace Microsoft.FSharp.Collections with get x = m.[x] and set x v = ignore(x,v); raise (NotSupportedException(SR.GetString(SR.mapCannotBeMutated))) - // REVIEW: this implementation could avoid copying the Values to an array + // REVIEW: this implementation could avoid copying the Keys to an array member s.Keys = ([| for kvp in s -> kvp.Key |] :> ICollection<'Key>) // REVIEW: this implementation could avoid copying the Values to an array @@ -591,6 +607,62 @@ namespace Microsoft.FSharp.Collections member s.TryGetValue(k,r) = if s.ContainsKey(k) then (r <- s.[k]; true) else false member s.Remove(k : 'Key) = ignore(k); (raise (NotSupportedException(SR.GetString(SR.mapCannotBeMutated))) : bool) + interface System.Collections.IDictionary with + member m.Item + with get key = + match key with + | null -> + // According to the documentation for IDictionary, implementations should + // raise ArgumentNullException if the key passed to the indexer is null. + nullArg "key" + | :? 'Key as key -> + match m.TryFind key with + | Some v -> box v + | None -> null + | _ -> null + and set key value = + if Object.ReferenceEquals (null, key) then nullArg "key" + ignore value + raise (NotSupportedException(SR.GetString(SR.mapCannotBeMutated))) + + // REVIEW: this implementation could avoid copying the Keys to an array + member s.Keys = ([| for kvp in s -> kvp.Key |] :> System.Collections.ICollection) + + // REVIEW: this implementation could avoid copying the Values to an array + member s.Values = ([| for kvp in s -> kvp.Value |] :> System.Collections.ICollection) + + member __.IsFixedSize = true + member __.IsReadOnly = true + + member __.Add(key, value) = + if Object.ReferenceEquals (null, key) then nullArg "key" + ignore value + raise (NotSupportedException(SR.GetString(SR.mapCannotBeMutated))) + member __.Clear () = raise (NotSupportedException(SR.GetString(SR.mapCannotBeMutated))) + member m.Contains key = + // IDictionary MSDN documentation says: + // "Implementations can vary in whether they allow the key to be null." + match key with + | :? 'Key as k -> + m.ContainsKey k + | null when not +#if FX_RESHAPED_REFLECTION + ((typeof<'Key>).GetTypeInfo().IsValueType) +#else + typeof<'Key>.IsValueType +#endif + -> + // If this map's key type is a reference type, a null reference could be a + // legitimate key in the map so pass it through to ContainsKey. + // Need to call ContainsKey with default(Key) instead of null to satisfy type constraints. + m.ContainsKey Unchecked.defaultof<_> + | _ -> false + + member __.GetEnumerator () = upcast (MapTree.mkIEnumerator tree) + member __.Remove key = + if Object.ReferenceEquals (null, key) then nullArg "key" + raise (NotSupportedException(SR.GetString(SR.mapCannotBeMutated))) + interface ICollection> with member __.Add(x) = ignore(x); raise (NotSupportedException(SR.GetString(SR.mapCannotBeMutated))); member __.Clear() = raise (NotSupportedException(SR.GetString(SR.mapCannotBeMutated))); @@ -600,8 +672,26 @@ namespace Microsoft.FSharp.Collections member s.IsReadOnly = true member s.Count = s.Count + interface System.Collections.ICollection with + member s.Count = s.Count + member s.IsSynchronized = true + member s.SyncRoot = upcast s + member __.CopyTo(arr,i) = + // Check arguments in accordance with what's specified by the docs for ICollection. + if Object.ReferenceEquals (null, arr) then nullArg "arr" + elif i < 0 then raise (IndexOutOfRangeException ()) + + // Raise an ArgumentException if 'arr' is multidimensional, + // or has the wrong element type. + match arr with + | :? (KeyValuePair<'Key,'Value>[]) as kvpArr -> + MapTree.copyToArray tree kvpArr i + | _ -> + raise (ArgumentException("arr")) + interface System.IComparable with - member m.CompareTo(obj: obj) = + member m.CompareTo(obj: obj) = + // REVIEW: Docs for IComparable say implementations shouldn't raise exceptions for null arguments. match obj with | :? Map<'Key,'Value> as m2-> Seq.compareWith @@ -612,7 +702,49 @@ namespace Microsoft.FSharp.Collections | _ -> invalidArg "obj" (SR.GetString(SR.notComparable)) - override x.ToString() = + interface System.IComparable> with + member m.CompareTo other = + // REVIEW: Docs for IComparable say implementations shouldn't raise exceptions for null arguments. + (m, other) + ||> Seq.compareWith (fun (kvp1 : KeyValuePair<_,_>) (kvp2 : KeyValuePair<_,_>) -> + let c = comparer.Compare(kvp1.Key,kvp2.Key) + if c <> 0 then c else Unchecked.compare kvp1.Value kvp2.Value) + + interface System.IEquatable> with + member this.Equals other = + // Not equal if the other instance is null. + not (Object.ReferenceEquals (null, other)) && this.EqualsImpl other + +#if FX_ATLEAST_45 + interface IReadOnlyCollection> with + member s.Count = s.Count + + interface IReadOnlyDictionary<'Key, 'Value> with + member m.Item + with get x = m.[x] + + // REVIEW: this implementation could avoid copying the Keys to an array + member s.Keys = ([| for kvp in s -> kvp.Key |] :> IEnumerable<'Key>) + + // REVIEW: this implementation could avoid copying the Values to an array + member s.Values = ([| for kvp in s -> kvp.Value |] :> IEnumerable<'Value>) + + member s.ContainsKey(k) = s.ContainsKey(k) + member s.TryGetValue(k,r) = + match s.TryFind k with + | None -> + // Like Dictionary<_,_>, clear the 'out' value if the key wasn't found. + r <- Unchecked.defaultof<_> + false + | Some v -> + r <- v + true +#endif + + #if !FX_NO_DEBUG_DISPLAYS + [] + #endif + member x.StructuredFormat = match List.ofSeq (Seq.truncate 4 x) with | [] -> "map []" | [KeyValue h1] -> System.Text.StringBuilder().Append("map [").Append(LanguagePrimitives.anyToStringShowingNull h1).Append("]").ToString() @@ -620,7 +752,11 @@ namespace Microsoft.FSharp.Collections | [KeyValue h1;KeyValue h2;KeyValue h3] -> System.Text.StringBuilder().Append("map [").Append(LanguagePrimitives.anyToStringShowingNull h1).Append("; ").Append(LanguagePrimitives.anyToStringShowingNull h2).Append("; ").Append(LanguagePrimitives.anyToStringShowingNull h3).Append("]").ToString() | KeyValue h1 :: KeyValue h2 :: KeyValue h3 :: _ -> System.Text.StringBuilder().Append("map [").Append(LanguagePrimitives.anyToStringShowingNull h1).Append("; ").Append(LanguagePrimitives.anyToStringShowingNull h2).Append("; ").Append(LanguagePrimitives.anyToStringShowingNull h3).Append("; ... ]").ToString() - and + override x.ToString() = + x.StructuredFormat + + + and [] MapDebugView<'Key,'Value when 'Key : comparison>(v: Map<'Key,'Value>) = diff --git a/src/fsharp/FSharp.Core/map.fsi b/src/fsharp/FSharp.Core/map.fsi index 959acd0316f..e753cf55a1d 100644 --- a/src/fsharp/FSharp.Core/map.fsi +++ b/src/fsharp/FSharp.Core/map.fsi @@ -56,12 +56,23 @@ namespace Microsoft.FSharp.Collections /// The mapped value, or None if the key is not in the map. member TryFind: key:'Key -> 'Value option - interface IDictionary<'Key, 'Value> - interface ICollection> - interface IEnumerable> interface System.IComparable - interface System.Collections.IEnumerable + interface System.IComparable> + interface System.IEquatable> + interface System.Collections.IDictionary + interface IDictionary<'Key, 'Value> + interface System.Collections.ICollection + interface ICollection> + interface System.Collections.IEnumerable + interface IEnumerable> + +#if FX_ATLEAST_45 + interface IReadOnlyCollection> + interface IReadOnlyDictionary<'Key, 'Value> +#endif + override Equals : obj -> bool + override GetHashCode : unit -> int /// Functional programming operators related to the Map<_,_> type. [] diff --git a/src/fsharp/FSharp.Core/set.fs b/src/fsharp/FSharp.Core/set.fs index a05d8527557..df8d538024a 100644 --- a/src/fsharp/FSharp.Core/set.fs +++ b/src/fsharp/FSharp.Core/set.fs @@ -88,7 +88,7 @@ namespace Microsoft.FSharp.Collections (-2 <= (h1 - h2) && (h1 - h2) <= 2) && checkInvariant t1 && checkInvariant t2 #endif - let tolerance = 2 + let [] private tolerance = 2 let mk l k r = match l,r with @@ -514,6 +514,7 @@ namespace Microsoft.FSharp.Collections [] [>)>] [] + [] [] type Set<[]'T when 'T: comparison >(comparer:IComparer<'T>, tree: SetTree<'T>) = @@ -689,20 +690,40 @@ namespace Microsoft.FSharp.Collections override this.GetHashCode() = this.ComputeHashCode() + member private this.EqualsImpl (that : Set<'T>) = + use e1 = (this :> seq<_>).GetEnumerator() + use e2 = (that :> seq<_>).GetEnumerator() + let rec loop () = + let m1 = e1.MoveNext() + let m2 = e2.MoveNext() + (m1 = m2) && (not m1 || ((e1.Current = e2.Current) && loop())) + loop() + override this.Equals(that) = match that with | :? Set<'T> as that -> - use e1 = (this :> seq<_>).GetEnumerator() - use e2 = (that :> seq<_>).GetEnumerator() - let rec loop () = - let m1 = e1.MoveNext() - let m2 = e2.MoveNext() - (m1 = m2) && (not m1 || ((e1.Current = e2.Current) && loop())) - loop() + this.EqualsImpl that | _ -> false interface System.IComparable with - member this.CompareTo(that: obj) = SetTree.compare this.Comparer this.Tree ((that :?> Set<'T>).Tree) + member this.CompareTo(that: obj) = + match that with + | :? Set<'T> as other -> + SetTree.compare this.Comparer this.Tree other.Tree + | _ -> + invalidArg "that" (SR.GetString(SR.notComparable)) + + interface System.IComparable> with + member this.CompareTo other = + // According to the MSDN docs for IComparable, implementations of this + // method shouldn't raise exceptions when passed a null 'other' instance. + if Object.ReferenceEquals (null, other) then 1 // 'this' > 'other' + else SetTree.compare this.Comparer this.Tree other.Tree + + interface System.IEquatable> with + member this.Equals other = + // Not equal when the other instance is null. + not (Object.ReferenceEquals (null, other)) && this.EqualsImpl other interface ICollection<'T> with member s.Add(x) = ignore(x); raise (new System.NotSupportedException("ReadOnlyCollection")) @@ -711,7 +732,24 @@ namespace Microsoft.FSharp.Collections member s.Contains(x) = SetTree.mem s.Comparer x s.Tree member s.CopyTo(arr,i) = SetTree.copyToArray s.Tree arr i member s.IsReadOnly = true - member s.Count = SetTree.count s.Tree + member s.Count = SetTree.count s.Tree + + interface System.Collections.ICollection with + member s.Count = SetTree.count s.Tree + member s.IsSynchronized = true + member s.SyncRoot = upcast s + member s.CopyTo(arr,i) = + // Check arguments in accordance with what's specified by the docs for ICollection. + if Object.ReferenceEquals (null, arr) then nullArg "arr" + elif i < 0 then raise (IndexOutOfRangeException ()) + + // Raise an ArgumentException if 'arr' is multidimensional, + // or has the wrong element type. + match arr with + | :? ('T[]) as arr -> + SetTree.copyToArray s.Tree arr i + | _ -> + raise (ArgumentException("arr")) interface IEnumerable<'T> with member s.GetEnumerator() = SetTree.mkIEnumerator s.Tree @@ -719,6 +757,11 @@ namespace Microsoft.FSharp.Collections interface IEnumerable with override s.GetEnumerator() = (SetTree.mkIEnumerator s.Tree :> IEnumerator) +#if FX_ATLEAST_45 + interface IReadOnlyCollection<'T> with + member s.Count = SetTree.count s.Tree +#endif + static member Singleton(x:'T) : Set<'T> = Set<'T>.Empty.Add(x) new (elements : seq<'T>) = @@ -731,7 +774,10 @@ namespace Microsoft.FSharp.Collections let comparer = LanguagePrimitives.FastGenericComparer<'T> Set(comparer,SetTree.ofArray comparer arr) - override x.ToString() = + #if !FX_NO_DEBUG_DISPLAYS + [] + #endif + member x.StructuredFormat = match List.ofSeq (Seq.truncate 4 x) with | [] -> "set []" | [h1] -> System.Text.StringBuilder().Append("set [").Append(LanguagePrimitives.anyToStringShowingNull h1).Append("]").ToString() @@ -739,6 +785,9 @@ namespace Microsoft.FSharp.Collections | [h1;h2;h3] -> System.Text.StringBuilder().Append("set [").Append(LanguagePrimitives.anyToStringShowingNull h1).Append("; ").Append(LanguagePrimitives.anyToStringShowingNull h2).Append("; ").Append(LanguagePrimitives.anyToStringShowingNull h3).Append("]").ToString() | h1 :: h2 :: h3 :: _ -> System.Text.StringBuilder().Append("set [").Append(LanguagePrimitives.anyToStringShowingNull h1).Append("; ").Append(LanguagePrimitives.anyToStringShowingNull h2).Append("; ").Append(LanguagePrimitives.anyToStringShowingNull h3).Append("; ... ]").ToString() + override x.ToString() = + x.StructuredFormat + and [] SetDebugView<'T when 'T : comparison>(v: Set<'T>) = diff --git a/src/fsharp/FSharp.Core/set.fsi b/src/fsharp/FSharp.Core/set.fsi index e9dc5d4d6d3..3695631e5a2 100644 --- a/src/fsharp/FSharp.Core/set.fsi +++ b/src/fsharp/FSharp.Core/set.fsi @@ -90,12 +90,21 @@ namespace Microsoft.FSharp.Collections /// Returns the highest element in the set according to the ordering being used for the set. member MaximumElement: 'T - interface ICollection<'T> - interface IEnumerable<'T> - interface System.Collections.IEnumerable + interface IEnumerable<'T> + interface System.Collections.IEnumerable + interface ICollection<'T> + interface System.Collections.ICollection + interface System.IComparable> interface System.IComparable + interface System.IEquatable> + +#if FX_ATLEAST_45 + interface IReadOnlyCollection<'T> +#endif + override Equals : obj -> bool + override GetHashCode : unit -> int namespace Microsoft.FSharp.Collections