From 13e2e662c7211f3397f202738c34124c0693555a Mon Sep 17 00:00:00 2001 From: Jack Pappas Date: Thu, 22 Dec 2016 19:58:57 -0500 Subject: [PATCH 1/3] For Map and Set types, implement non-generic ICollection and IDictionary (Map only); also implement generic IComparable`1 and IEquatable`1. When targeting .NET 4.5 and above, Set and Map now also implement IReadOnlyCollection`1 and IReadOnlyDictionary`1 (Map only). Includes some unit tests for the new implementations but more need to be added to ensure they work as expected and match the behavior of other .NET collections as closely as possible. --- .../Microsoft.FSharp.Collections/MapType.fs | 112 +++++++++++- src/fsharp/FSharp.Core/map.fs | 159 ++++++++++++++++-- src/fsharp/FSharp.Core/map.fsi | 19 ++- src/fsharp/FSharp.Core/set.fs | 62 +++++-- src/fsharp/FSharp.Core/set.fsi | 15 +- 5 files changed, 329 insertions(+), 38 deletions(-) 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 60b581d41f8..14db34f9dc5 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,35 @@ 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)) + CheckThrowsKeyNotFoundException(fun () -> id.[1] |> ignore) + 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 +138,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 +195,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 +365,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 1fb5a566f0f..a645891d45e 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,16 +402,28 @@ 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) @@ -562,22 +575,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) @@ -587,7 +603,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 @@ -598,6 +614,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))); @@ -607,8 +679,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 @@ -619,6 +709,45 @@ namespace Microsoft.FSharp.Collections | _ -> invalidArg "obj" (SR.GetString(SR.notComparable)) + 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 + override x.ToString() = match List.ofSeq (Seq.truncate 4 x) with | [] -> "map []" diff --git a/src/fsharp/FSharp.Core/map.fsi b/src/fsharp/FSharp.Core/map.fsi index faec0292dfb..d022f7759b0 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 6b31169c905..e5b069f86ec 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 @@ -698,20 +698,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")) @@ -720,7 +740,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 @@ -728,6 +765,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>) = diff --git a/src/fsharp/FSharp.Core/set.fsi b/src/fsharp/FSharp.Core/set.fsi index 8ee184ae402..c943f424b5a 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 From ecc7c142fb19929ab62ee6225354a0b132254a81 Mon Sep 17 00:00:00 2001 From: Jack Pappas Date: Fri, 30 Dec 2016 08:50:51 -0500 Subject: [PATCH 2/3] Fix IDictionary item getter test for missing key --- .../FSharp.Core/Microsoft.FSharp.Collections/MapType.fs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) 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 14db34f9dc5..30997c063d1 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 @@ -107,7 +107,9 @@ type MapType() = // Empty ID let id = (Map.empty : Map) :> IDictionary Assert.IsFalse(id.Contains(box 5)) - CheckThrowsKeyNotFoundException(fun () -> id.[1] |> ignore) + // 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, [| |] ) From 0499875950416bce0b86791c9ac372dc4827628e Mon Sep 17 00:00:00 2001 From: Jack Pappas Date: Thu, 5 Jan 2017 20:41:39 -0500 Subject: [PATCH 3/3] Decorate Map and Set with StructuredFormatDisplay Map and Set already had ToString() overloads which returned a structured format string. However, adding IDictionary to Map caused fsi to print Map instances differently due to special handling it has for IDictionary instances. Exposing the structured formatting via the StructuredFormatDisplayAttribute works around this since it takes precedence over the default printers (formatting functions) in fsi. --- src/fsharp/FSharp.Core/map.fs | 9 ++++++++- src/fsharp/FSharp.Core/set.fs | 9 ++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/fsharp/FSharp.Core/map.fs b/src/fsharp/FSharp.Core/map.fs index a645891d45e..5b96f96a467 100644 --- a/src/fsharp/FSharp.Core/map.fs +++ b/src/fsharp/FSharp.Core/map.fs @@ -435,6 +435,7 @@ namespace Microsoft.FSharp.Collections #endif [] [] + [] [] type Map<[]'Key,[]'Value when 'Key : comparison >(comparer: IComparer<'Key>, tree: MapTree<'Key,'Value>) = @@ -748,7 +749,10 @@ namespace Microsoft.FSharp.Collections true #endif - override x.ToString() = + #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() @@ -756,6 +760,9 @@ 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() + override x.ToString() = + x.StructuredFormat + #if !FX_NO_DEBUG_PROXIES and diff --git a/src/fsharp/FSharp.Core/set.fs b/src/fsharp/FSharp.Core/set.fs index e5b069f86ec..017402841e3 100644 --- a/src/fsharp/FSharp.Core/set.fs +++ b/src/fsharp/FSharp.Core/set.fs @@ -518,6 +518,7 @@ namespace Microsoft.FSharp.Collections #if !FX_NO_DEBUG_DISPLAYS [] #endif + [] [] type Set<[]'T when 'T : comparison >(comparer:IComparer<'T>, tree: SetTree<'T>) = @@ -782,7 +783,10 @@ namespace Microsoft.FSharp.Collections let comparer = LanguagePrimitives.FastGenericComparer<'T> new 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() @@ -790,6 +794,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>) =