diff --git a/src/libraries/System.Collections.Immutable/ref/System.Collections.Immutable.cs b/src/libraries/System.Collections.Immutable/ref/System.Collections.Immutable.cs index b70fcca6b235c6..c04f557cf54378 100644 --- a/src/libraries/System.Collections.Immutable/ref/System.Collections.Immutable.cs +++ b/src/libraries/System.Collections.Immutable/ref/System.Collections.Immutable.cs @@ -90,8 +90,12 @@ public static partial class ImmutableArray public static System.Collections.Immutable.ImmutableArray Create(T item1, T item2, T item3, T item4) { throw null; } public static System.Collections.Immutable.ImmutableArray Create(params T[]? items) { throw null; } public static System.Collections.Immutable.ImmutableArray Create(T[] items, int start, int length) { throw null; } + public static System.Collections.Immutable.ImmutableArray Create(System.ReadOnlySpan items) { throw null; } + public static System.Collections.Immutable.ImmutableArray Create(System.Span items) { throw null; } public static System.Collections.Immutable.ImmutableArray ToImmutableArray(this System.Collections.Generic.IEnumerable items) { throw null; } public static System.Collections.Immutable.ImmutableArray ToImmutableArray(this System.Collections.Immutable.ImmutableArray.Builder builder) { throw null; } + public static System.Collections.Immutable.ImmutableArray ToImmutableArray(this System.ReadOnlySpan items) { throw null; } + public static System.Collections.Immutable.ImmutableArray ToImmutableArray(this System.Span items) { throw null; } } public readonly partial struct ImmutableArray : System.Collections.Generic.ICollection, System.Collections.Generic.IEnumerable, System.Collections.Generic.IList, System.Collections.Generic.IReadOnlyCollection, System.Collections.Generic.IReadOnlyList, System.Collections.ICollection, System.Collections.IEnumerable, System.Collections.IList, System.Collections.Immutable.IImmutableList, System.Collections.IStructuralComparable, System.Collections.IStructuralEquatable, System.IEquatable> { @@ -118,8 +122,11 @@ public static partial class ImmutableArray public System.Collections.Immutable.ImmutableArray Add(T item) { throw null; } public System.Collections.Immutable.ImmutableArray AddRange(System.Collections.Generic.IEnumerable items) { throw null; } public System.Collections.Immutable.ImmutableArray AddRange(System.Collections.Immutable.ImmutableArray items) { throw null; } + public System.Collections.Immutable.ImmutableArray AddRange(System.ReadOnlySpan items) { throw null; } + public System.Collections.Immutable.ImmutableArray AddRange(params T[] items) { throw null; } public System.ReadOnlyMemory AsMemory() { throw null; } public System.ReadOnlySpan AsSpan() { throw null; } + public System.ReadOnlySpan AsSpan(int start, int length) { throw null; } public System.Collections.Immutable.ImmutableArray< #nullable disable TOther @@ -140,6 +147,7 @@ public static System.Collections.Immutable.ImmutableArray< public void CopyTo(int sourceIndex, T[] destination, int destinationIndex, int length) { } public void CopyTo(T[] destination) { } public void CopyTo(T[] destination, int destinationIndex) { } + public void CopyTo(System.Span destination) { } public bool Equals(System.Collections.Immutable.ImmutableArray other) { throw null; } public override bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] object? obj) { throw null; } public System.Collections.Immutable.ImmutableArray.Enumerator GetEnumerator() { throw null; } @@ -152,6 +160,8 @@ public void CopyTo(T[] destination, int destinationIndex) { } public System.Collections.Immutable.ImmutableArray Insert(int index, T item) { throw null; } public System.Collections.Immutable.ImmutableArray InsertRange(int index, System.Collections.Generic.IEnumerable items) { throw null; } public System.Collections.Immutable.ImmutableArray InsertRange(int index, System.Collections.Immutable.ImmutableArray items) { throw null; } + public System.Collections.Immutable.ImmutableArray InsertRange(int index, T[] items) { throw null; } + public System.Collections.Immutable.ImmutableArray InsertRange(int index, System.ReadOnlySpan items) { throw null; } public ref readonly T ItemRef(int index) { throw null; } public int LastIndexOf(T item) { throw null; } public int LastIndexOf(T item, int startIndex) { throw null; } @@ -171,9 +181,12 @@ public void CopyTo(T[] destination, int destinationIndex) { } public System.Collections.Immutable.ImmutableArray RemoveRange(System.Collections.Immutable.ImmutableArray items) { throw null; } public System.Collections.Immutable.ImmutableArray RemoveRange(System.Collections.Immutable.ImmutableArray items, System.Collections.Generic.IEqualityComparer? equalityComparer) { throw null; } public System.Collections.Immutable.ImmutableArray RemoveRange(int index, int length) { throw null; } + public System.Collections.Immutable.ImmutableArray RemoveRange(System.ReadOnlySpan items, System.Collections.Generic.IEqualityComparer? equalityComparer = null) { throw null; } + public System.Collections.Immutable.ImmutableArray RemoveRange(T[] items, System.Collections.Generic.IEqualityComparer? equalityComparer = null) { throw null; } public System.Collections.Immutable.ImmutableArray Replace(T oldValue, T newValue) { throw null; } public System.Collections.Immutable.ImmutableArray Replace(T oldValue, T newValue, System.Collections.Generic.IEqualityComparer? equalityComparer) { throw null; } public System.Collections.Immutable.ImmutableArray SetItem(int index, T item) { throw null; } + public System.Collections.Immutable.ImmutableArray Slice(int start, int length) { throw null; } public System.Collections.Immutable.ImmutableArray Sort() { throw null; } public System.Collections.Immutable.ImmutableArray Sort(System.Collections.Generic.IComparer? comparer) { throw null; } public System.Collections.Immutable.ImmutableArray Sort(System.Comparison comparison) { throw null; } @@ -226,9 +239,12 @@ public void AddRange(T[] items, int length) { } public void AddRange(System.Collections.Immutable.ImmutableArray items) where TDerived : T { } public void AddRange(System.Collections.Immutable.ImmutableArray.Builder items) where TDerived : T { } public void AddRange(TDerived[] items) where TDerived : T { } + public void AddRange(System.ReadOnlySpan items) { } + public void AddRange(System.ReadOnlySpan items) where TDerived : T { } public void Clear() { } public bool Contains(T item) { throw null; } public void CopyTo(T[] array, int index) { } + public void CopyTo(System.Span destination) { } public System.Collections.Generic.IEnumerator GetEnumerator() { throw null; } public int IndexOf(T item) { throw null; } public int IndexOf(T item, int startIndex) { throw null; } diff --git a/src/libraries/System.Collections.Immutable/ref/System.Collections.Immutable.csproj b/src/libraries/System.Collections.Immutable/ref/System.Collections.Immutable.csproj index 01c989df4fef66..10f55a7ef52876 100644 --- a/src/libraries/System.Collections.Immutable/ref/System.Collections.Immutable.csproj +++ b/src/libraries/System.Collections.Immutable/ref/System.Collections.Immutable.csproj @@ -5,6 +5,7 @@ + diff --git a/src/libraries/System.Collections.Immutable/ref/System.Collections.Immutable.netcoreapp.cs b/src/libraries/System.Collections.Immutable/ref/System.Collections.Immutable.netcoreapp.cs new file mode 100644 index 00000000000000..d6c786d33e236f --- /dev/null +++ b/src/libraries/System.Collections.Immutable/ref/System.Collections.Immutable.netcoreapp.cs @@ -0,0 +1,13 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// ------------------------------------------------------------------------------ +// Changes to this file must follow the https://aka.ms/api-review process. +// ------------------------------------------------------------------------------ + +namespace System.Collections.Immutable +{ + public readonly partial struct ImmutableArray : System.Collections.Generic.ICollection, System.Collections.Generic.IEnumerable, System.Collections.Generic.IList, System.Collections.Generic.IReadOnlyCollection, System.Collections.Generic.IReadOnlyList, System.Collections.ICollection, System.Collections.IEnumerable, System.Collections.IList, System.Collections.Immutable.IImmutableList, System.Collections.IStructuralComparable, System.Collections.IStructuralEquatable, System.IEquatable> + { + public System.ReadOnlySpan AsSpan(System.Range range) { throw null; } + } +} diff --git a/src/libraries/System.Collections.Immutable/src/System.Collections.Immutable.csproj b/src/libraries/System.Collections.Immutable/src/System.Collections.Immutable.csproj index 9f95b79a7d3ab5..c66bec757e4106 100644 --- a/src/libraries/System.Collections.Immutable/src/System.Collections.Immutable.csproj +++ b/src/libraries/System.Collections.Immutable/src/System.Collections.Immutable.csproj @@ -49,6 +49,7 @@ System.Collections.Immutable.ImmutableStack<T> + diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray.cs index 5558be5bfdbdee..5830fa943a47af 100644 --- a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray.cs +++ b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray.cs @@ -32,7 +32,7 @@ public static ImmutableArray Create() /// /// The type of element stored in the array. /// The element to store in the array. - /// A 1-element array. + /// A 1-element immutable array containing the specified item. public static ImmutableArray Create(T item) { T[] array = new[] { item }; @@ -45,7 +45,7 @@ public static ImmutableArray Create(T item) /// The type of element stored in the array. /// The first element to store in the array. /// The second element to store in the array. - /// A 2-element array. + /// A 2-element immutable array containing the specified items. public static ImmutableArray Create(T item1, T item2) { T[] array = new[] { item1, item2 }; @@ -59,7 +59,7 @@ public static ImmutableArray Create(T item1, T item2) /// The first element to store in the array. /// The second element to store in the array. /// The third element to store in the array. - /// A 3-element array. + /// A 3-element immutable array containing the specified items. public static ImmutableArray Create(T item1, T item2, T item3) { T[] array = new[] { item1, item2, item3 }; @@ -74,13 +74,63 @@ public static ImmutableArray Create(T item1, T item2, T item3) /// The second element to store in the array. /// The third element to store in the array. /// The fourth element to store in the array. - /// A 4-element array. + /// A 4-element immutable array containing the specified items. public static ImmutableArray Create(T item1, T item2, T item3, T item4) { T[] array = new[] { item1, item2, item3, item4 }; return new ImmutableArray(array); } + /// + /// Creates an with the specified elements. + /// + /// The type of element stored in the array. + /// The elements to store in the array. + /// An immutable array containing the specified items. + public static ImmutableArray Create(ReadOnlySpan items) + { + if (items.IsEmpty) + { + return ImmutableArray.Empty; + } + + T[] array = items.ToArray(); + return new ImmutableArray(array); + } + + /// + /// Creates an with the specified elements. + /// + /// The type of element stored in the array. + /// The elements to store in the array. + /// An immutable array containing the specified items. + public static ImmutableArray Create(Span items) + { + return Create((ReadOnlySpan)items); + } + + /// + /// Produce an immutable array of contents from specified elements. + /// + /// The type of element in the list. + /// The elements to store in the array. + /// An immutable array containing the specified items. + public static ImmutableArray ToImmutableArray(this ReadOnlySpan items) + { + return Create(items); + } + + /// + /// Produce an immutable array of contents from specified elements. + /// + /// The type of element in the list. + /// The elements to store in the array. + /// An immutable array containing the specified items. + public static ImmutableArray ToImmutableArray(this Span items) + { + return Create((ReadOnlySpan)items); + } + /// /// Creates an populated with the contents of the specified sequence. /// @@ -133,7 +183,7 @@ public static ImmutableArray CreateRange(IEnumerable items) /// /// The type of element stored in the array. /// The elements to store in the array. - /// An immutable array. + /// An immutable array containing the specified items. public static ImmutableArray Create(params T[]? items) { if (items == null || items.Length == 0) @@ -368,7 +418,7 @@ public static ImmutableArray.Builder CreateBuilder(int initialCapacity) /// /// The type of element in the sequence. /// The sequence to enumerate. - /// An immutable array. + /// An immutable array containing the specified items. public static ImmutableArray ToImmutableArray(this IEnumerable items) { if (items is ImmutableArray) @@ -383,7 +433,7 @@ public static ImmutableArray ToImmutableArray(this IEnumerable /// Returns an immutable copy of the current contents of the builder's collection. /// /// The builder to create the immutable array from. - /// An immutable array. + /// An immutable array containing the specified items from . public static ImmutableArray ToImmutableArray(this ImmutableArray.Builder builder) { Requires.NotNull(builder, nameof(builder)); diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.Builder.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.Builder.cs index bdbb036a4b7e74..924a919205a38b 100644 --- a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.Builder.cs +++ b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.Builder.cs @@ -348,7 +348,35 @@ public void AddRange(ImmutableArray items, int length) /// /// Adds the specified items to the end of the array. /// - /// The items. + /// The items to add at the end of the array. + public void AddRange(ReadOnlySpan items) + { + int offset = this.Count; + this.Count += items.Length; + + items.CopyTo(new Span(_elements, offset, items.Length)); + } + + /// + /// Adds the specified items to the end of the array. + /// + /// The items to add at the end of the array. + public void AddRange(ReadOnlySpan items) where TDerived : T + { + int offset = this.Count; + this.Count += items.Length; + + var elements = new Span(_elements, offset, items.Length); + for (int i = 0; i < items.Length; i++) + { + elements[i] = items[i]; + } + } + + /// + /// Adds the specified items to the end of the array. + /// + /// The items to add at the end of the array. public void AddRange(ImmutableArray items) where TDerived : T { if (items.array != null) @@ -360,7 +388,7 @@ public void AddRange(ImmutableArray items) where TDerived : /// /// Adds the specified items to the end of the array. /// - /// The items. + /// The items to add at the end of the array. public void AddRange(Builder items) { Requires.NotNull(items, nameof(items)); @@ -370,7 +398,7 @@ public void AddRange(Builder items) /// /// Adds the specified items to the end of the array. /// - /// The items. + /// The items to add at the end of the array. public void AddRange(ImmutableArray.Builder items) where TDerived : T { Requires.NotNull(items, nameof(items)); @@ -705,6 +733,16 @@ public void Sort(int index, int count, IComparer? comparer) } } + /// + /// Copies the current contents to the specified . + /// + /// The to copy to. + public void CopyTo(Span destination) + { + Requires.Range(this.Count <= destination.Length, nameof(destination)); + new ReadOnlySpan(_elements, 0, this.Count).CopyTo(destination); + } + /// /// Returns an enumerator for the contents of the array. /// diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.cs index e8661bd05e509a..8b7ebb92f95244 100644 --- a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.cs +++ b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.cs @@ -193,7 +193,7 @@ public int IndexOf(T item, int startIndex, int count, IEqualityComparer? equa public int LastIndexOf(T item) { var self = this; - if (self.Length == 0) + if (self.IsEmpty) { return -1; } @@ -210,7 +210,7 @@ public int LastIndexOf(T item) public int LastIndexOf(T item, int startIndex) { var self = this; - if (self.Length == 0 && startIndex == 0) + if (self.IsEmpty && startIndex == 0) { return -1; } @@ -292,7 +292,7 @@ public ImmutableArray Insert(int index, T item) self.ThrowNullRefIfNotInitialized(); Requires.Range(index >= 0 && index <= self.Length, nameof(index)); - if (self.Length == 0) + if (self.IsEmpty) { return ImmutableArray.Create(item); } @@ -325,7 +325,7 @@ public ImmutableArray InsertRange(int index, IEnumerable items) Requires.Range(index >= 0 && index <= self.Length, nameof(index)); Requires.NotNull(items, nameof(items)); - if (self.Length == 0) + if (self.IsEmpty) { return ImmutableArray.CreateRange(items); } @@ -356,7 +356,7 @@ public ImmutableArray InsertRange(int index, IEnumerable items) if (!items.TryCopyTo(tmp, index)) { int sequenceIndex = index; - foreach (var item in items) + foreach (T item in items) { tmp[sequenceIndex++] = item; } @@ -382,25 +382,12 @@ public ImmutableArray InsertRange(int index, ImmutableArray items) { return items; } - else if (items.IsEmpty) + if (items.IsEmpty) { return self; } - T[] tmp = new T[self.Length + items.Length]; - - if (index != 0) - { - Array.Copy(self.array!, tmp, index); - } - if (index != self.Length) - { - Array.Copy(self.array!, index, tmp, index + items.Length, self.Length - index); - } - - Array.Copy(items.array!, 0, tmp, index, items.Length); - - return new ImmutableArray(tmp); + return self.InsertSpanRangeInternal(index, items.AsSpan()); } /// @@ -411,7 +398,7 @@ public ImmutableArray InsertRange(int index, ImmutableArray items) public ImmutableArray Add(T item) { var self = this; - if (self.Length == 0) + if (self.IsEmpty) { return ImmutableArray.Create(item); } @@ -589,14 +576,13 @@ public ImmutableArray RemoveRange(IEnumerable items, IEqualityComparer? Requires.NotNull(items, nameof(items)); var indicesToRemove = new SortedSet(); - foreach (var item in items) + foreach (T item in items) { - int index = self.IndexOf(item, 0, self.Length, equalityComparer); - while (index >= 0 && !indicesToRemove.Add(index) && index + 1 < self.Length) + int index = -1; + do { - // This is a duplicate of one we've found. Try hard to find another instance in the list to remove. index = self.IndexOf(item, index + 1, equalityComparer); - } + } while (index >= 0 && !indicesToRemove.Add(index) && index < self.Length - 1); } return self.RemoveAtRange(indicesToRemove); @@ -626,22 +612,9 @@ public ImmutableArray RemoveRange(ImmutableArray items) /// public ImmutableArray RemoveRange(ImmutableArray items, IEqualityComparer? equalityComparer) { - var self = this; Requires.NotNull(items.array!, nameof(items)); - if (items.IsEmpty) - { - self.ThrowNullRefIfNotInitialized(); - return self; - } - else if (items.Length == 1) - { - return self.Remove(items[0], equalityComparer); - } - else - { - return self.RemoveRange(items.array!, equalityComparer); - } + return RemoveRange(items.AsSpan(), equalityComparer); } /// @@ -791,6 +764,169 @@ public IEnumerable OfType() return self.array.OfType(); } + /// + /// Adds the specified values to this list. + /// + /// The values to add. + /// A new list with the elements added. + public ImmutableArray AddRange(ReadOnlySpan items) + { + var self = this; + return self.InsertRange(self.Length, items); + } + + /// + /// Adds the specified values to this list. + /// + /// The values to add. + /// A new list with the elements added. + public ImmutableArray AddRange(params T[] items) + { + var self = this; + return self.InsertRange(self.Length, items); + } + + /// + /// Creates a over the portion of current beginning at a specified position for a specified length. + /// + /// The index at which to begin the span. + /// The number of items in the span. + /// The representation of the + public ReadOnlySpan AsSpan(int start, int length) => new ReadOnlySpan(array, start, length); + + /// + /// Copies the elements of current to an . + /// + /// The that is the destination of the elements copied from current . + public void CopyTo(Span destination) + { + var self = this; + self.ThrowNullRefIfNotInitialized(); + Requires.Range(self.Length <= destination.Length, nameof(destination)); + + self.AsSpan().CopyTo(destination); + } + + /// + /// Inserts the specified values at the specified index. + /// + /// The index at which to insert the value. + /// The elements to insert. + /// The new immutable collection. + public ImmutableArray InsertRange(int index, T[] items) + { + var self = this; + self.ThrowNullRefIfNotInitialized(); + Requires.Range(index >= 0 && index <= self.Length, nameof(index)); + Requires.NotNull(items, nameof(items)); + + if (items.Length == 0) + { + return self; + } + if (self.IsEmpty) + { + return new ImmutableArray(items); + } + + return self.InsertSpanRangeInternal(index, items); + } + + /// + /// Inserts the specified values at the specified index. + /// + /// The index at which to insert the value. + /// The elements to insert. + /// The new immutable collection. + public ImmutableArray InsertRange(int index, ReadOnlySpan items) + { + var self = this; + self.ThrowNullRefIfNotInitialized(); + Requires.Range(index >= 0 && index <= self.Length, nameof(index)); + + if (items.IsEmpty) + { + return self; + } + if (self.IsEmpty) + { + return items.ToImmutableArray(); + } + + return self.InsertSpanRangeInternal(index, items); + } + + /// + /// Removes the specified values from this list. + /// + /// The items to remove if matches are found in this list. + /// + /// The equality comparer to use in the search. + /// + /// + /// A new list with the elements removed. + /// + public ImmutableArray RemoveRange(ReadOnlySpan items, IEqualityComparer? equalityComparer = null) + { + var self = this; + self.ThrowNullRefIfNotInitialized(); + + if (items.IsEmpty || self.IsEmpty) + { + return self; + } + + if (items.Length == 1) + { + return self.Remove(items[0], equalityComparer); + } + + var indicesToRemove = new SortedSet(); + foreach (T item in items) + { + int index = -1; + do + { + index = self.IndexOf(item, index + 1, equalityComparer); + } while (index >= 0 && !indicesToRemove.Add(index) && index < self.Length - 1); + } + + return self.RemoveAtRange(indicesToRemove); + } + + /// + /// Removes the specified values from this list. + /// + /// The items to remove if matches are found in this list. + /// + /// The equality comparer to use in the search. + /// + /// + /// A new list with the elements removed. + /// + public ImmutableArray RemoveRange(T[] items, IEqualityComparer? equalityComparer = null) + { + var self = this; + self.ThrowNullRefIfNotInitialized(); + + Requires.NotNull(items, nameof(items)); + + return self.RemoveRange(new ReadOnlySpan(items), equalityComparer); + } + + /// + /// Forms a slice out of the current starting at a specified index for a specified length. + /// + /// The index at which to begin this slice. + /// The desired length for the slice. + /// A that consists of length elements from the current starting at start. + public ImmutableArray Slice(int start, int length) + { + var self = this; + self.ThrowNullRefIfNotInitialized(); + return ImmutableArray.Create(self, start, length); + } + #region Explicit interface methods void IList.Insert(int index, T item) @@ -1228,7 +1364,7 @@ private ImmutableArray RemoveAtRange(ICollection indicesToRemove) int copied = 0; int removed = 0; int lastIndexRemoved = -1; - foreach (var indexToRemove in indicesToRemove) + foreach (int indexToRemove in indicesToRemove) { int copyLength = lastIndexRemoved == -1 ? indexToRemove : (indexToRemove - lastIndexRemoved - 1); Debug.Assert(indexToRemove > lastIndexRemoved); // We require that the input be a sorted set. @@ -1242,5 +1378,25 @@ private ImmutableArray RemoveAtRange(ICollection indicesToRemove) return new ImmutableArray(newArray); } + + private ImmutableArray InsertSpanRangeInternal(int index, ReadOnlySpan items) + { + Debug.Assert(array != null); + Debug.Assert(!IsEmpty); + Debug.Assert(!items.IsEmpty); + + var tmp = new T[Length + items.Length]; + if (index != 0) + { + Array.Copy(array!, tmp, index); + } + items.CopyTo(new Span(tmp, index, items.Length)); + if (index != Length) + { + Array.Copy(array!, index, tmp, index + items.Length, Length - index); + } + + return new ImmutableArray(tmp); + } } } diff --git a/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.netcoreapp.cs b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.netcoreapp.cs new file mode 100644 index 00000000000000..cf5c0013b7a31d --- /dev/null +++ b/src/libraries/System.Collections.Immutable/src/System/Collections/Immutable/ImmutableArray_1.netcoreapp.cs @@ -0,0 +1,24 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; + +namespace System.Collections.Immutable +{ + public readonly partial struct ImmutableArray : IReadOnlyList, IList, IEquatable>, IList, IImmutableArray, IStructuralComparable, IStructuralEquatable, IImmutableList + { + /// + /// Creates a over the portion of current based on specified + /// + /// Range in current . + /// The representation of the + public ReadOnlySpan AsSpan(Range range) + { + var self = this; + self.ThrowNullRefIfNotInitialized(); + + (int start, int length) = range.GetOffsetAndLength(self.Length); + return new ReadOnlySpan(self.array, start, length); + } + } +} diff --git a/src/libraries/System.Collections.Immutable/tests/ImmutableArrayBuilderTest.cs b/src/libraries/System.Collections.Immutable/tests/ImmutableArrayBuilderTest.cs index 5a70cb43e1e31c..5b34fe28d84ffb 100644 --- a/src/libraries/System.Collections.Immutable/tests/ImmutableArrayBuilderTest.cs +++ b/src/libraries/System.Collections.Immutable/tests/ImmutableArrayBuilderTest.cs @@ -12,6 +12,13 @@ namespace System.Collections.Immutable.Tests { public class ImmutableArrayBuilderTest : SimpleElementImmutablesTestBase { + public static IEnumerable BuilderAddRangeData() + { + yield return new object[] { new[] { "a", "b" }, Array.Empty(), new[] { "a", "b" } }; + yield return new object[] { Array.Empty(), new[] { "a", "b" }, new[] { "a", "b" } }; + yield return new object[] { new[] { "a", "b" }, new[] { "c", "d" }, new[] { "a", "b", "c", "d" } }; + } + [Fact] public void CreateBuilderDefaultCapacity() { @@ -151,31 +158,83 @@ public void AddRangeImmutableArray() AssertExtensions.Throws("items", () => builder2.AddRange((ImmutableArray.Builder)null)); } - [Fact] - public void AddRangeDerivedArray() + [Theory] + [MemberData(nameof(BuilderAddRangeData))] + public void AddRangeDerivedArray(string[] builderElements, string[] rangeElements, string[] expectedResult) { + // Initialize builder var builder = new ImmutableArray.Builder(); - builder.AddRange(new[] { "a", "b" }); - Assert.Equal(new[] { "a", "b" }, builder); + builder.AddRange(builderElements); + + // AddRange + builder.AddRange(rangeElements); + + // Assert + Assert.Equal(expectedResult, builder); } - [Fact] - public void AddRangeDerivedImmutableArray() + [Theory] + [MemberData(nameof(BuilderAddRangeData))] + public void AddRangeSpan(string[] builderElements, string[] rangeElements, string[] expectedResult) + { + // Initialize builder + var builder = new ImmutableArray.Builder(); + builder.AddRange(builderElements); + + // AddRange + builder.AddRange(new ReadOnlySpan(rangeElements)); + + // Assert + Assert.Equal(expectedResult, builder); + } + + [Theory] + [MemberData(nameof(BuilderAddRangeData))] + public void AddRangeDerivedSpan(string[] builderElements, string[] rangeElements, string[] expectedResult) + { + // Initialize builder + var builder = new ImmutableArray.Builder(); + builder.AddRange(builderElements); + + // AddRange + builder.AddRange(new ReadOnlySpan(rangeElements)); + + // Assert + Assert.Equal(expectedResult, builder); + } + + [Theory] + [MemberData(nameof(BuilderAddRangeData))] + public void AddRangeDerivedImmutableArray(string[] builderElements, string[] rangeElements, string[] expectedResult) { + // Initialize builder var builder = new ImmutableArray.Builder(); - builder.AddRange(new[] { "a", "b" }.ToImmutableArray()); - Assert.Equal(new[] { "a", "b" }, builder); + builder.AddRange(builderElements); + + // AddRange + builder.AddRange(rangeElements.ToImmutableArray()); + + // Assert + Assert.Equal(expectedResult, builder); } - [Fact] - public void AddRangeDerivedBuilder() + [Theory] + [MemberData(nameof(BuilderAddRangeData))] + public void AddRangeDerivedBuilder(string[] builderElements, string[] rangeElements, string[] expectedResult) { + // Initialize builder + var builderBase = new ImmutableArray.Builder(); + builderBase.AddRange(builderElements); + + // Prepare another builder to add var builder = new ImmutableArray.Builder(); - builder.AddRange(new[] { "a", "b" }); + builder.AddRange(rangeElements); - var builderBase = new ImmutableArray.Builder(); + // AddRange builderBase.AddRange(builder); - Assert.Equal(new[] { "a", "b" }, builderBase); + + // Assert + Assert.Equal(expectedResult, builderBase); } [Fact] @@ -478,7 +537,7 @@ public void ToImmutableArray() } [Fact] - public void CopyTo() + public void CopyToArray() { var builder = ImmutableArray.Create(1, 2, 3).ToBuilder(); var target = new int[4]; @@ -491,6 +550,30 @@ public void CopyTo() AssertExtensions.Throws("index", () => builder.CopyTo(target, 2)); } + [Fact] + public void CopyToSpan() + { + var builder = ImmutableArray.Create(1, 2, 3).ToBuilder(); + Span span; + int[] target = new int[4]; + + // Span is longer than immutableArray + span = new Span(target); + builder.CopyTo(span); + Assert.Equal(new[] { 1, 2, 3, 0 }, target); + span.Fill(0); + + // Span has same length as immutableArray + span = new Span(target, 0, 3); + builder.CopyTo(span); + Assert.Equal(new[] { 1, 2, 3, 0 }, target); + span.Fill(0); + + // Span is shorter than immutableArray + span = new Span(target, 0, 2); + AssertExtensions.Throws("destination", span, s => builder.CopyTo(s)); + } + [Fact] public void Clear() { diff --git a/src/libraries/System.Collections.Immutable/tests/ImmutableArrayTest.cs b/src/libraries/System.Collections.Immutable/tests/ImmutableArrayTest.cs index 7349cefa67b5c3..3193a996faf5ce 100644 --- a/src/libraries/System.Collections.Immutable/tests/ImmutableArrayTest.cs +++ b/src/libraries/System.Collections.Immutable/tests/ImmutableArrayTest.cs @@ -14,7 +14,7 @@ namespace System.Collections.Immutable.Tests { - public class ImmutableArrayTest : SimpleElementImmutablesTestBase + public partial class ImmutableArrayTest : SimpleElementImmutablesTestBase { private static readonly ImmutableArray s_emptyDefault = default; // init explicitly to avoid CS0649 private static readonly ImmutableArray s_empty = ImmutableArray.Create(); @@ -47,6 +47,17 @@ public static IEnumerable StringImmutableArrayData() yield return new object[] { new[] { (string)null } }; } + public static IEnumerable RangeIndexLengthData() + { + yield return new object[] { s_empty, 0, 0 }; + yield return new object[] { s_oneElement, 1, 0 }; + yield return new object[] { s_oneElement, 0, 1 }; + yield return new object[] { s_oneElement, 0, 0 }; + yield return new object[] { new[] { 1, 2, 3, 4 }, 0, 2 }; + yield return new object[] { new[] { 1, 2, 3, 4 }, 1, 2 }; + yield return new object[] { new[] { 1, 2, 3, 4 }, 2, 2 }; + } + [Theory] [MemberData(nameof(Int32EnumerableData))] public void Clear(IEnumerable source) @@ -68,19 +79,29 @@ public void AsSpanRoundTripTests(IEnumerable source) public void AsSpanRoundTripEmptyArrayTests() { ImmutableArray immutableArray = ImmutableArray.Create(Array.Empty()); + ReadOnlySpan span = immutableArray.AsSpan(); Assert.Equal(immutableArray, span.ToArray()); Assert.Equal(immutableArray.Length, span.Length); + + ReadOnlySpan startRangedSpan = immutableArray.AsSpan(0, 0); + Assert.Equal(immutableArray, startRangedSpan.ToArray()); + Assert.Equal(immutableArray.Length, startRangedSpan.Length); } [Fact] public void AsSpanRoundTripDefaultArrayTests() { ImmutableArray immutableArray = new ImmutableArray(); - ReadOnlySpan span = immutableArray.AsSpan(); Assert.True(immutableArray.IsDefault); + + ReadOnlySpan span = immutableArray.AsSpan(); Assert.Equal(0, span.Length); Assert.True(span.IsEmpty); + + ReadOnlySpan startRangeSpan = immutableArray.AsSpan(0, 0); + Assert.Equal(0, startRangeSpan.Length); + Assert.True(startRangeSpan.IsEmpty); } [Theory] @@ -97,10 +118,36 @@ public void AsSpanRoundTripStringTests(IEnumerable source) public void AsSpanRoundTripDefaultArrayStringTests() { ImmutableArray immutableArray = new ImmutableArray(); - ReadOnlySpan span = immutableArray.AsSpan(); Assert.True(immutableArray.IsDefault); + + ReadOnlySpan span = immutableArray.AsSpan(); Assert.Equal(0, span.Length); Assert.True(span.IsEmpty); + + ReadOnlySpan startRangeSpan = immutableArray.AsSpan(0, 0); + Assert.Equal(0, startRangeSpan.Length); + Assert.True(startRangeSpan.IsEmpty); + } + + [Theory] + [MemberData(nameof(RangeIndexLengthData))] + public void AsSpanStartLength(IEnumerable source, int start, int length) + { + var array = source.ToImmutableArray(); + var expected = source.Skip(start).Take(length); + Assert.Equal(expected, array.AsSpan(start, length).ToArray()); + } + + [Theory] + [MemberData(nameof(Int32EnumerableData))] + public void AsSpanStartLengthInvalid(IEnumerable source) + { + var array = source.ToImmutableArray(); + + AssertExtensions.Throws(() => array.AsSpan(-1, 1)); + AssertExtensions.Throws(() => array.AsSpan(array.Length + 1, 1)); + AssertExtensions.Throws(() => array.AsSpan(0, -1)); + AssertExtensions.Throws(() => array.AsSpan(0, array.Length + 1)); } [Theory] @@ -155,7 +202,7 @@ public void AsMemoryRoundTripDefaultArrayStringTests() [Fact] public void CreateEnumerableElementType() { - // Create should not have the same semantics as CreateRange, except for arrays. + // Create should not have the same semantics as CreateRange, except for arrays, span and readonlySpan. // If you pass in an IEnumerable to Create, you should get an // ImmutableArray>. However, if you pass a T[] in, you should get // a ImmutableArray. @@ -168,6 +215,12 @@ public void CreateEnumerableElementType() var enumerable = Enumerable.Empty(); Assert.IsType>>(ImmutableArray.Create(enumerable)); + + Span span = Span.Empty; + Assert.IsType>(ImmutableArray.Create(span)); + + ReadOnlySpan readonlySpan = ReadOnlySpan.Empty; + Assert.IsType>(ImmutableArray.Create(readonlySpan)); } [Fact] @@ -175,6 +228,8 @@ public void CreateEmpty() { Assert.True(s_empty == ImmutableArray.Create()); Assert.True(s_empty == ImmutableArray.Create(new int[0])); + Assert.True(s_empty == ImmutableArray.Create(Span.Empty)); + Assert.True(s_empty == ImmutableArray.Create(ReadOnlySpan.Empty)); } [Theory] @@ -342,6 +397,14 @@ public void CreateFromSlice(IEnumerable source, int start, int length) Assert.Equal(source.Skip(start).Take(length), ImmutableArray.Create(source.ToArray(), start, length)); } + [Theory] + [MemberData(nameof(CreateFromSliceData))] + public void SliceFromSliceData(IEnumerable source, int start, int length) + { + var immutableArray = source.ToImmutableArray(); + Assert.Equal(source.Skip(start).Take(length), immutableArray.Slice(start, length)); + } + public static IEnumerable CreateFromSliceData() { yield return new object[] { new int[] { }, 0, 0 }; @@ -380,6 +443,15 @@ public void CreateFromSliceOfImmutableArrayOptimizations(IEnumerable source Assert.True(array == slice); // Verify that the underlying arrays are reference-equal. } + [Theory] + [MemberData(nameof(Int32EnumerableData))] + public void SliceFromCompleteImmutableArrayOptimizations(IEnumerable source) + { + var array = source.ToImmutableArray(); + var slice = array.Slice(0, array.Length); + Assert.True(array == slice); // Verify that the underlying arrays are reference-equal. + } + [Theory] [MemberData(nameof(Int32EnumerableData))] public void CreateFromSliceOfImmutableArrayEmptyReturnsSingleton(IEnumerable source) @@ -408,6 +480,25 @@ public void CreateFromSliceOfArrayInvalid(IEnumerable source) } } + [Theory] + [MemberData(nameof(Int32EnumerableData))] + public void SliceFromInvalidRange(IEnumerable source) + { + var array = source.ToImmutableArray(); + + AssertExtensions.Throws("start", () => array.Slice(-1, 0)); + AssertExtensions.Throws("start", () => array.Slice(array.Length + 1, 0)); + + AssertExtensions.Throws("length", () => array.Slice(0, -1)); + AssertExtensions.Throws("length", () => array.Slice(0, array.Length + 1)); + AssertExtensions.Throws("length", () => array.Slice(Math.Max(0, array.Length - 1), 2)); + + if (array.Length > 0) + { + AssertExtensions.Throws("length", () => array.Slice(1, array.Length)); + } + } + [Theory] [MemberData(nameof(Int32EnumerableData))] public void CreateFromSliceOfArrayEmptyReturnsSingleton(IEnumerable source) @@ -417,6 +508,24 @@ public void CreateFromSliceOfArrayEmptyReturnsSingleton(IEnumerable source) Assert.True(s_empty == slice); } + [Theory] + [MemberData(nameof(Int32EnumerableData))] + public void SliceEmptyReturnsSingleton(IEnumerable source) + { + var array = source.ToImmutableArray(); + var slice = array.Slice(Math.Min(1, array.Length), 0); + Assert.True(s_empty == slice); + } + + [Theory] + [InlineData(0, 0)] + [InlineData(0, -1)] + [InlineData(-1, 0)] + public void SliceDefaultInvalid(int start, int length) + { + TestExtensionsMethods.ValidateDefaultThisBehavior(() => s_emptyDefault.Slice(start, length)); + } + [Theory] [MemberData(nameof(Int32EnumerableData))] public void CreateFromArray(IEnumerable source) @@ -424,6 +533,20 @@ public void CreateFromArray(IEnumerable source) Assert.Equal(source, ImmutableArray.Create(source.ToArray())); } + [Theory] + [MemberData(nameof(Int32EnumerableData))] + public void CreateFromSpan(IEnumerable source) + { + Assert.Equal(source, ImmutableArray.Create(source.ToArray().AsSpan())); + } + + [Theory] + [MemberData(nameof(Int32EnumerableData))] + public void CreateFromReadOnlySpan(IEnumerable source) + { + Assert.Equal(source, ImmutableArray.Create(new ReadOnlySpan(source.ToArray()))); + } + [Fact] public void CreateFromArrayNull() { @@ -586,7 +709,12 @@ public void CastArrayBadReference() public void ToImmutableArray(IEnumerable source) { var array = source.ToImmutableArray(); + Span span = source.ToArray().AsSpan(); + Assert.Equal(source, array); + Assert.Equal(source, span.ToImmutableArray()); + Assert.Equal(source, ((ReadOnlySpan)span).ToImmutableArray()); + Assert.True(array == array.ToImmutableArray()); } @@ -1015,11 +1143,27 @@ public void AddRange(IEnumerable source, IEnumerable items) var array = source.ToImmutableArray(); Assert.Equal(source.Concat(items), array.AddRange(it)); // Enumerable overload - Assert.Equal(source.Concat(items), array.AddRange(it.ToImmutableArray())); // Struct overload + Assert.Equal(source.Concat(items), array.AddRange(it.ToImmutableArray())); // ImmutableArray overload + + int[] itArray = it.ToArray(); + Assert.Equal(source.Concat(items), array.AddRange(itArray)); // Array overload + Assert.Equal(source.Concat(items), array.AddRange(new ReadOnlySpan(itArray))); // ReadOnlySpan overload + Assert.Equal(source, array); // Make sure the original array wasn't affected. }); } + [Theory] + [MemberData(nameof(Int32EnumerableData))] + public void AddRangeEmptyOptimization(IEnumerable source) + { + ImmutableArray array = source.ToImmutableArray(); + + // Verify that underlying array is reference-equal as original array + Assert.True(array.AddRange(Array.Empty()) == array); + Assert.True(array.AddRange(ReadOnlySpan.Empty) == array); + } + public static IEnumerable AddData() { yield return new object[] { new int[] { }, new[] { 1 } }; @@ -1043,8 +1187,13 @@ public void AddRangeInvalid(IEnumerable source) // If the lhs or the rhs is a default ImmutableArray, AddRange should throw. TestExtensionsMethods.ValidateDefaultThisBehavior(() => s_emptyDefault.AddRange(source)); // Enumerable overload - TestExtensionsMethods.ValidateDefaultThisBehavior(() => s_emptyDefault.AddRange(source.ToImmutableArray())); // Struct overload - TestExtensionsMethods.ValidateDefaultThisBehavior(() => source.ToImmutableArray().AddRange(s_emptyDefault)); // Struct overload + TestExtensionsMethods.ValidateDefaultThisBehavior(() => s_emptyDefault.AddRange(source.ToImmutableArray())); // ImmutableArray overload + TestExtensionsMethods.ValidateDefaultThisBehavior(() => source.ToImmutableArray().AddRange(s_emptyDefault)); // ImmutableArray overload + + int[] sourceArray = source.ToArray(); + TestExtensionsMethods.ValidateDefaultThisBehavior(() => s_emptyDefault.AddRange(sourceArray)); // Array overload + TestExtensionsMethods.ValidateDefaultThisBehavior(() => s_emptyDefault.AddRange(new ReadOnlySpan(sourceArray))); // ReadOnlySpan overload + Assert.Throws(() => source.ToImmutableArray().AddRange((IEnumerable)s_emptyDefault)); // Enumerable overload TestExtensionsMethods.ValidateDefaultThisBehavior(() => s_emptyDefault.AddRange(s_emptyDefault)); @@ -1095,46 +1244,64 @@ public void InsertDefaultInvalid(int index) [MemberData(nameof(Int32EnumerableData))] public void InsertRangeInvalid(IEnumerable source) { - var array = source.ToImmutableArray(); + var immutableArray = source.ToImmutableArray(); - AssertExtensions.Throws("index", () => array.InsertRange(array.Length + 1, s_oneElement)); - AssertExtensions.Throws("index", () => array.InsertRange(-1, s_oneElement)); + AssertExtensions.Throws("index", () => immutableArray.InsertRange(immutableArray.Length + 1, s_oneElement)); + AssertExtensions.Throws("index", () => immutableArray.InsertRange(-1, s_oneElement)); - AssertExtensions.Throws("index", () => array.InsertRange(array.Length + 1, (IEnumerable)s_oneElement)); - AssertExtensions.Throws("index", () => array.InsertRange(-1, (IEnumerable)s_oneElement)); + AssertExtensions.Throws("index", () => immutableArray.InsertRange(immutableArray.Length + 1, (IEnumerable)s_oneElement)); + AssertExtensions.Throws("index", () => immutableArray.InsertRange(-1, (IEnumerable)s_oneElement)); + + int[] array = s_oneElement.ToArray(); + AssertExtensions.Throws("index", () => immutableArray.InsertRange(immutableArray.Length + 1, array)); + AssertExtensions.Throws("index", () => immutableArray.InsertRange(-1, array)); + + var span = new ReadOnlySpan(array); + AssertExtensions.Throws("index", span, s => immutableArray.InsertRange(immutableArray.Length + 1, s)); + AssertExtensions.Throws("index", span, s => immutableArray.InsertRange(-1, s)); } [Theory] [MemberData(nameof(Int32EnumerableData))] public void InsertRangeDefaultInvalid(IEnumerable items) { - var array = items.ToImmutableArray(); + var immutableArray = items.ToImmutableArray(); TestExtensionsMethods.ValidateDefaultThisBehavior(() => s_emptyDefault.InsertRange(1, items)); TestExtensionsMethods.ValidateDefaultThisBehavior(() => s_emptyDefault.InsertRange(-1, items)); TestExtensionsMethods.ValidateDefaultThisBehavior(() => s_emptyDefault.InsertRange(0, items)); + TestExtensionsMethods.ValidateDefaultThisBehavior(() => s_emptyDefault.InsertRange(1, immutableArray)); + TestExtensionsMethods.ValidateDefaultThisBehavior(() => s_emptyDefault.InsertRange(-1, immutableArray)); + TestExtensionsMethods.ValidateDefaultThisBehavior(() => s_emptyDefault.InsertRange(0, immutableArray)); + + int[] array = items.ToArray(); TestExtensionsMethods.ValidateDefaultThisBehavior(() => s_emptyDefault.InsertRange(1, array)); TestExtensionsMethods.ValidateDefaultThisBehavior(() => s_emptyDefault.InsertRange(-1, array)); TestExtensionsMethods.ValidateDefaultThisBehavior(() => s_emptyDefault.InsertRange(0, array)); - TestExtensionsMethods.ValidateDefaultThisBehavior(() => array.InsertRange(1, s_emptyDefault)); - TestExtensionsMethods.ValidateDefaultThisBehavior(() => array.InsertRange(-1, s_emptyDefault)); - TestExtensionsMethods.ValidateDefaultThisBehavior(() => array.InsertRange(0, s_emptyDefault)); + var span = new ReadOnlySpan(array); + TestExtensionsMethods.ValidateDefaultThisBehavior(span, s => s_emptyDefault.InsertRange(1, s)); + TestExtensionsMethods.ValidateDefaultThisBehavior(span, s => s_emptyDefault.InsertRange(-1, s)); + TestExtensionsMethods.ValidateDefaultThisBehavior(span, s => s_emptyDefault.InsertRange(0, s)); - if (array.Length > 0) + TestExtensionsMethods.ValidateDefaultThisBehavior(() => immutableArray.InsertRange(1, s_emptyDefault)); + TestExtensionsMethods.ValidateDefaultThisBehavior(() => immutableArray.InsertRange(-1, s_emptyDefault)); + TestExtensionsMethods.ValidateDefaultThisBehavior(() => immutableArray.InsertRange(0, s_emptyDefault)); + + if (immutableArray.Length > 0) { - Assert.Throws(() => array.InsertRange(1, (IEnumerable)s_emptyDefault)); + Assert.Throws(() => immutableArray.InsertRange(1, (IEnumerable)s_emptyDefault)); } - Assert.Throws(() => array.InsertRange(0, (IEnumerable)s_emptyDefault)); + Assert.Throws(() => immutableArray.InsertRange(0, (IEnumerable)s_emptyDefault)); } [Theory] [MemberData(nameof(InsertRangeData))] public void InsertRange(IEnumerable source, int index, IEnumerable items) { - var array = source.ToImmutableArray(); + var immutableArray = source.ToImmutableArray(); Assert.All(ChangeType(items), it => { @@ -1142,14 +1309,31 @@ public void InsertRange(IEnumerable source, int index, IEnumerable ite .Concat(items) .Concat(source.Skip(index)); - Assert.Equal(expected, array.InsertRange(index, it)); // Enumerable overload - Assert.Equal(expected, array.InsertRange(index, it.ToImmutableArray())); // Struct overload + Assert.Equal(expected, immutableArray.InsertRange(index, it)); // Enumerable overload + Assert.Equal(expected, immutableArray.InsertRange(index, it.ToImmutableArray())); // ImmutableArray overload - if (index == array.Length) + int[] array; + if (items.GetType() == typeof(uint[])) + { + array = it.Select(i => (int)i).ToArray(); + } + else + { + array = it.ToArray(); + } + + Assert.Equal(expected, immutableArray.InsertRange(index, array)); // Array overload + Assert.Equal(expected, immutableArray.InsertRange(index, new ReadOnlySpan(array))); // Span overload + + if (index == immutableArray.Length) { // Insertion at the end is equivalent to adding. - Assert.Equal(expected, array.InsertRange(index, it)); // Enumerable overload - Assert.Equal(expected, array.InsertRange(index, it.ToImmutableArray())); // Struct overload + expected = source.Concat(items); + + Assert.Equal(expected, immutableArray.InsertRange(index, it)); // Enumerable overload + Assert.Equal(expected, immutableArray.InsertRange(index, it.ToImmutableArray())); // ImmutableArray overload + Assert.Equal(expected, immutableArray.InsertRange(index, array)); // Array overload + Assert.Equal(expected, immutableArray.InsertRange(index, new ReadOnlySpan(array))); // Span overload } }); } @@ -1260,7 +1444,7 @@ public void RemoveDefaultInvalid() } [Theory] - [MemberData(nameof(RemoveRangeIndexLengthData))] + [MemberData(nameof(RangeIndexLengthData))] public void RemoveRangeIndexLength(IEnumerable source, int index, int length) { var array = source.ToImmutableArray(); @@ -1268,17 +1452,6 @@ public void RemoveRangeIndexLength(IEnumerable source, int index, int lengt Assert.Equal(expected, array.RemoveRange(index, length)); } - public static IEnumerable RemoveRangeIndexLengthData() - { - yield return new object[] { s_empty, 0, 0 }; - yield return new object[] { s_oneElement, 1, 0 }; - yield return new object[] { s_oneElement, 0, 1 }; - yield return new object[] { s_oneElement, 0, 0 }; - yield return new object[] { new[] { 1, 2, 3, 4 }, 0, 2 }; - yield return new object[] { new[] { 1, 2, 3, 4 }, 1, 2 }; - yield return new object[] { new[] { 1, 2, 3, 4 }, 2, 2 }; - } - [Theory] [MemberData(nameof(Int32EnumerableData))] public void RemoveRangeIndexLengthInvalid(IEnumerable source) @@ -1303,48 +1476,59 @@ public void RemoveRangeIndexLengthDefaultInvalid(int index, int length) [Theory] [MemberData(nameof(RemoveRangeEnumerableData))] - public void RemoveRangeEnumerable(IEnumerable source, IEnumerable items, IEqualityComparer comparer) + public void RemoveRangeEnumerable(IEnumerable source, IEnumerable items, IEqualityComparer comparer) { - var array = source.ToImmutableArray(); - IEnumerable expected = items.Aggregate( + ImmutableArray immutableArray = source.ToImmutableArray(); + IEnumerable expected = items.Aggregate( seed: source.ToImmutableArray(), func: (a, i) => a.Remove(i, comparer)); - Assert.Equal(expected, array.RemoveRange(items, comparer)); // Enumerable overload - Assert.Equal(expected, array.RemoveRange(items.ToImmutableArray(), comparer)); // Struct overload - Assert.Equal(expected, ((IImmutableList)array).RemoveRange(items, comparer)); + Assert.Equal(expected, immutableArray.RemoveRange(items, comparer)); // Enumerable overload + Assert.Equal(expected, immutableArray.RemoveRange(items.ToImmutableArray(), comparer)); // ImmutableArray overload + + int?[] array = items.ToArray(); + Assert.Equal(expected, immutableArray.RemoveRange(array, comparer)); // Array overload + ReadOnlySpan span = new ReadOnlySpan(array); + Assert.Equal(expected, immutableArray.RemoveRange(span, comparer)); // Span overload + + Assert.Equal(expected, ((IImmutableList)immutableArray).RemoveRange(items, comparer)); if (comparer == null || comparer == EqualityComparer.Default) { - Assert.Equal(expected, array.RemoveRange(items)); // Enumerable overload - Assert.Equal(expected, array.RemoveRange(items.ToImmutableArray())); // Struct overload - Assert.Equal(expected, ((IImmutableList)array).RemoveRange(items)); + Assert.Equal(expected, immutableArray.RemoveRange(items)); // Enumerable overload + Assert.Equal(expected, immutableArray.RemoveRange(items.ToImmutableArray())); // ImmutableArray overload + Assert.Equal(expected, immutableArray.RemoveRange(array)); // Array overload + Assert.Equal(expected, immutableArray.RemoveRange(span)); // Span overload + Assert.Equal(expected, ((IImmutableList)immutableArray).RemoveRange(items)); } } public static IEnumerable RemoveRangeEnumerableData() { - return SharedEqualityComparers().SelectMany(comparer => + return SharedEqualityComparers().SelectMany(comparer => new[] { - new object[] { s_empty, s_empty, comparer }, - new object[] { s_empty, s_oneElement, comparer }, - new object[] { s_oneElement, s_empty, comparer }, - new object[] { new[] { 1, 2, 3 }, new[] { 2, 3, 4 }, comparer }, - new object[] { Enumerable.Range(1, 5), Enumerable.Range(6, 5), comparer }, - new object[] { new[] { 1, 2, 3 }, new[] { 2 }, comparer }, - new object[] { s_empty, new int[] { }, comparer }, - new object[] { new[] { 1, 2, 3 }, new[] { 2 }, comparer }, - new object[] { new[] { 1, 2, 3 }, new[] { 1, 3, 5 }, comparer }, - new object[] { Enumerable.Range(1, 10), new[] { 2, 4, 5, 7, 10 }, comparer }, - new object[] { Enumerable.Range(1, 10), new[] { 1, 2, 4, 5, 7, 10 }, comparer }, - new object[] { new[] { 1, 2, 3 }, new[] { 5 }, comparer }, - new object[] { new[] { 1, 2, 2, 3 }, new[] { 2 }, comparer }, - new object[] { new[] { 1, 2, 2, 3 }, new[] { 2, 2 }, comparer }, - new object[] { new[] { 1, 2, 2, 3 }, new[] { 2, 2, 2 }, comparer }, - new object[] { new[] { 1, 2, 3 }, new[] { 42 }, comparer }, - new object[] { new[] { 1, 2, 3 }, new[] { 42, 42 }, comparer }, - new object[] { new[] { 1, 2, 3 }, new[] { 42, 42, 42 }, comparer }, + new object[] { Array.Empty(), Array.Empty(), comparer }, + new object[] { Array.Empty(), new int?[] { 1 }, comparer }, + new object[] { new int?[] { 1 }, Array.Empty(), comparer }, + new object[] { new int?[] { 1, 2, 3 }, new int?[] { 2, 3, 4 }, comparer }, + new object[] { Enumerable.Range(1, 5).Cast(), Enumerable.Range(6, 5).Cast(), comparer }, + new object[] { new int?[] { 1, 2, 3 }, new int?[] { 2 }, comparer }, + new object[] { new int?[] { 1, 2, 3 }, new int?[] { 1, 3, 5 }, comparer }, + new object[] { Enumerable.Range(1, 10).Cast(), new int?[] { 2, 4, 5, 7, 10 }, comparer }, + new object[] { Enumerable.Range(1, 10).Cast(), new int?[] { 1, 2, 4, 5, 7, 10 }, comparer }, + new object[] { new int?[] { 1, 2, 3 }, new int?[] { 5 }, comparer }, + new object[] { new int?[] { 1, 2, 2, 3 }, new int?[] { 2 }, comparer }, + new object[] { new int?[] { 1, 2, 2, 3 }, new int?[] { 2, 2 }, comparer }, + new object[] { new int?[] { 1, 2, 2, 3 }, new int?[] { 2, 2, 2 }, comparer }, + new object[] { new int?[] { 1, 2, 3 }, new int?[] { 42 }, comparer }, + new object[] { new int?[] { 1, 2, 3 }, new int?[] { 42, 42 }, comparer }, + new object[] { new int?[] { 1, 2, 3 }, new int?[] { 42, 42, 42 }, comparer }, + new object[] { new int?[] { null }, new int?[] { 1 }, comparer }, + new object[] { new int?[] { 1 }, new int?[] { null}, comparer }, + new object[] { new int?[] { 1, null, 2, null }, new int?[] { 1, null}, comparer }, + new object[] { new int?[] { 1, null, 2 }, new int?[] { 1, null, null}, comparer }, + new object[] { new int?[] { 1, null, 2, null }, new int?[] { 1, null, null}, comparer }, }); } @@ -1352,7 +1536,7 @@ public static IEnumerable RemoveRangeEnumerableData() [MemberData(nameof(Int32EnumerableData))] public void RemoveRangeEnumerableInvalid(IEnumerable source) { - var array = source.ToImmutableArray(); + var immutableArray = source.ToImmutableArray(); Assert.All(SharedEqualityComparers(), comparer => { @@ -1363,18 +1547,18 @@ public void RemoveRangeEnumerableInvalid(IEnumerable source) Assert.Throws(() => ((IImmutableList)s_emptyDefault).RemoveRange(source, comparer)); // Struct overloads, lhs is default - TestExtensionsMethods.ValidateDefaultThisBehavior(() => s_emptyDefault.RemoveRange(array)); - TestExtensionsMethods.ValidateDefaultThisBehavior(() => s_emptyDefault.RemoveRange(array, comparer)); + TestExtensionsMethods.ValidateDefaultThisBehavior(() => s_emptyDefault.RemoveRange(immutableArray)); + TestExtensionsMethods.ValidateDefaultThisBehavior(() => s_emptyDefault.RemoveRange(immutableArray, comparer)); // Struct overloads, rhs is default - AssertExtensions.Throws("items", () => array.RemoveRange(s_emptyDefault)); - AssertExtensions.Throws("items", () => array.RemoveRange(s_emptyDefault, comparer)); + AssertExtensions.Throws("items", () => immutableArray.RemoveRange(s_emptyDefault)); + AssertExtensions.Throws("items", () => immutableArray.RemoveRange(s_emptyDefault, comparer)); // Enumerable overloads, rhs is default - Assert.Throws(() => array.RemoveRange((IEnumerable)s_emptyDefault)); - Assert.Throws(() => array.RemoveRange((IEnumerable)s_emptyDefault, comparer)); - Assert.Throws(() => ((IImmutableList)array).RemoveRange(s_emptyDefault)); - Assert.Throws(() => ((IImmutableList)array).RemoveRange(s_emptyDefault, comparer)); + Assert.Throws(() => immutableArray.RemoveRange((IEnumerable)s_emptyDefault)); + Assert.Throws(() => immutableArray.RemoveRange((IEnumerable)s_emptyDefault, comparer)); + Assert.Throws(() => ((IImmutableList)immutableArray).RemoveRange(s_emptyDefault)); + Assert.Throws(() => ((IImmutableList)immutableArray).RemoveRange(s_emptyDefault, comparer)); // Struct overloads, both sides are default AssertExtensions.Throws("items", () => s_emptyDefault.RemoveRange(s_emptyDefault)); @@ -1387,16 +1571,30 @@ public void RemoveRangeEnumerableInvalid(IEnumerable source) Assert.Throws(() => ((IImmutableList)s_emptyDefault).RemoveRange(s_emptyDefault, comparer)); // Enumerable overloads, rhs is null - AssertExtensions.Throws("items", () => array.RemoveRange(items: null)); - AssertExtensions.Throws("items", () => array.RemoveRange(items: null, equalityComparer: comparer)); - AssertExtensions.Throws("items", () => ((IImmutableList)array).RemoveRange(items: null)); - AssertExtensions.Throws("items", () => ((IImmutableList)array).RemoveRange(items: null, equalityComparer: comparer)); + AssertExtensions.Throws("items", () => immutableArray.RemoveRange(items: null as IEnumerable)); + AssertExtensions.Throws("items", () => immutableArray.RemoveRange(items: null as IEnumerable, equalityComparer: comparer)); + AssertExtensions.Throws("items", () => ((IImmutableList)immutableArray).RemoveRange(items: null)); + AssertExtensions.Throws("items", () => ((IImmutableList)immutableArray).RemoveRange(items: null, equalityComparer: comparer)); // Enumerable overloads, lhs is default and rhs is null - TestExtensionsMethods.ValidateDefaultThisBehavior(() => s_emptyDefault.RemoveRange(items: null)); - TestExtensionsMethods.ValidateDefaultThisBehavior(() => s_emptyDefault.RemoveRange(items: null, equalityComparer: comparer)); - Assert.Throws(() => ((IImmutableList)s_emptyDefault).RemoveRange(items: null)); - Assert.Throws(() => ((IImmutableList)s_emptyDefault).RemoveRange(items: null, equalityComparer: comparer)); + TestExtensionsMethods.ValidateDefaultThisBehavior(() => s_emptyDefault.RemoveRange(items: null as IEnumerable)); + TestExtensionsMethods.ValidateDefaultThisBehavior(() => s_emptyDefault.RemoveRange(items: null as IEnumerable, equalityComparer: comparer)); + Assert.Throws(() => ((IImmutableList)s_emptyDefault).RemoveRange(items: null as IEnumerable)); + Assert.Throws(() => ((IImmutableList)s_emptyDefault).RemoveRange(items: null as IEnumerable, equalityComparer: comparer)); + + // Array overloads, lhs is default and rhs is null + TestExtensionsMethods.ValidateDefaultThisBehavior(() => s_emptyDefault.RemoveRange(items: null as int[], comparer)); + + // Array overloads, rhs is null + AssertExtensions.Throws("items", () => immutableArray.RemoveRange(items: null as int[], equalityComparer: comparer)); + + // Array overloads, lhs is default + int[] array = source.ToArray(); + TestExtensionsMethods.ValidateDefaultThisBehavior(() => s_emptyDefault.RemoveRange(items: array, comparer)); + + // Span overloads, lhs is default + var span = new ReadOnlySpan(array); + TestExtensionsMethods.ValidateDefaultThisBehavior(span, s => s_emptyDefault.RemoveRange(items: s, comparer)); }); } @@ -1597,6 +1795,15 @@ public void CopyTo(IEnumerable source, int sourceIndex, IEnumerable de Assert.Equal(destination.Skip(destinationIndex + array.Length), destinationArray.Skip(destinationIndex + array.Length)); }); + CopyAndInvoke(destination, destinationArray => + { + array.CopyTo(new Span(destinationArray, destinationIndex, array.Length)); + + Assert.Equal(destination.Take(destinationIndex), destinationArray.Take(destinationIndex)); + Assert.Equal(source, destinationArray.Skip(destinationIndex).Take(array.Length)); + Assert.Equal(destination.Skip(destinationIndex + array.Length), destinationArray.Skip(destinationIndex + array.Length)); + }); + if (destinationIndex == 0) { CopyAndInvoke(destination, destinationArray => @@ -1644,6 +1851,7 @@ public void CopyToInvalid(IEnumerable source) if (array.Length > 0) { AssertExtensions.Throws("destinationArray", string.Empty, () => array.CopyTo(array.Length - 1, new int[1], 1, 1)); // Not enough room in the destination. + AssertExtensions.Throws("destination", () => array.CopyTo(new Span(new int[array.Length - 1]))); // Not enough room in the destination. } } @@ -1663,6 +1871,8 @@ public void CopyToDefaultInvalid(int destinationLength, int destinationIndex) TestExtensionsMethods.ValidateDefaultThisBehavior(() => s_emptyDefault.CopyTo(destination, destinationIndex)); TestExtensionsMethods.ValidateDefaultThisBehavior(() => s_emptyDefault.CopyTo(0, destination, destinationIndex, 0)); + TestExtensionsMethods.ValidateDefaultThisBehavior(() => s_emptyDefault.CopyTo(new Span(destination, destinationIndex, destinationLength - destinationIndex))); + TestExtensionsMethods.ValidateDefaultThisBehavior(() => s_emptyDefault.CopyTo(new Span(destination, destinationIndex, 0))); } [Theory] diff --git a/src/libraries/System.Collections.Immutable/tests/ImmutableArrayTest.netcoreapp.cs b/src/libraries/System.Collections.Immutable/tests/ImmutableArrayTest.netcoreapp.cs new file mode 100644 index 00000000000000..5821f93fc8c5bb --- /dev/null +++ b/src/libraries/System.Collections.Immutable/tests/ImmutableArrayTest.netcoreapp.cs @@ -0,0 +1,50 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using System.Linq; +using Xunit; + +namespace System.Collections.Immutable.Tests +{ + public partial class ImmutableArrayTest : SimpleElementImmutablesTestBase + { + [Fact] + public void AsSpanRoundTripEmptyArrayTests_RangeInput() + { + ImmutableArray immutableArray = ImmutableArray.Create(Array.Empty()); + + ReadOnlySpan rangedSpan = immutableArray.AsSpan(new Range(0, 0)); + Assert.Equal(immutableArray, rangedSpan.ToArray()); + Assert.Equal(immutableArray.Length, rangedSpan.Length); + } + + [Fact] + public void AsSpanEmptyRangeNotInitialized() + { + TestExtensionsMethods.ValidateDefaultThisBehavior(() => s_emptyDefault.AsSpan(new Range(0, 0))); + } + + [Theory] + [MemberData(nameof(RangeIndexLengthData))] + public void AsSpanStartLength_RangeInput(IEnumerable source, int start, int length) + { + var array = source.ToImmutableArray(); + var expected = source.Skip(start).Take(length); + + Assert.Equal(expected, array.AsSpan(new Range(start, start + length)).ToArray()); + } + + [Theory] + [MemberData(nameof(Int32EnumerableData))] + public void AsSpanStartLengthInvalid_RangeInput(IEnumerable source) + { + var array = source.ToImmutableArray(); + + AssertExtensions.Throws(() => array.AsSpan(new Range(-1, 0))); + AssertExtensions.Throws(() => array.AsSpan(new Range(array.Length + 1, array.Length + 2))); + AssertExtensions.Throws(() => array.AsSpan(new Range(0, -1))); + AssertExtensions.Throws(() => array.AsSpan(new Range(0, array.Length + 1))); + } + } +} diff --git a/src/libraries/System.Collections.Immutable/tests/System.Collections.Immutable.Tests.csproj b/src/libraries/System.Collections.Immutable/tests/System.Collections.Immutable.Tests.csproj index 5dc59a38382154..ecd0a47fae59cd 100644 --- a/src/libraries/System.Collections.Immutable/tests/System.Collections.Immutable.Tests.csproj +++ b/src/libraries/System.Collections.Immutable/tests/System.Collections.Immutable.Tests.csproj @@ -11,6 +11,7 @@ + diff --git a/src/libraries/System.Collections.Immutable/tests/TestExtensionsMethods.cs b/src/libraries/System.Collections.Immutable/tests/TestExtensionsMethods.cs index 7373a24d5d778c..2e2c65d491e3db 100644 --- a/src/libraries/System.Collections.Immutable/tests/TestExtensionsMethods.cs +++ b/src/libraries/System.Collections.Immutable/tests/TestExtensionsMethods.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using Xunit; +using Xunit.Sdk; namespace System.Collections.Immutable.Tests { @@ -14,5 +15,23 @@ internal static void ValidateDefaultThisBehavior(Action a) { Assert.Throws(a); } + + internal static void ValidateDefaultThisBehavior(ReadOnlySpan span, AssertExtensions.AssertThrowsActionReadOnly action) + { + try + { + action(span); + } + catch (NullReferenceException nullRefEx) when (nullRefEx.GetType() == typeof(NullReferenceException)) + { + return; + } + catch (Exception ex) + { + throw new ThrowsException(typeof(NullReferenceException), ex); + } + + throw new ThrowsException(typeof(NullReferenceException)); + } } }