diff --git a/src/libraries/System.Memory/ref/System.Memory.cs b/src/libraries/System.Memory/ref/System.Memory.cs index 53d3da5e0441c..00e5cd71cda98 100644 --- a/src/libraries/System.Memory/ref/System.Memory.cs +++ b/src/libraries/System.Memory/ref/System.Memory.cs @@ -471,13 +471,16 @@ public static void Sort(this System.Span keys, Sy public static bool TryWrite(this System.Span destination, System.IFormatProvider? provider, System.Text.CompositeFormat format, out int charsWritten, TArg0 arg0, TArg1 arg1, TArg2 arg2) { throw null; } public static bool TryWrite(this Span destination, System.IFormatProvider? provider, System.Text.CompositeFormat format, out int charsWritten, params object?[] args) { throw null; } public static bool TryWrite(this Span destination, System.IFormatProvider? provider, System.Text.CompositeFormat format, out int charsWritten, params System.ReadOnlySpan args) { throw null; } - public ref struct SpanSplitEnumerator where T : System.IEquatable + public ref struct SpanSplitEnumerator : System.Collections.Generic.IEnumerator, System.Collections.IEnumerator, System.IDisposable where T : System.IEquatable { private object _dummy; private int _dummyPrimitive; public readonly System.Range Current { get { throw null; } } public System.MemoryExtensions.SpanSplitEnumerator GetEnumerator() { throw null; } public bool MoveNext() { throw null; } + object System.Collections.IEnumerator.Current { get { throw null; } } + void System.Collections.IEnumerator.Reset() { throw null; } + void System.IDisposable.Dispose() { throw null; } } [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Runtime.CompilerServices.InterpolatedStringHandlerAttribute] @@ -758,20 +761,26 @@ public static partial class Utf8Parser } namespace System.Text { - public ref partial struct SpanLineEnumerator + public ref partial struct SpanLineEnumerator : System.Collections.Generic.IEnumerator>, System.Collections.IEnumerator, System.IDisposable { private object _dummy; private int _dummyPrimitive; public System.ReadOnlySpan Current { get { throw null; } } public System.Text.SpanLineEnumerator GetEnumerator() { throw null; } public bool MoveNext() { throw null; } + object System.Collections.IEnumerator.Current { get { throw null; } } + void System.Collections.IEnumerator.Reset() { throw null; } + void IDisposable.Dispose() { throw null; } } - public ref partial struct SpanRuneEnumerator + public ref partial struct SpanRuneEnumerator : System.Collections.Generic.IEnumerator, System.Collections.IEnumerator, System.IDisposable { private object _dummy; private int _dummyPrimitive; public System.Text.Rune Current { get { throw null; } } public System.Text.SpanRuneEnumerator GetEnumerator() { throw null; } public bool MoveNext() { throw null; } + object System.Collections.IEnumerator.Current { get { throw null; } } + void System.Collections.IEnumerator.Reset() { throw null; } + void IDisposable.Dispose() { throw null; } } } diff --git a/src/libraries/System.Memory/tests/ReadOnlySpan/GetEnumerator.cs b/src/libraries/System.Memory/tests/ReadOnlySpan/GetEnumerator.cs index 1811041238b7d..64ad55e575175 100644 --- a/src/libraries/System.Memory/tests/ReadOnlySpan/GetEnumerator.cs +++ b/src/libraries/System.Memory/tests/ReadOnlySpan/GetEnumerator.cs @@ -4,6 +4,7 @@ using Xunit; using System.Linq; using System.Collections.Generic; +using System.Collections; namespace System.SpanTests { @@ -29,6 +30,8 @@ public static void GetEnumerator_ForEach_AllValuesReturnedCorrectly(int[] array) } Assert.Equal(Enumerable.Sum(array), sum); + Assert.Equal(Enumerable.Sum(array), SumGI(span.GetEnumerator())); + Assert.Equal(Enumerable.Sum(array), SumI(span.GetEnumerator())); } [Theory] @@ -48,6 +51,8 @@ public static void GetEnumerator_Manual_AllValuesReturnedCorrectly(int[] array) Assert.False(e.MoveNext()); Assert.Equal(Enumerable.Sum(array), sum); + Assert.Equal(Enumerable.Sum(array), SumGI(span.GetEnumerator())); + Assert.Equal(Enumerable.Sum(array), SumI(span.GetEnumerator())); } [Fact] @@ -55,6 +60,74 @@ public static void GetEnumerator_MoveNextOnDefault_ReturnsFalse() { Assert.False(default(ReadOnlySpan.Enumerator).MoveNext()); Assert.ThrowsAny(() => default(ReadOnlySpan.Enumerator).Current); + + TestGI(default(ReadOnlySpan.Enumerator)); + TestI(default(ReadOnlySpan.Enumerator)); + + static void TestGI(TEnumerator enumerator) where TEnumerator : IEnumerator, allows ref struct + { + Assert.False(enumerator.MoveNext()); + try { _ = enumerator.Current; } catch (Exception) { } + enumerator.Dispose(); + enumerator.Reset(); + Assert.False(enumerator.MoveNext()); + try { _ = enumerator.Current; } catch (Exception) { } + } + + static void TestI(TEnumerator enumerator) where TEnumerator : IEnumerator, allows ref struct + { + Assert.False(enumerator.MoveNext()); + try { _ = enumerator.Current; } catch (Exception) { } + enumerator.Reset(); + Assert.False(enumerator.MoveNext()); + try { _ = enumerator.Current; } catch (Exception) { } + } + } + + private static int SumGI(TEnumerator enumerator) where TEnumerator : IEnumerator, allows ref struct + { + int sum1 = 0; + enumerator.Dispose(); + while (enumerator.MoveNext()) + { + sum1 += enumerator.Current; + enumerator.Dispose(); + } + Assert.False(enumerator.MoveNext()); + + int sum2 = 0; + enumerator.Reset(); + enumerator.Dispose(); + while (enumerator.MoveNext()) + { + sum2 += enumerator.Current; + enumerator.Dispose(); + } + Assert.False(enumerator.MoveNext()); + + Assert.Equal(sum1, sum2); + return sum2; + } + + private static int SumI(TEnumerator enumerator) where TEnumerator : IEnumerator, allows ref struct + { + int sum1 = 0; + while (enumerator.MoveNext()) + { + sum1 += (int)enumerator.Current; + } + Assert.False(enumerator.MoveNext()); + + int sum2 = 0; + enumerator.Reset(); + while (enumerator.MoveNext()) + { + sum2 += (int)enumerator.Current; + } + Assert.False(enumerator.MoveNext()); + + Assert.Equal(sum1, sum2); + return sum2; } } } diff --git a/src/libraries/System.Memory/tests/ReadOnlySpan/Split.T.cs b/src/libraries/System.Memory/tests/ReadOnlySpan/Split.T.cs index 740e73c50e5f8..ce758a999abcb 100644 --- a/src/libraries/System.Memory/tests/ReadOnlySpan/Split.T.cs +++ b/src/libraries/System.Memory/tests/ReadOnlySpan/Split.T.cs @@ -3,6 +3,8 @@ // See the LICENSE file in the project root for more information. using System.Buffers; +using System.Collections; +using System.Collections.Generic; using Xunit; namespace System.SpanTests @@ -10,7 +12,7 @@ namespace System.SpanTests public static partial class ReadOnlySpanTests { [Fact] - public static void DefaultSpanSplitEnumeratorBehaviour() + public static void SpanSplitEnumerator_Default() { var charSpanEnumerator = new MemoryExtensions.SpanSplitEnumerator(); Assert.Equal(new Range(0, 0), charSpanEnumerator.Current); @@ -19,10 +21,34 @@ public static void DefaultSpanSplitEnumeratorBehaviour() // Implicit DoesNotThrow assertion charSpanEnumerator.GetEnumerator(); + TestGI(charSpanEnumerator); + TestI(charSpanEnumerator); + var stringSpanEnumerator = new MemoryExtensions.SpanSplitEnumerator(); Assert.Equal(new Range(0, 0), stringSpanEnumerator.Current); Assert.False(stringSpanEnumerator.MoveNext()); stringSpanEnumerator.GetEnumerator(); + TestGI(stringSpanEnumerator); + TestI(stringSpanEnumerator); + + static void TestGI(TEnumerator enumerator) where TEnumerator : IEnumerator, allows ref struct + { + Assert.Equal(new Range(0, 0), enumerator.Current); + Assert.False(enumerator.MoveNext()); + try { enumerator.Reset(); } catch (NotSupportedException) { } + enumerator.Dispose(); + Assert.Equal(new Range(0, 0), enumerator.Current); + Assert.False(enumerator.MoveNext()); + } + + static void TestI(TEnumerator enumerator) where TEnumerator : IEnumerator, allows ref struct + { + Assert.Equal(new Range(0, 0), enumerator.Current); + Assert.False(enumerator.MoveNext()); + try { enumerator.Reset(); } catch (NotSupportedException) { } + Assert.Equal(new Range(0, 0), enumerator.Current); + Assert.False(enumerator.MoveNext()); + } } [Fact] @@ -132,7 +158,7 @@ static void Test(T[] value, T[] separator, Range[] result) where T : IEquatab } [Fact] - public static void SplitAnySeparatorData() + public static void SplitAny_SeparatorData() { // Split no separators Test((char[])['a', ' ', 'b'], (char[])[], (Range[])[0..1, 2..3]); // an empty span of separators for char is handled as all whitespace being separators @@ -196,6 +222,9 @@ separator is char[] separators && private static void AssertEnsureCorrectEnumeration(MemoryExtensions.SpanSplitEnumerator enumerator, Range[] result) where T : IEquatable { + AssertEnsureCorrectEnumerationGI>(enumerator, result); + AssertEnsureCorrectEnumerationI>(enumerator, result); + Assert.Equal(new Range(0, 0), enumerator.Current); for (int i = 0; i < result.Length; i++) @@ -207,6 +236,50 @@ private static void AssertEnsureCorrectEnumeration(MemoryExtensions.SpanSplit Assert.False(enumerator.MoveNext()); } + private static void AssertEnsureCorrectEnumerationGI(TEnumerator enumerator, Range[] result) + where T : IEquatable + where TEnumerator : IEnumerator, allows ref struct + { + Assert.Equal(new Range(0, 0), enumerator.Current); + try { enumerator.Reset(); } catch (NotSupportedException) { } + enumerator.Dispose(); + Assert.Equal(new Range(0, 0), enumerator.Current); + + for (int i = 0; i < result.Length; i++) + { + enumerator.Dispose(); + try { enumerator.Reset(); } catch (NotSupportedException) { } + Assert.True(enumerator.MoveNext()); + Assert.Equal(result[i], enumerator.Current); + } + + Assert.False(enumerator.MoveNext()); + try { enumerator.Reset(); } catch (NotSupportedException) { } + enumerator.Dispose(); + Assert.False(enumerator.MoveNext()); + } + + private static void AssertEnsureCorrectEnumerationI(TEnumerator enumerator, Range[] result) + where T : IEquatable + where TEnumerator : IEnumerator, allows ref struct + { + Assert.Equal(new Range(0, 0), enumerator.Current); + + try { enumerator.Reset(); } catch (NotSupportedException) { } + Assert.Equal(new Range(0, 0), enumerator.Current); + + for (int i = 0; i < result.Length; i++) + { + try { enumerator.Reset(); } catch (NotSupportedException) { } + Assert.True(enumerator.MoveNext()); + Assert.Equal(result[i], enumerator.Current); + } + + Assert.False(enumerator.MoveNext()); + try { enumerator.Reset(); } catch (NotSupportedException) { } + Assert.False(enumerator.MoveNext()); + } + public record struct CustomStruct(int value) : IEquatable; public record class CustomClass(int value) : IEquatable; diff --git a/src/libraries/System.Memory/tests/Span/EnumerateLines.cs b/src/libraries/System.Memory/tests/Span/EnumerateLines.cs index d6c4c653adfe7..e0abfbc97e38e 100644 --- a/src/libraries/System.Memory/tests/Span/EnumerateLines.cs +++ b/src/libraries/System.Memory/tests/Span/EnumerateLines.cs @@ -2,11 +2,13 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Buffers; +using System.Collections; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; +using System.Text; using Xunit; namespace System.SpanTests @@ -24,20 +26,102 @@ public static partial class SpanTests new object[] { '\u2029' }, }; + [Fact] + public static void EnumerateLines_Default() + { + // Enumerations over default enumerator should return zero elements + + SpanLineEnumerator enumerator = default; + TestGI(enumerator); + TestI(enumerator); + Assert.Equal("", enumerator.Current.ToString()); + Assert.False(enumerator.MoveNext()); + Assert.Equal("", enumerator.Current.ToString()); + + static void TestGI(TEnumerator enumerator) where TEnumerator : IEnumerator>, allows ref struct + { + Assert.Equal("", enumerator.Current.ToString()); + try { enumerator.Reset(); } catch (NotSupportedException) { } + enumerator.Dispose(); + Assert.Equal("", enumerator.Current.ToString()); + + Assert.False(enumerator.MoveNext()); + try { enumerator.Reset(); } catch (NotSupportedException) { } + enumerator.Dispose(); + Assert.Equal("", enumerator.Current.ToString()); + Assert.False(enumerator.MoveNext()); + } + + static void TestI(TEnumerator enumerator) where TEnumerator : IEnumerator, allows ref struct + { + try { _ = enumerator.Current; } catch (NotSupportedException) { } + try { enumerator.Reset(); } catch (NotSupportedException) { } + try { _ = enumerator.Current; } catch (NotSupportedException) { } + + Assert.False(enumerator.MoveNext()); + try { enumerator.Reset(); } catch (NotSupportedException) { } + try { _ = enumerator.Current; } catch (NotSupportedException) { } + Assert.False(enumerator.MoveNext()); + } + } + [Fact] public static void EnumerateLines_Empty() { // Enumerations over empty inputs should return a single empty element var enumerator = Span.Empty.EnumerateLines().GetEnumerator(); + TestGI(enumerator); + TestI(enumerator); + Assert.Equal("", enumerator.Current.ToString()); Assert.True(enumerator.MoveNext()); Assert.Equal("", enumerator.Current.ToString()); Assert.False(enumerator.MoveNext()); + Assert.Equal("", enumerator.Current.ToString()); enumerator = ReadOnlySpan.Empty.EnumerateLines().GetEnumerator(); + TestGI(enumerator); + TestI(enumerator); + Assert.Equal("", enumerator.Current.ToString()); Assert.True(enumerator.MoveNext()); Assert.Equal("", enumerator.Current.ToString()); Assert.False(enumerator.MoveNext()); + Assert.Equal("", enumerator.Current.ToString()); + + static void TestGI(TEnumerator enumerator) where TEnumerator : IEnumerator>, allows ref struct + { + Assert.Equal("", enumerator.Current.ToString()); + try { enumerator.Reset(); } catch (NotSupportedException) { } + enumerator.Dispose(); + Assert.Equal("", enumerator.Current.ToString()); + + Assert.True(enumerator.MoveNext()); + try { enumerator.Reset(); } catch (NotSupportedException) { } + enumerator.Dispose(); + Assert.Equal("", enumerator.Current.ToString()); + + Assert.False(enumerator.MoveNext()); + try { enumerator.Reset(); } catch (NotSupportedException) { } + enumerator.Dispose(); + Assert.Equal("", enumerator.Current.ToString()); + Assert.False(enumerator.MoveNext()); + } + + static void TestI(TEnumerator enumerator) where TEnumerator : IEnumerator, allows ref struct + { + try { _ = enumerator.Current; } catch (NotSupportedException) { } + try { enumerator.Reset(); } catch (NotSupportedException) { } + try { _ = enumerator.Current; } catch (NotSupportedException) { } + + Assert.True(enumerator.MoveNext()); + try { enumerator.Reset(); } catch (NotSupportedException) { } + try { _ = enumerator.Current; } catch (NotSupportedException) { } + + Assert.False(enumerator.MoveNext()); + try { enumerator.Reset(); } catch (NotSupportedException) { } + try { _ = enumerator.Current; } catch (NotSupportedException) { } + Assert.False(enumerator.MoveNext()); + } } [Theory] @@ -70,12 +154,48 @@ public static void EnumerateLines_Battery(string input, string[] expectedRanges) List actualRangesNormalized = new List(); foreach (ReadOnlySpan line in input.AsSpan().EnumerateLines()) { - actualRangesNormalized.Add(GetNormalizedRangeFromSubspan(input, line)); + actualRangesNormalized.Add(GetNormalizedRangeFromSubSpan(input, line)); } Assert.Equal(expectedRangesNormalized, actualRangesNormalized); + Assert.Equal(expectedRangesNormalized, TestGI(input.AsSpan().EnumerateLines(), input)); - static unsafe Range GetNormalizedRangeFromSubspan(ReadOnlySpan outer, ReadOnlySpan inner) + static List TestGI(TEnumerator enumerator, string input) + where TEnumerator : IEnumerator>, allows ref struct + { + List actualRangesNormalized = new List(); + + Assert.Equal("", enumerator.Current.ToString()); + EnumerateLines_TestCurrentI(enumerator); + try { enumerator.Reset(); } catch (NotSupportedException) { } + enumerator.Dispose(); + Assert.Equal("", enumerator.Current.ToString()); + EnumerateLines_TestCurrentI(enumerator); + + while (enumerator.MoveNext()) + { + try { enumerator.Reset(); } catch (NotSupportedException) { } + enumerator.Dispose(); + + actualRangesNormalized.Add(GetNormalizedRangeFromSubSpan(input, enumerator.Current)); + EnumerateLines_TestCurrentI(enumerator); + + try { enumerator.Reset(); } catch (NotSupportedException) { } + enumerator.Dispose(); + } + + Assert.Equal("", enumerator.Current.ToString()); + EnumerateLines_TestCurrentI(enumerator); + try { enumerator.Reset(); } catch (NotSupportedException) { } + enumerator.Dispose(); + Assert.Equal("", enumerator.Current.ToString()); + EnumerateLines_TestCurrentI(enumerator); + Assert.False(enumerator.MoveNext()); + + return actualRangesNormalized; + } + + static unsafe Range GetNormalizedRangeFromSubSpan(ReadOnlySpan outer, ReadOnlySpan inner) { // We can't use MemoryExtensions.Overlaps because it doesn't handle empty spans in the way we need. @@ -158,12 +278,41 @@ public static void EnumerateLines_EnumerationIsNotPolynomialComplexity(char newl span = MemoryMarshal.CreateSpan(ref MemoryMarshal.GetReference(span), span.Length + 4096); var enumerator = span.EnumerateLines().GetEnumerator(); + TestGI(enumerator); + Assert.Equal(0, enumerator.Current.Length); Assert.True(enumerator.MoveNext()); Assert.Equal(512, enumerator.Current.Length); enumerator = ((ReadOnlySpan)span).EnumerateLines().GetEnumerator(); + TestGI(enumerator); + Assert.Equal(0, enumerator.Current.Length); Assert.True(enumerator.MoveNext()); Assert.Equal(512, enumerator.Current.Length); + + static void TestGI(TEnumerator enumerator) + where TEnumerator : IEnumerator>, allows ref struct + { + Assert.Equal(0, enumerator.Current.Length); + EnumerateLines_TestCurrentI(enumerator); + + try { enumerator.Reset(); } catch (NotSupportedException) { } + enumerator.Dispose(); + + Assert.True(enumerator.MoveNext()); + Assert.Equal(512, enumerator.Current.Length); + EnumerateLines_TestCurrentI(enumerator); + + try { enumerator.Reset(); } catch (NotSupportedException) { } + enumerator.Dispose(); + + Assert.Equal(512, enumerator.Current.Length); + EnumerateLines_TestCurrentI(enumerator); + } + } + + private static void EnumerateLines_TestCurrentI(TEnumerator enumerator) where TEnumerator : IEnumerator, allows ref struct + { + try { _ = enumerator.Current; } catch (NotSupportedException) { } } } } diff --git a/src/libraries/System.Memory/tests/Span/EnumerateRunes.cs b/src/libraries/System.Memory/tests/Span/EnumerateRunes.cs index 8db90c8b649d2..605784d7b291b 100644 --- a/src/libraries/System.Memory/tests/Span/EnumerateRunes.cs +++ b/src/libraries/System.Memory/tests/Span/EnumerateRunes.cs @@ -1,6 +1,7 @@ // 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; using System.Collections.Generic; using System.Text; using Xunit; @@ -10,10 +11,54 @@ namespace System.SpanTests public static partial class SpanTests { [Fact] - public static void EnumerateRunesEmpty() + public static void EnumerateRunes_DefaultAndEmpty() { - Assert.False(MemoryExtensions.EnumerateRunes(ReadOnlySpan.Empty).GetEnumerator().MoveNext()); - Assert.False(MemoryExtensions.EnumerateRunes(Span.Empty).GetEnumerator().MoveNext()); + SpanRuneEnumerator enumerator = default; + TestGI(enumerator); + TestI(enumerator); + Assert.Equal(default, enumerator.Current); + Assert.False(enumerator.MoveNext()); + Assert.Equal(default, enumerator.Current); + + enumerator = MemoryExtensions.EnumerateRunes(ReadOnlySpan.Empty).GetEnumerator(); + TestGI(enumerator); + TestI(enumerator); + Assert.Equal(default, enumerator.Current); + Assert.False(enumerator.MoveNext()); + Assert.Equal(default, enumerator.Current); + + enumerator = MemoryExtensions.EnumerateRunes(Span.Empty).GetEnumerator(); + TestGI(enumerator); + TestI(enumerator); + Assert.Equal(default, enumerator.Current); + Assert.False(enumerator.MoveNext()); + Assert.Equal(default, enumerator.Current); + + static void TestGI(TEnumerator enumerator) where TEnumerator : IEnumerator, allows ref struct + { + Assert.Equal(default, enumerator.Current); + try { enumerator.Reset(); } catch (NotSupportedException) { } + enumerator.Dispose(); + Assert.Equal(default, enumerator.Current); + + Assert.False(enumerator.MoveNext()); + try { enumerator.Reset(); } catch (NotSupportedException) { } + enumerator.Dispose(); + Assert.Equal(default, enumerator.Current); + Assert.False(enumerator.MoveNext()); + } + + static void TestI(TEnumerator enumerator) where TEnumerator : IEnumerator, allows ref struct + { + Assert.Equal(default(Rune), enumerator.Current); + try { enumerator.Reset(); } catch (NotSupportedException) { } + Assert.Equal(default(Rune), enumerator.Current); + + Assert.False(enumerator.MoveNext()); + try { enumerator.Reset(); } catch (NotSupportedException) { } + Assert.Equal(default(Rune), enumerator.Current); + Assert.False(enumerator.MoveNext()); + } } [Theory] @@ -39,6 +84,8 @@ public static void EnumerateRunes_Battery(char[] chars, int[] expected) enumeratedValues.Add(rune.Value); } Assert.Equal(expected, enumeratedValues.ToArray()); + Assert.Equal(expected, EnumerateRunes_TestGI(((Span)chars).EnumerateRunes()).ToArray()); + Assert.Equal(expected, EnumerateRunes_TestI(((Span)chars).EnumerateRunes()).ToArray()); // next, ROS @@ -48,6 +95,8 @@ public static void EnumerateRunes_Battery(char[] chars, int[] expected) enumeratedValues.Add(rune.Value); } Assert.Equal(expected, enumeratedValues.ToArray()); + Assert.Equal(expected, EnumerateRunes_TestGI(((ReadOnlySpan)chars).EnumerateRunes()).ToArray()); + Assert.Equal(expected, EnumerateRunes_TestI(((ReadOnlySpan)chars).EnumerateRunes()).ToArray()); } [Fact] @@ -65,6 +114,61 @@ public static void EnumerateRunes_DoesNotReadPastEndOfSpan() enumeratedValues.Add(rune.Value); } Assert.Equal(new int[] { 'y', '\uFFFD' }, enumeratedValues.ToArray()); + Assert.Equal(new int[] { 'y', '\uFFFD' }, EnumerateRunes_TestGI(span.EnumerateRunes()).ToArray()); + Assert.Equal(new int[] { 'y', '\uFFFD' }, EnumerateRunes_TestI(span.EnumerateRunes()).ToArray()); + } + + private static List EnumerateRunes_TestGI(TEnumerator enumerator) where TEnumerator : IEnumerator, allows ref struct + { + List enumeratedValues = new List(); + + Assert.Equal(default, enumerator.Current); + try { enumerator.Reset(); } catch (NotSupportedException) { } + enumerator.Dispose(); + Assert.Equal(default, enumerator.Current); + + while (enumerator.MoveNext()) + { + try { enumerator.Reset(); } catch (NotSupportedException) { } + enumerator.Dispose(); + + enumeratedValues.Add(enumerator.Current.Value); + + try { enumerator.Reset(); } catch (NotSupportedException) { } + enumerator.Dispose(); + } + + Assert.Equal(default, enumerator.Current); + try { enumerator.Reset(); } catch (NotSupportedException) { } + enumerator.Dispose(); + Assert.Equal(default, enumerator.Current); + + return enumeratedValues; + } + + private static List EnumerateRunes_TestI(TEnumerator enumerator) where TEnumerator : IEnumerator, allows ref struct + { + List enumeratedValues = new List(); + + Assert.Equal(default(Rune), enumerator.Current); + try { enumerator.Reset(); } catch (NotSupportedException) { } + Assert.Equal(default(Rune), enumerator.Current); + + while (enumerator.MoveNext()) + { + try { enumerator.Reset(); } catch (NotSupportedException) { } + + enumeratedValues.Add(((Rune)enumerator.Current).Value); + + try { enumerator.Reset(); } catch (NotSupportedException) { } + } + + Assert.Equal(default(Rune), enumerator.Current); + try { enumerator.Reset(); } catch (NotSupportedException) { } + Assert.Equal(default(Rune), enumerator.Current); + Assert.False(enumerator.MoveNext()); + + return enumeratedValues; } } } diff --git a/src/libraries/System.Memory/tests/Span/GetEnumerator.cs b/src/libraries/System.Memory/tests/Span/GetEnumerator.cs index 230292b1bb28a..c53ae1eb28945 100644 --- a/src/libraries/System.Memory/tests/Span/GetEnumerator.cs +++ b/src/libraries/System.Memory/tests/Span/GetEnumerator.cs @@ -4,6 +4,7 @@ using Xunit; using System.Linq; using System.Collections.Generic; +using System.Collections; namespace System.SpanTests { @@ -29,6 +30,8 @@ public static void GetEnumerator_ForEach_AllValuesReturnedCorrectly(int[] array) } Assert.Equal(Enumerable.Sum(array), sum); + Assert.Equal(Enumerable.Sum(array), SumGI(span.GetEnumerator())); + Assert.Equal(Enumerable.Sum(array), SumI(span.GetEnumerator())); } [Theory] @@ -48,6 +51,8 @@ public static void GetEnumerator_Manual_AllValuesReturnedCorrectly(int[] array) Assert.False(e.MoveNext()); Assert.Equal(Enumerable.Sum(array), sum); + Assert.Equal(Enumerable.Sum(array), SumGI(span.GetEnumerator())); + Assert.Equal(Enumerable.Sum(array), SumI(span.GetEnumerator())); } [Fact] @@ -78,6 +83,74 @@ public static void GetEnumerator_MoveNextOnDefault_ReturnsFalse() { Assert.False(default(Span.Enumerator).MoveNext()); Assert.ThrowsAny(() => default(Span.Enumerator).Current); + + TestGI(default(Span.Enumerator)); + TestI(default(Span.Enumerator)); + + static void TestGI(TEnumerator enumerator) where TEnumerator : IEnumerator, allows ref struct + { + Assert.False(enumerator.MoveNext()); + try { _ = enumerator.Current; } catch (Exception) { } + enumerator.Dispose(); + enumerator.Reset(); + Assert.False(enumerator.MoveNext()); + try { _ = enumerator.Current; } catch (Exception) { } + } + + static void TestI(TEnumerator enumerator) where TEnumerator : IEnumerator, allows ref struct + { + Assert.False(enumerator.MoveNext()); + try { _ = enumerator.Current; } catch (Exception) { } + enumerator.Reset(); + Assert.False(enumerator.MoveNext()); + try { _ = enumerator.Current; } catch (Exception) { } + } + } + + private static int SumGI(TEnumerator enumerator) where TEnumerator : IEnumerator, allows ref struct + { + int sum1 = 0; + enumerator.Dispose(); + while (enumerator.MoveNext()) + { + sum1 += enumerator.Current; + enumerator.Dispose(); + } + Assert.False(enumerator.MoveNext()); + + int sum2 = 0; + enumerator.Reset(); + enumerator.Dispose(); + while (enumerator.MoveNext()) + { + sum2 += enumerator.Current; + enumerator.Dispose(); + } + Assert.False(enumerator.MoveNext()); + + Assert.Equal(sum1, sum2); + return sum2; + } + + private static int SumI(TEnumerator enumerator) where TEnumerator : IEnumerator, allows ref struct + { + int sum1 = 0; + while (enumerator.MoveNext()) + { + sum1 += (int)enumerator.Current; + } + Assert.False(enumerator.MoveNext()); + + int sum2 = 0; + enumerator.Reset(); + while (enumerator.MoveNext()) + { + sum2 += (int)enumerator.Current; + } + Assert.False(enumerator.MoveNext()); + + Assert.Equal(sum1, sum2); + return sum2; } } } diff --git a/src/libraries/System.Numerics.Tensors/ref/System.Numerics.Tensors.netcore.cs b/src/libraries/System.Numerics.Tensors/ref/System.Numerics.Tensors.netcore.cs index d3c31753c42ba..e05b03924f7d3 100644 --- a/src/libraries/System.Numerics.Tensors/ref/System.Numerics.Tensors.netcore.cs +++ b/src/libraries/System.Numerics.Tensors/ref/System.Numerics.Tensors.netcore.cs @@ -154,12 +154,16 @@ public void CopyTo(scoped System.Numerics.Tensors.TensorSpan destination) { } public bool TryCopyTo(scoped System.Numerics.Tensors.TensorSpan destination) { throw null; } public bool TryFlattenTo(scoped System.Span destination) { throw null; } public void FlattenTo(scoped System.Span destination) { throw null; } - public ref partial struct Enumerator + public ref partial struct Enumerator : System.Collections.Generic.IEnumerator, System.Collections.IEnumerator, System.IDisposable { private object _dummy; private int _dummyPrimitive; public ref readonly T Current { get { throw null; } } public bool MoveNext() { throw null; } + T System.Collections.Generic.IEnumerator.Current { get { throw null; } } + object System.Collections.IEnumerator.Current { get { throw null; } } + void System.Collections.IEnumerator.Reset() { throw null; } + void System.IDisposable.Dispose() { throw null; } } } [System.Diagnostics.CodeAnalysis.Experimental("SYSLIB5001", UrlFormat = "https://aka.ms/dotnet-warnings/{0}")] @@ -734,12 +738,16 @@ public void FlattenTo(scoped System.Span destination) { } public override string ToString() { throw null; } public bool TryCopyTo(scoped System.Numerics.Tensors.TensorSpan destination) { throw null; } public bool TryFlattenTo(scoped System.Span destination) { throw null; } - public ref partial struct Enumerator + public ref partial struct Enumerator : System.Collections.Generic.IEnumerator, System.Collections.IEnumerator, System.IDisposable { private object _dummy; private int _dummyPrimitive; public ref T Current { get { throw null; } } public bool MoveNext() { throw null; } + T System.Collections.Generic.IEnumerator.Current { get { throw null; } } + object System.Collections.IEnumerator.Current { get { throw null; } } + void System.Collections.IEnumerator.Reset() { throw null; } + void System.IDisposable.Dispose() { throw null; } } } [System.Diagnostics.CodeAnalysis.Experimental("SYSLIB5001", UrlFormat = "https://aka.ms/dotnet-warnings/{0}")] diff --git a/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/ReadOnlyTensorSpan.cs b/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/ReadOnlyTensorSpan.cs index c4bdb876dd627..7b0537b9c3817 100644 --- a/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/ReadOnlyTensorSpan.cs +++ b/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/ReadOnlyTensorSpan.cs @@ -2,6 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Buffers; +using System.Collections; +using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; @@ -467,7 +469,7 @@ public static ReadOnlyTensorSpan CastUp(ReadOnlyTensorSpan new Enumerator(this); /// Enumerates the elements of a . - public ref struct Enumerator + public ref struct Enumerator : IEnumerator { /// The span being enumerated. private readonly ReadOnlyTensorSpan _span; @@ -484,7 +486,8 @@ internal Enumerator(ReadOnlyTensorSpan span) _span = span; _items = -1; _curIndexes = new nint[_span.Rank]; - _curIndexes[_span.Rank - 1] = -1; + if (_span.Rank > 0) + _curIndexes[_span.Rank - 1] = -1; } /// Advances the enumerator to the next element of the span. @@ -505,6 +508,31 @@ public ref readonly T Current [MethodImpl(MethodImplOptions.AggressiveInlining)] get => ref _span[_curIndexes]; } + + /// Gets the element at the current position of the enumerator. + T IEnumerator.Current + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _span[_curIndexes]; + } + + /// Gets the element at the current position of the enumerator. + object IEnumerator.Current + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _span[_curIndexes]!; + } + + /// Sets the current position of the enumerator to its initial position, which is before the first element in the tensor span. + void IEnumerator.Reset() + { + _items = -1; + _curIndexes.Clear(); + if (_span.Rank > 0) + _curIndexes[_span.Rank - 1] = -1; + } + + void IDisposable.Dispose() { } } /// @@ -686,7 +714,7 @@ internal ReadOnlyTensorSpan Slice(scoped ReadOnlySpan lengths) public ReadOnlyTensorSpan Slice(params scoped ReadOnlySpan ranges) { if (ranges.Length != Lengths.Length) - throw new ArgumentOutOfRangeException(nameof(ranges), "Number of dimensions to slice does not equal the number of dimensions in the span"); + ThrowHelper.ThrowIndexOutOfRangeException(); ReadOnlyTensorSpan toReturn; scoped Span lengths; diff --git a/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/TensorSpan.cs b/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/TensorSpan.cs index dcfeb47f9aab0..c62916e8dd33d 100644 --- a/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/TensorSpan.cs +++ b/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/TensorSpan.cs @@ -2,6 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Buffers; +using System.Collections; +using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; @@ -460,7 +462,7 @@ public override int GetHashCode() => public Enumerator GetEnumerator() => new Enumerator(this); /// Enumerates the elements of a . - public ref struct Enumerator + public ref struct Enumerator : IEnumerator { /// The span being enumerated. private readonly TensorSpan _span; @@ -477,8 +479,8 @@ internal Enumerator(TensorSpan span) _span = span; _items = -1; _curIndexes = new nint[_span.Rank]; - - _curIndexes[_span.Rank - 1] = -1; + if (_span.Rank > 0) + _curIndexes[_span.Rank - 1] = -1; } /// Advances the enumerator to the next element of the span. @@ -499,6 +501,31 @@ public ref T Current [MethodImpl(MethodImplOptions.AggressiveInlining)] get => ref _span[_curIndexes]; } + + /// Gets the element at the current position of the enumerator. + T IEnumerator.Current + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _span[_curIndexes]; + } + + /// Gets the element at the current position of the enumerator. + object IEnumerator.Current + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _span[_curIndexes]!; + } + + /// Sets the current position of the enumerator to its initial position, which is before the first element in the tensor span. + void IEnumerator.Reset() + { + _items = -1; + _curIndexes.Clear(); + if (_span.Rank > 0) + _curIndexes[_span.Rank - 1] = -1; + } + + void IDisposable.Dispose() { } } /// diff --git a/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/TensorSpanHelpers.cs b/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/TensorSpanHelpers.cs index 0d5623eeed830..1084f589e4ff7 100644 --- a/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/TensorSpanHelpers.cs +++ b/src/libraries/System.Numerics.Tensors/src/System/Numerics/Tensors/netcore/TensorSpanHelpers.cs @@ -220,7 +220,7 @@ public static void ValidateStrides(ReadOnlySpan strides, ReadOnlySpanThe length of the TensorSpan we are iterating over. public static void AdjustIndexes(int curIndex, nint addend, Span curIndexes, scoped ReadOnlySpan length) { - if (addend <= 0 || curIndex < 0) + if (addend <= 0 || curIndex < 0 || length[curIndex] <= 0) return; curIndexes[curIndex] += addend; diff --git a/src/libraries/System.Numerics.Tensors/tests/ReadOnlyTensorSpanTests.cs b/src/libraries/System.Numerics.Tensors/tests/ReadOnlyTensorSpanTests.cs index d88ccfb9a7fbe..65a5698a241d5 100644 --- a/src/libraries/System.Numerics.Tensors/tests/ReadOnlyTensorSpanTests.cs +++ b/src/libraries/System.Numerics.Tensors/tests/ReadOnlyTensorSpanTests.cs @@ -1,12 +1,11 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; using System.Buffers; +using System.Collections; using System.Collections.Generic; using System.Linq; -using System.Text; -using System.Threading.Tasks; +using System.Runtime.CompilerServices; using Xunit; namespace System.Numerics.Tensors.Tests @@ -14,13 +13,13 @@ namespace System.Numerics.Tensors.Tests public class ReadOnlyTensorSpanTests { [Fact] - public static void ReadOnlyTensorSpanSystemArrayConstructorTests() + public static void ReadOnlyTensorSpan_SystemArrayConstructorTests() { // When using System.Array constructor make sure the type of the array matches T[] - Assert.Throws(() => new TensorSpan(array: new[] { 1 })); + Assert.Throws(() => new ReadOnlyTensorSpan(array: new[] { 1 })); string[] stringArray = { "a", "b", "c" }; - Assert.Throws(() => new TensorSpan(array: stringArray)); + Assert.Throws(() => new ReadOnlyTensorSpan(array: stringArray)); // Make sure basic T[,] constructor works int[,] a = new int[,] { { 91, 92, -93, 94 } }; @@ -32,6 +31,7 @@ public static void ReadOnlyTensorSpanSystemArrayConstructorTests() Assert.Equal(92, spanInt[0, 1]); Assert.Equal(-93, spanInt[0, 2]); Assert.Equal(94, spanInt[0, 3]); + ReadOnlyTensorSpan_TestEnumerator(spanInt); // Make sure null works // Should be a tensor with 0 elements and Rank 0 and no strides or lengths @@ -40,6 +40,7 @@ public static void ReadOnlyTensorSpanSystemArrayConstructorTests() Assert.Equal(0, spanInt.Rank); Assert.Equal(0, spanInt.Lengths.Length); Assert.Equal(0, spanInt.Strides.Length); + ReadOnlyTensorSpan_TestEnumerator(spanInt); // Make sure empty array works // Should be a Tensor with 0 elements but Rank 2 with dimension 0 length 0 @@ -51,9 +52,11 @@ public static void ReadOnlyTensorSpanSystemArrayConstructorTests() Assert.Equal(0, spanInt.FlattenedLength); Assert.Equal(0, spanInt.Strides[0]); Assert.Equal(0, spanInt.Strides[1]); + ReadOnlyTensorSpan_TestEnumerator(spanInt); // Make sure it still throws on index 0, 0 Assert.Throws(() => { var spanInt = new ReadOnlyTensorSpan(b); + ReadOnlyTensorSpan_TestEnumerator(spanInt); var x = spanInt[0, 0]; }); @@ -66,6 +69,7 @@ public static void ReadOnlyTensorSpanSystemArrayConstructorTests() Assert.Equal(92, spanInt[0, 1]); Assert.Equal(-93, spanInt[1, 0]); Assert.Equal(94, spanInt[1, 1]); + ReadOnlyTensorSpan_TestEnumerator(spanInt); // Make sure can use only some of the array spanInt = new ReadOnlyTensorSpan(a, (int[])[0, 0], [1, 2], default); @@ -74,27 +78,32 @@ public static void ReadOnlyTensorSpanSystemArrayConstructorTests() Assert.Equal(2, spanInt.Lengths[1]); Assert.Equal(91, spanInt[0, 0]); Assert.Equal(92, spanInt[0, 1]); + ReadOnlyTensorSpan_TestEnumerator(spanInt); Assert.Throws(() => { var spanInt = new ReadOnlyTensorSpan(a, (int[])[0, 0], [1, 2], default); + ReadOnlyTensorSpan_TestEnumerator(spanInt); var x = spanInt[1, 1]; }); Assert.Throws(() => { var spanInt = new ReadOnlyTensorSpan(a, (int[])[0, 0], [1, 2], default); + ReadOnlyTensorSpan_TestEnumerator(spanInt); var x = spanInt[0, -1]; }); Assert.Throws(() => { var spanInt = new ReadOnlyTensorSpan(a, (int[])[0, 0], [1, 2], default); + ReadOnlyTensorSpan_TestEnumerator(spanInt); var x = spanInt[-1, 0]; }); Assert.Throws(() => { var spanInt = new ReadOnlyTensorSpan(a, (int[])[0, 0], [1, 2], default); + ReadOnlyTensorSpan_TestEnumerator(spanInt); var x = spanInt[1, 0]; }); @@ -105,6 +114,7 @@ public static void ReadOnlyTensorSpanSystemArrayConstructorTests() Assert.Equal(2, spanInt.Lengths[1]); Assert.Equal(92, spanInt[0, 0]); Assert.Equal(-93, spanInt[0, 1]); + ReadOnlyTensorSpan_TestEnumerator(spanInt); // Make sure Index offset works correctly spanInt = new ReadOnlyTensorSpan(a, (int[])[0, 2], [1, 2], default); @@ -113,6 +123,7 @@ public static void ReadOnlyTensorSpanSystemArrayConstructorTests() Assert.Equal(2, spanInt.Lengths[1]); Assert.Equal(-93, spanInt[0, 0]); Assert.Equal(94, spanInt[0, 1]); + ReadOnlyTensorSpan_TestEnumerator(spanInt); // Make sure 2D array works with strides of all 0 and initial offset to loop over last element again spanInt = new ReadOnlyTensorSpan(a, (int[])[0, 3], [2, 2], [0, 0]); @@ -123,6 +134,7 @@ public static void ReadOnlyTensorSpanSystemArrayConstructorTests() Assert.Equal(94, spanInt[0, 1]); Assert.Equal(94, spanInt[1, 0]); Assert.Equal(94, spanInt[1, 1]); + ReadOnlyTensorSpan_TestEnumerator(spanInt); // Make sure we catch that there aren't enough elements in the array for the lengths Assert.Throws(() => @@ -139,6 +151,7 @@ public static void ReadOnlyTensorSpanSystemArrayConstructorTests() Assert.Equal(92, spanInt[0, 1]); Assert.Equal(-93, spanInt[1, 0]); Assert.Equal(94, spanInt[1, 1]); + ReadOnlyTensorSpan_TestEnumerator(spanInt); // Make sure 2D array works with stride of 0 to loop over first 2 elements again spanInt = new ReadOnlyTensorSpan(a, (int[])[0, 0], [2, 2], [0, 1]); @@ -149,6 +162,7 @@ public static void ReadOnlyTensorSpanSystemArrayConstructorTests() Assert.Equal(92, spanInt[0, 1]); Assert.Equal(91, spanInt[1, 0]); Assert.Equal(92, spanInt[1, 1]); + ReadOnlyTensorSpan_TestEnumerator(spanInt); // Make sure 2D array works with stride of 0 and initial offset to loop over last 2 elements again spanInt = new ReadOnlyTensorSpan(a, (int[])[0, 2], [2, 2], [0, 1]); @@ -159,6 +173,7 @@ public static void ReadOnlyTensorSpanSystemArrayConstructorTests() Assert.Equal(94, spanInt[0, 1]); Assert.Equal(-93, spanInt[1, 0]); Assert.Equal(94, spanInt[1, 1]); + ReadOnlyTensorSpan_TestEnumerator(spanInt); // Make sure strides can't be negative Assert.Throws(() => @@ -189,6 +204,7 @@ public static void ReadOnlyTensorSpanSystemArrayConstructorTests() Assert.Equal(91, spanInt[0, 1]); Assert.Equal(-93, spanInt[1, 0]); Assert.Equal(-93, spanInt[1, 1]); + ReadOnlyTensorSpan_TestEnumerator(spanInt); // Make sure you can't overlap elements using strides Assert.Throws(() => @@ -205,6 +221,7 @@ public static void ReadOnlyTensorSpanSystemArrayConstructorTests() Assert.Equal(94, spanInt[0, 1]); Assert.Equal(94, spanInt[1, 0]); Assert.Equal(94, spanInt[1, 1]); + ReadOnlyTensorSpan_TestEnumerator(spanInt); //Make sure it works with NIndex spanInt = new ReadOnlyTensorSpan(a, (NIndex[])[1, 1], [2, 2], [0, 0]); @@ -215,6 +232,7 @@ public static void ReadOnlyTensorSpanSystemArrayConstructorTests() Assert.Equal(94, spanInt[0, 1]); Assert.Equal(94, spanInt[1, 0]); Assert.Equal(94, spanInt[1, 1]); + ReadOnlyTensorSpan_TestEnumerator(spanInt); //Make sure it works with NIndex spanInt = new ReadOnlyTensorSpan(a, (NIndex[])[^1, ^1], [2, 2], [0, 0]); @@ -225,15 +243,16 @@ public static void ReadOnlyTensorSpanSystemArrayConstructorTests() Assert.Equal(94, spanInt[0, 1]); Assert.Equal(94, spanInt[1, 0]); Assert.Equal(94, spanInt[1, 1]); + ReadOnlyTensorSpan_TestEnumerator(spanInt); } [Fact] - public static void ReadOnlyTensorSpanArrayConstructorTests() + public static void ReadOnlyTensorSpan_ArrayConstructorTests() { // Make sure exception is thrown if lengths and strides would let you go past the end of the array - Assert.Throws(() => new TensorSpan(new double[0], lengths: new IntPtr[] { 2 }, strides: new IntPtr[] { 1 })); - Assert.Throws(() => new TensorSpan(new double[1], lengths: new IntPtr[] { 2 }, strides: new IntPtr[] { 1 })); - Assert.Throws(() => new TensorSpan(new double[2], lengths: new IntPtr[] { 2 }, strides: new IntPtr[] { 2 })); + Assert.Throws(() => new ReadOnlyTensorSpan(new double[0], lengths: new IntPtr[] { 2 }, strides: new IntPtr[] { 1 })); + Assert.Throws(() => new ReadOnlyTensorSpan(new double[1], lengths: new IntPtr[] { 2 }, strides: new IntPtr[] { 1 })); + Assert.Throws(() => new ReadOnlyTensorSpan(new double[2], lengths: new IntPtr[] { 2 }, strides: new IntPtr[] { 2 })); // Make sure basic T[] constructor works int[] a = { 91, 92, -93, 94 }; @@ -244,6 +263,7 @@ public static void ReadOnlyTensorSpanArrayConstructorTests() Assert.Equal(92, spanInt[1]); Assert.Equal(-93, spanInt[2]); Assert.Equal(94, spanInt[3]); + ReadOnlyTensorSpan_TestEnumerator(spanInt); // Make sure null works // Should be a tensor with 0 elements and Rank 0 and no strides or lengths @@ -251,6 +271,7 @@ public static void ReadOnlyTensorSpanArrayConstructorTests() Assert.Equal(0, spanInt.Rank); Assert.Equal(0, spanInt.Lengths.Length); Assert.Equal(0, spanInt.Strides.Length); + ReadOnlyTensorSpan_TestEnumerator(spanInt); // Make sure empty array works // Should be a Tensor with 0 elements but Rank 1 with dimension 0 length 0 @@ -260,9 +281,11 @@ public static void ReadOnlyTensorSpanArrayConstructorTests() Assert.Equal(0, spanInt.Lengths[0]); Assert.Equal(0, spanInt.FlattenedLength); Assert.Equal(0, spanInt.Strides[0]); + ReadOnlyTensorSpan_TestEnumerator(spanInt); // Make sure it still throws on index 0 Assert.Throws(() => { var spanInt = new ReadOnlyTensorSpan(b); + ReadOnlyTensorSpan_TestEnumerator(spanInt); var x = spanInt[0]; }); @@ -273,16 +296,18 @@ public static void ReadOnlyTensorSpanArrayConstructorTests() Assert.Equal(0, spanInt.Lengths[0]); Assert.Equal(0, spanInt.FlattenedLength); Assert.Equal(0, spanInt.Strides[0]); + ReadOnlyTensorSpan_TestEnumerator(spanInt); // Make sure 2D array works - spanInt = new ReadOnlyTensorSpan(a, new Index(0), [2,2], default); + spanInt = new ReadOnlyTensorSpan(a, new Index(0), [2, 2], default); Assert.Equal(2, spanInt.Rank); Assert.Equal(2, spanInt.Lengths[0]); Assert.Equal(2, spanInt.Lengths[1]); - Assert.Equal(91, spanInt[0,0]); - Assert.Equal(92, spanInt[0,1]); - Assert.Equal(-93, spanInt[1,0]); - Assert.Equal(94, spanInt[1,1]); + Assert.Equal(91, spanInt[0, 0]); + Assert.Equal(92, spanInt[0, 1]); + Assert.Equal(-93, spanInt[1, 0]); + Assert.Equal(94, spanInt[1, 1]); + ReadOnlyTensorSpan_TestEnumerator(spanInt); // Make sure can use only some of the array spanInt = new ReadOnlyTensorSpan(a, new Index(0), [1, 2], default); @@ -291,13 +316,16 @@ public static void ReadOnlyTensorSpanArrayConstructorTests() Assert.Equal(2, spanInt.Lengths[1]); Assert.Equal(91, spanInt[0, 0]); Assert.Equal(92, spanInt[0, 1]); + ReadOnlyTensorSpan_TestEnumerator(spanInt); Assert.Throws(() => { var spanInt = new ReadOnlyTensorSpan(a, new Index(0), [1, 2], default); + ReadOnlyTensorSpan_TestEnumerator(spanInt); var x = spanInt[1, 1]; }); Assert.Throws(() => { var spanInt = new ReadOnlyTensorSpan(a, new Index(0), [1, 2], default); + ReadOnlyTensorSpan_TestEnumerator(spanInt); var x = spanInt[1, 0]; }); @@ -308,6 +336,7 @@ public static void ReadOnlyTensorSpanArrayConstructorTests() Assert.Equal(2, spanInt.Lengths[1]); Assert.Equal(92, spanInt[0, 0]); Assert.Equal(-93, spanInt[0, 1]); + ReadOnlyTensorSpan_TestEnumerator(spanInt); // Make sure Index offset works correctly spanInt = new ReadOnlyTensorSpan(a, new Index(2), [1, 2], default); @@ -316,6 +345,7 @@ public static void ReadOnlyTensorSpanArrayConstructorTests() Assert.Equal(2, spanInt.Lengths[1]); Assert.Equal(-93, spanInt[0, 0]); Assert.Equal(94, spanInt[0, 1]); + ReadOnlyTensorSpan_TestEnumerator(spanInt); // Make sure we catch that there aren't enough elements in the array for the lengths Assert.Throws(() => { @@ -331,6 +361,7 @@ public static void ReadOnlyTensorSpanArrayConstructorTests() Assert.Equal(92, spanInt[0, 1]); Assert.Equal(-93, spanInt[1, 0]); Assert.Equal(94, spanInt[1, 1]); + ReadOnlyTensorSpan_TestEnumerator(spanInt); // Make sure 2D array works with stride of 0 to loop over first 2 elements again spanInt = new ReadOnlyTensorSpan(a, new Index(0), [2, 2], [0, 1]); @@ -341,6 +372,7 @@ public static void ReadOnlyTensorSpanArrayConstructorTests() Assert.Equal(92, spanInt[0, 1]); Assert.Equal(91, spanInt[1, 0]); Assert.Equal(92, spanInt[1, 1]); + ReadOnlyTensorSpan_TestEnumerator(spanInt); // Make sure 2D array works with stride of 0 and initial offset to loop over last 2 elements again spanInt = new ReadOnlyTensorSpan(a, new Index(2), [2, 2], [0, 1]); @@ -351,6 +383,7 @@ public static void ReadOnlyTensorSpanArrayConstructorTests() Assert.Equal(94, spanInt[0, 1]); Assert.Equal(-93, spanInt[1, 0]); Assert.Equal(94, spanInt[1, 1]); + ReadOnlyTensorSpan_TestEnumerator(spanInt); // Make sure 2D array works with strides of all 0 and initial offset to loop over last element again spanInt = new ReadOnlyTensorSpan(a, new Index(3), [2, 2], [0, 0]); @@ -361,6 +394,7 @@ public static void ReadOnlyTensorSpanArrayConstructorTests() Assert.Equal(94, spanInt[0, 1]); Assert.Equal(94, spanInt[1, 0]); Assert.Equal(94, spanInt[1, 1]); + ReadOnlyTensorSpan_TestEnumerator(spanInt); // Make sure strides can't be negative Assert.Throws(() => { @@ -387,6 +421,7 @@ public static void ReadOnlyTensorSpanArrayConstructorTests() Assert.Equal(91, spanInt[0, 1]); Assert.Equal(-93, spanInt[1, 0]); Assert.Equal(-93, spanInt[1, 1]); + ReadOnlyTensorSpan_TestEnumerator(spanInt); // Make sure you can't overlap elements using strides Assert.Throws(() => { @@ -395,10 +430,10 @@ public static void ReadOnlyTensorSpanArrayConstructorTests() } [Fact] - public static void ReadOnlyTensorSpanSpanConstructorTests() + public static void ReadOnlyTensorSpan_SpanConstructorTests() { // Make sure basic T[] constructor works - Span a = [ 91, 92, -93, 94 ]; + Span a = [91, 92, -93, 94]; scoped ReadOnlyTensorSpan spanInt = new ReadOnlyTensorSpan(a); Assert.Equal(1, spanInt.Rank); Assert.Equal(4, spanInt.Lengths[0]); @@ -406,6 +441,7 @@ public static void ReadOnlyTensorSpanSpanConstructorTests() Assert.Equal(92, spanInt[1]); Assert.Equal(-93, spanInt[2]); Assert.Equal(94, spanInt[3]); + ReadOnlyTensorSpan_TestEnumerator(spanInt); // Make sure empty span works // Should be a Tensor with 0 elements but Rank 1 with dimension 0 length 0 @@ -415,10 +451,12 @@ public static void ReadOnlyTensorSpanSpanConstructorTests() Assert.Equal(0, spanInt.Lengths[0]); Assert.Equal(0, spanInt.FlattenedLength); Assert.Equal(0, spanInt.Strides[0]); + ReadOnlyTensorSpan_TestEnumerator(spanInt); // Make sure it still throws on index 0 Assert.Throws(() => { Span b = []; var spanInt = new ReadOnlyTensorSpan(b); + ReadOnlyTensorSpan_TestEnumerator(spanInt); var x = spanInt[0]; }); @@ -431,6 +469,7 @@ public static void ReadOnlyTensorSpanSpanConstructorTests() Assert.Equal(92, spanInt[0, 1]); Assert.Equal(-93, spanInt[1, 0]); Assert.Equal(94, spanInt[1, 1]); + ReadOnlyTensorSpan_TestEnumerator(spanInt); // Make sure can use only some of the array spanInt = new ReadOnlyTensorSpan(a, [1, 2], default); @@ -439,15 +478,18 @@ public static void ReadOnlyTensorSpanSpanConstructorTests() Assert.Equal(2, spanInt.Lengths[1]); Assert.Equal(91, spanInt[0, 0]); Assert.Equal(92, spanInt[0, 1]); + ReadOnlyTensorSpan_TestEnumerator(spanInt); Assert.Throws(() => { Span a = [91, 92, -93, 94]; var spanInt = new ReadOnlyTensorSpan(a, [1, 2], default); + ReadOnlyTensorSpan_TestEnumerator(spanInt); var x = spanInt[1, 1]; }); Assert.Throws(() => { Span a = [91, 92, -93, 94]; var spanInt = new ReadOnlyTensorSpan(a, [1, 2], default); + ReadOnlyTensorSpan_TestEnumerator(spanInt); var x = spanInt[1, 0]; }); @@ -458,6 +500,7 @@ public static void ReadOnlyTensorSpanSpanConstructorTests() Assert.Equal(2, spanInt.Lengths[1]); Assert.Equal(92, spanInt[0, 0]); Assert.Equal(-93, spanInt[0, 1]); + ReadOnlyTensorSpan_TestEnumerator(spanInt); // Make sure Index offset works correctly spanInt = new ReadOnlyTensorSpan(a.Slice(2), [1, 2], default); @@ -466,6 +509,7 @@ public static void ReadOnlyTensorSpanSpanConstructorTests() Assert.Equal(2, spanInt.Lengths[1]); Assert.Equal(-93, spanInt[0, 0]); Assert.Equal(94, spanInt[0, 1]); + ReadOnlyTensorSpan_TestEnumerator(spanInt); // Make sure we catch that there aren't enough elements in the array for the lengths Assert.Throws(() => { @@ -482,6 +526,7 @@ public static void ReadOnlyTensorSpanSpanConstructorTests() Assert.Equal(92, spanInt[0, 1]); Assert.Equal(-93, spanInt[1, 0]); Assert.Equal(94, spanInt[1, 1]); + ReadOnlyTensorSpan_TestEnumerator(spanInt); // Make sure 2D array works with stride of 0 to loop over first 2 elements again spanInt = new ReadOnlyTensorSpan(a, [2, 2], [0, 1]); @@ -492,6 +537,7 @@ public static void ReadOnlyTensorSpanSpanConstructorTests() Assert.Equal(92, spanInt[0, 1]); Assert.Equal(91, spanInt[1, 0]); Assert.Equal(92, spanInt[1, 1]); + ReadOnlyTensorSpan_TestEnumerator(spanInt); // Make sure 2D array works with stride of 0 and initial offset to loop over last 2 elements again spanInt = new ReadOnlyTensorSpan(a.Slice(2), [2, 2], [0, 1]); @@ -502,6 +548,7 @@ public static void ReadOnlyTensorSpanSpanConstructorTests() Assert.Equal(94, spanInt[0, 1]); Assert.Equal(-93, spanInt[1, 0]); Assert.Equal(94, spanInt[1, 1]); + ReadOnlyTensorSpan_TestEnumerator(spanInt); // Make sure 2D array works with strides of all 0 and initial offset to loop over last element again spanInt = new ReadOnlyTensorSpan(a.Slice(3), [2, 2], [0, 0]); @@ -512,6 +559,7 @@ public static void ReadOnlyTensorSpanSpanConstructorTests() Assert.Equal(94, spanInt[0, 1]); Assert.Equal(94, spanInt[1, 0]); Assert.Equal(94, spanInt[1, 1]); + ReadOnlyTensorSpan_TestEnumerator(spanInt); // Make sure strides can't be negative Assert.Throws(() => { @@ -542,6 +590,7 @@ public static void ReadOnlyTensorSpanSpanConstructorTests() Assert.Equal(91, spanInt[0, 1]); Assert.Equal(-93, spanInt[1, 0]); Assert.Equal(-93, spanInt[1, 1]); + ReadOnlyTensorSpan_TestEnumerator(spanInt); // Make sure you can't overlap elements using strides Assert.Throws(() => { @@ -551,7 +600,7 @@ public static void ReadOnlyTensorSpanSpanConstructorTests() } [Fact] - public static unsafe void ReadOnlyTensorSpanPointerConstructorTests() + public static unsafe void ReadOnlyTensorSpan_PointerConstructorTests() { // Make sure basic T[] constructor works Span a = [91, 92, -93, 94]; @@ -565,6 +614,7 @@ public static unsafe void ReadOnlyTensorSpanPointerConstructorTests() Assert.Equal(92, spanInt[1]); Assert.Equal(-93, spanInt[2]); Assert.Equal(94, spanInt[3]); + ReadOnlyTensorSpan_TestEnumerator(spanInt); } // Make sure empty span works @@ -577,6 +627,7 @@ public static unsafe void ReadOnlyTensorSpanPointerConstructorTests() Assert.Equal(0, spanInt.Lengths[0]); Assert.Equal(0, spanInt.FlattenedLength); Assert.Equal(0, spanInt.Strides[0]); + ReadOnlyTensorSpan_TestEnumerator(spanInt); // Make sure it still throws on index 0 Assert.Throws(() => { @@ -584,6 +635,7 @@ public static unsafe void ReadOnlyTensorSpanPointerConstructorTests() fixed (int* p = b) { var spanInt = new ReadOnlyTensorSpan(p, 0); + ReadOnlyTensorSpan_TestEnumerator(spanInt); var x = spanInt[0]; } }); @@ -601,6 +653,7 @@ public static unsafe void ReadOnlyTensorSpanPointerConstructorTests() Assert.Equal(92, spanInt[0, 1]); Assert.Equal(-93, spanInt[1, 0]); Assert.Equal(94, spanInt[1, 1]); + ReadOnlyTensorSpan_TestEnumerator(spanInt); // Make sure can use only some of the array spanInt = new ReadOnlyTensorSpan(p, 4, [1, 2], default); @@ -609,12 +662,14 @@ public static unsafe void ReadOnlyTensorSpanPointerConstructorTests() Assert.Equal(2, spanInt.Lengths[1]); Assert.Equal(91, spanInt[0, 0]); Assert.Equal(92, spanInt[0, 1]); + ReadOnlyTensorSpan_TestEnumerator(spanInt); Assert.Throws(() => { Span a = [91, 92, -93, 94]; fixed (int* p = a) { var spanInt = new ReadOnlyTensorSpan(p, 4, [1, 2], default); + ReadOnlyTensorSpan_TestEnumerator(spanInt); var x = spanInt[1, 1]; } }); @@ -625,6 +680,7 @@ public static unsafe void ReadOnlyTensorSpanPointerConstructorTests() fixed (int* p = a) { var spanInt = new ReadOnlyTensorSpan(p, 4, [1, 2], default); + ReadOnlyTensorSpan_TestEnumerator(spanInt); var x = spanInt[1, 0]; } }); @@ -636,6 +692,7 @@ public static unsafe void ReadOnlyTensorSpanPointerConstructorTests() Assert.Equal(2, spanInt.Lengths[1]); Assert.Equal(92, spanInt[0, 0]); Assert.Equal(-93, spanInt[0, 1]); + ReadOnlyTensorSpan_TestEnumerator(spanInt); // Make sure Index offset works correctly spanInt = new ReadOnlyTensorSpan(p + 2, 2, [1, 2], default); @@ -644,6 +701,7 @@ public static unsafe void ReadOnlyTensorSpanPointerConstructorTests() Assert.Equal(2, spanInt.Lengths[1]); Assert.Equal(-93, spanInt[0, 0]); Assert.Equal(94, spanInt[0, 1]); + ReadOnlyTensorSpan_TestEnumerator(spanInt); // Make sure we catch that there aren't enough elements in the array for the lengths Assert.Throws(() => @@ -664,6 +722,7 @@ public static unsafe void ReadOnlyTensorSpanPointerConstructorTests() Assert.Equal(92, spanInt[0, 1]); Assert.Equal(-93, spanInt[1, 0]); Assert.Equal(94, spanInt[1, 1]); + ReadOnlyTensorSpan_TestEnumerator(spanInt); // Make sure 2D array works with stride of 0 to loop over first 2 elements again spanInt = new ReadOnlyTensorSpan(p, 4, [2, 2], [0, 1]); @@ -674,6 +733,7 @@ public static unsafe void ReadOnlyTensorSpanPointerConstructorTests() Assert.Equal(92, spanInt[0, 1]); Assert.Equal(91, spanInt[1, 0]); Assert.Equal(92, spanInt[1, 1]); + ReadOnlyTensorSpan_TestEnumerator(spanInt); // Make sure 2D array works with stride of 0 and initial offset to loop over last 2 elements again spanInt = new ReadOnlyTensorSpan(p + 2, 2, [2, 2], [0, 1]); @@ -684,6 +744,7 @@ public static unsafe void ReadOnlyTensorSpanPointerConstructorTests() Assert.Equal(94, spanInt[0, 1]); Assert.Equal(-93, spanInt[1, 0]); Assert.Equal(94, spanInt[1, 1]); + ReadOnlyTensorSpan_TestEnumerator(spanInt); // Make sure 2D array works with strides of all 0 and initial offset to loop over last element again spanInt = new ReadOnlyTensorSpan(p + 3, 1, [2, 2], [0, 0]); @@ -694,6 +755,7 @@ public static unsafe void ReadOnlyTensorSpanPointerConstructorTests() Assert.Equal(94, spanInt[0, 1]); Assert.Equal(94, spanInt[1, 0]); Assert.Equal(94, spanInt[1, 1]); + ReadOnlyTensorSpan_TestEnumerator(spanInt); // Make sure strides can't be negative Assert.Throws(() => @@ -750,6 +812,7 @@ public static unsafe void ReadOnlyTensorSpanPointerConstructorTests() Assert.Equal(91, spanInt[0, 1]); Assert.Equal(-93, spanInt[1, 0]); Assert.Equal(-93, spanInt[1, 1]); + ReadOnlyTensorSpan_TestEnumerator(spanInt); // Make sure you can't overlap elements using strides Assert.Throws(() => @@ -764,11 +827,11 @@ public static unsafe void ReadOnlyTensorSpanPointerConstructorTests() } [Fact] - public static void ReadOnlyTensorSpanLargeDimensionsTests() + public static void ReadOnlyTensorSpan_LargeDimensionsTests() { int[] a = { 91, 92, -93, 94, 95, -96 }; int[] results = new int[6]; - ReadOnlyTensorSpan spanInt = a.AsTensorSpan(1, 1, 1, 1, 1, 6); + ReadOnlyTensorSpan spanInt = a.AsReadOnlyTensorSpan(1, 1, 1, 1, 1, 6); Assert.Equal(6, spanInt.Rank); Assert.Equal(6, spanInt.Lengths.Length); @@ -793,10 +856,11 @@ public static void ReadOnlyTensorSpanLargeDimensionsTests() Assert.Equal(-96, spanInt[0, 0, 0, 0, 0, 5]); spanInt.FlattenTo(results); Assert.Equal(a, results); + ReadOnlyTensorSpan_TestEnumerator(spanInt); a = [91, 92, -93, 94, 95, -96, -91, -92, 93, -94, -95, 96]; results = new int[12]; - spanInt = a.AsTensorSpan(1, 2, 2, 1, 1, 3); + spanInt = a.AsReadOnlyTensorSpan(1, 2, 2, 1, 1, 3); Assert.Equal(6, spanInt.Lengths.Length); Assert.Equal(1, spanInt.Lengths[0]); Assert.Equal(2, spanInt.Lengths[1]); @@ -825,14 +889,15 @@ public static void ReadOnlyTensorSpanLargeDimensionsTests() Assert.Equal(96, spanInt[0, 1, 1, 0, 0, 2]); spanInt.FlattenTo(results); Assert.Equal(a, results); + ReadOnlyTensorSpan_TestEnumerator(spanInt); } [Fact] - public static void IntArrayAsReadOnlyTensorSpan() + public static void ReadOnlyTensorSpan_IntArrayAs() { int[] a = { 91, 92, -93, 94 }; int[] results = new int[4]; - ReadOnlyTensorSpan spanInt = a.AsTensorSpan(4); + ReadOnlyTensorSpan spanInt = a.AsReadOnlyTensorSpan(4); Assert.Equal(1, spanInt.Rank); Assert.Equal(1, spanInt.Lengths.Length); @@ -845,12 +910,13 @@ public static void IntArrayAsReadOnlyTensorSpan() Assert.Equal(94, spanInt[3]); spanInt.FlattenTo(results); Assert.Equal(a, results); + ReadOnlyTensorSpan_TestEnumerator(spanInt); a[0] = 91; a[1] = 92; a[2] = -93; a[3] = 94; - spanInt = a.AsTensorSpan(2, 2); + spanInt = a.AsReadOnlyTensorSpan(2, 2); spanInt.FlattenTo(results); Assert.Equal(a, results); Assert.Equal(2, spanInt.Rank); @@ -864,14 +930,15 @@ public static void IntArrayAsReadOnlyTensorSpan() Assert.Equal(92, spanInt[0, 1]); Assert.Equal(-93, spanInt[1, 0]); Assert.Equal(94, spanInt[1, 1]); + ReadOnlyTensorSpan_TestEnumerator(spanInt); } [Fact] - public static void ReadOnlyTensorSpanCopyTest() + public static void ReadOnlyTensorSpan_CopyTest() { int[] leftData = [1, 2, 3, 4, 5, 6, 7, 8, 9]; int[] rightData = new int[9]; - ReadOnlyTensorSpan leftSpan = leftData.AsTensorSpan(3, 3); + ReadOnlyTensorSpan leftSpan = leftData.AsReadOnlyTensorSpan(3, 3); TensorSpan rightSpan = rightData.AsTensorSpan(3, 3); leftSpan.CopyTo(rightSpan); var leftEnum = leftSpan.GetEnumerator(); @@ -890,15 +957,17 @@ public static void ReadOnlyTensorSpanCopyTest() { leftData = [1, 2, 3, 4, 5, 6, 7, 8, 9]; rightData = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; - TensorSpan leftSpan = leftData.AsTensorSpan(9); + ReadOnlyTensorSpan leftSpan = leftData.AsReadOnlyTensorSpan(9); TensorSpan tensor = rightData.AsTensorSpan(rightData.Length); + ReadOnlyTensorSpan_TestEnumerator(leftSpan); leftSpan.CopyTo(tensor); } ); + ReadOnlyTensorSpan_TestEnumerator(leftSpan); leftData = [.. Enumerable.Range(0, 27)]; rightData = [.. Enumerable.Range(0, 27)]; - leftSpan = leftData.AsTensorSpan(3, 3, 3); + leftSpan = leftData.AsReadOnlyTensorSpan(3, 3, 3); rightSpan = rightData.AsTensorSpan(3, 3, 3); leftSpan.CopyTo(rightSpan); @@ -909,18 +978,20 @@ public static void ReadOnlyTensorSpanCopyTest() Assert.Throws(() => { - var l = leftData.AsTensorSpan(3, 3, 3); + ReadOnlyTensorSpan l = leftData.AsReadOnlyTensorSpan(3, 3, 3); + ReadOnlyTensorSpan_TestEnumerator(l); var r = new TensorSpan(); l.CopyTo(r); }); + ReadOnlyTensorSpan_TestEnumerator(leftSpan); } [Fact] - public static void ReadOnlyTensorSpanTryCopyTest() + public static void ReadOnlyTensorSpan_TryCopyTest() { int[] leftData = [1, 2, 3, 4, 5, 6, 7, 8, 9]; int[] rightData = new int[9]; - ReadOnlyTensorSpan leftSpan = leftData.AsTensorSpan(3, 3); + ReadOnlyTensorSpan leftSpan = leftData.AsReadOnlyTensorSpan(3, 3); TensorSpan rightSpan = rightData.AsTensorSpan(3, 3); var success = leftSpan.TryCopyTo(rightSpan); Assert.True(success); @@ -934,17 +1005,19 @@ public static void ReadOnlyTensorSpanTryCopyTest() //Make sure its a copy rightSpan[0, 0] = 100; Assert.NotEqual(leftSpan[0, 0], rightSpan[0, 0]); + ReadOnlyTensorSpan_TestEnumerator(leftSpan); leftData = [1, 2, 3, 4, 5, 6, 7, 8, 9]; rightData = new int[15]; - leftSpan = leftData.AsTensorSpan(9); + leftSpan = leftData.AsReadOnlyTensorSpan(9); rightSpan = rightData.AsTensorSpan(15); success = leftSpan.TryCopyTo(rightSpan); Assert.False(success); + ReadOnlyTensorSpan_TestEnumerator(leftSpan); leftData = [.. Enumerable.Range(0, 27)]; rightData = [.. Enumerable.Range(0, 27)]; - leftSpan = leftData.AsTensorSpan(3, 3, 3); + leftSpan = leftData.AsReadOnlyTensorSpan(3, 3, 3); rightSpan = rightData.AsTensorSpan(3, 3, 3); success = leftSpan.TryCopyTo(rightSpan); Assert.True(success); @@ -953,72 +1026,88 @@ public static void ReadOnlyTensorSpanTryCopyTest() { Assert.Equal(leftEnum.Current, rightEnum.Current); } + ReadOnlyTensorSpan_TestEnumerator(leftSpan); - var l = leftData.AsTensorSpan(3, 3, 3); + ReadOnlyTensorSpan l = leftData.AsReadOnlyTensorSpan(3, 3, 3); var r = new TensorSpan(); success = l.TryCopyTo(r); Assert.False(success); + ReadOnlyTensorSpan_TestEnumerator(l); success = new ReadOnlyTensorSpan(new double[1]).TryCopyTo(Array.Empty()); Assert.False(success); + ReadOnlyTensorSpan_TestEnumerator(new ReadOnlyTensorSpan(new double[1])); } [Fact] - public static void ReadOnlyTensorSpanSliceTest() + public static void ReadOnlyTensorSpan_SliceTest() { - // Make sure slicing an empty TensorSpan works - TensorSpan emptyTensorSpan = new TensorSpan(Array.Empty()).Slice(new NRange[] { .. }); + // Make sure slicing an empty ReadOnlyTensorSpan works + ReadOnlyTensorSpan emptyTensorSpan = new ReadOnlyTensorSpan(Array.Empty()).Slice(new NRange[] { .. }); Assert.Equal([0], emptyTensorSpan.Lengths); Assert.Equal(1, emptyTensorSpan.Rank); Assert.Equal(0, emptyTensorSpan.FlattenedLength); + ReadOnlyTensorSpan_TestEnumerator(emptyTensorSpan); - // Make sure slicing a multi-dimensional empty TensorSpan works + // Make sure slicing a multi-dimensional empty ReadOnlyTensorSpan works int[,] empty2dArray = new int[2, 0]; - emptyTensorSpan = new TensorSpan(empty2dArray); - TensorSpan slicedEmptyTensorSpan = emptyTensorSpan.Slice(new NRange[] { .., .. }); + emptyTensorSpan = new ReadOnlyTensorSpan(empty2dArray); + ReadOnlyTensorSpan slicedEmptyTensorSpan = emptyTensorSpan.Slice(new NRange[] { .., .. }); Assert.Equal([2, 0], slicedEmptyTensorSpan.Lengths); Assert.Equal(2, slicedEmptyTensorSpan.Rank); Assert.Equal(0, slicedEmptyTensorSpan.FlattenedLength); + ReadOnlyTensorSpan_TestEnumerator(emptyTensorSpan); + ReadOnlyTensorSpan_TestEnumerator(slicedEmptyTensorSpan); slicedEmptyTensorSpan = emptyTensorSpan.Slice(new NRange[] { 0..1, .. }); Assert.Equal([1, 0], slicedEmptyTensorSpan.Lengths); Assert.Equal(2, slicedEmptyTensorSpan.Rank); Assert.Equal(0, slicedEmptyTensorSpan.FlattenedLength); + ReadOnlyTensorSpan_TestEnumerator(slicedEmptyTensorSpan); - // Make sure slicing a multi-dimensional empty TensorSpan works + // Make sure slicing a multi-dimensional empty ReadOnlyTensorSpan works int[,,,] empty4dArray = new int[2, 5, 1, 0]; - emptyTensorSpan = new TensorSpan(empty4dArray); + emptyTensorSpan = new ReadOnlyTensorSpan(empty4dArray); slicedEmptyTensorSpan = emptyTensorSpan.Slice(new NRange[] { .., .., .., .. }); Assert.Equal([2, 5, 1, 0], slicedEmptyTensorSpan.Lengths); Assert.Equal(4, slicedEmptyTensorSpan.Rank); Assert.Equal(0, slicedEmptyTensorSpan.FlattenedLength); + ReadOnlyTensorSpan_TestEnumerator(emptyTensorSpan); + ReadOnlyTensorSpan_TestEnumerator(slicedEmptyTensorSpan); - emptyTensorSpan = new TensorSpan(empty4dArray); + emptyTensorSpan = new ReadOnlyTensorSpan(empty4dArray); slicedEmptyTensorSpan = emptyTensorSpan.Slice(new NRange[] { 0..1, .., .., .. }); Assert.Equal([1, 5, 1, 0], slicedEmptyTensorSpan.Lengths); Assert.Equal(4, slicedEmptyTensorSpan.Rank); Assert.Equal(0, slicedEmptyTensorSpan.FlattenedLength); + ReadOnlyTensorSpan_TestEnumerator(emptyTensorSpan); + ReadOnlyTensorSpan_TestEnumerator(slicedEmptyTensorSpan); - emptyTensorSpan = new TensorSpan(empty4dArray); + emptyTensorSpan = new ReadOnlyTensorSpan(empty4dArray); slicedEmptyTensorSpan = emptyTensorSpan.Slice(new NRange[] { 0..1, 2..3, .., .. }); Assert.Equal([1, 1, 1, 0], slicedEmptyTensorSpan.Lengths); Assert.Equal(4, slicedEmptyTensorSpan.Rank); Assert.Equal(0, slicedEmptyTensorSpan.FlattenedLength); + ReadOnlyTensorSpan_TestEnumerator(emptyTensorSpan); + ReadOnlyTensorSpan_TestEnumerator(slicedEmptyTensorSpan); empty4dArray = new int[2, 0, 1, 5]; - emptyTensorSpan = new TensorSpan(empty4dArray); + emptyTensorSpan = new ReadOnlyTensorSpan(empty4dArray); slicedEmptyTensorSpan = emptyTensorSpan.Slice(new NRange[] { .., .., .., .. }); Assert.Equal([2, 0, 1, 5], slicedEmptyTensorSpan.Lengths); Assert.Equal(4, slicedEmptyTensorSpan.Rank); Assert.Equal(0, slicedEmptyTensorSpan.FlattenedLength); + ReadOnlyTensorSpan_TestEnumerator(emptyTensorSpan); + ReadOnlyTensorSpan_TestEnumerator(slicedEmptyTensorSpan); int[] a = [1, 2, 3, 4, 5, 6, 7, 8, 9]; int[] results = new int[9]; - ReadOnlyTensorSpan spanInt = a.AsTensorSpan(3, 3); + ReadOnlyTensorSpan spanInt = a.AsReadOnlyTensorSpan(3, 3); - Assert.Throws(() => a.AsTensorSpan(2, 3).Slice(0..1)); - Assert.Throws(() => a.AsTensorSpan(2, 3).Slice(1..2)); - Assert.Throws(() => a.AsTensorSpan(2, 3).Slice(0..1, 5..6)); + Assert.Throws(() => a.AsReadOnlyTensorSpan(2, 3).Slice(0..1)); + Assert.Throws(() => a.AsReadOnlyTensorSpan(2, 3).Slice(1..2)); + Assert.Throws(() => a.AsReadOnlyTensorSpan(2, 3).Slice(0..1, 5..6)); + ReadOnlyTensorSpan_TestEnumerator(spanInt); var sp = spanInt.Slice(1..3, 1..3); Assert.Equal(5, sp[0, 0]); @@ -1035,6 +1124,7 @@ public static void ReadOnlyTensorSpanSliceTest() { Assert.Equal(slice[index++], enumerator.Current); } + ReadOnlyTensorSpan_TestEnumerator(sp); sp = spanInt.Slice(0..3, 0..3); Assert.Equal(1, sp[0, 0]); @@ -1055,10 +1145,11 @@ public static void ReadOnlyTensorSpanSliceTest() { Assert.Equal(a[index++], enumerator.Current); } + ReadOnlyTensorSpan_TestEnumerator(sp); sp = spanInt.Slice(0..1, 0..1); Assert.Equal(1, sp[0, 0]); - Assert.Throws(() => a.AsTensorSpan(3, 3).Slice(0..1, 0..1)[0, 1]); + Assert.Throws(() => a.AsReadOnlyTensorSpan(3, 3).Slice(0..1, 0..1)[0, 1]); slice = [1]; results = new int[1]; sp.FlattenTo(results); @@ -1069,6 +1160,7 @@ public static void ReadOnlyTensorSpanSliceTest() { Assert.Equal(slice[index++], enumerator.Current); } + ReadOnlyTensorSpan_TestEnumerator(sp); sp = spanInt.Slice(0..2, 0..2); Assert.Equal(1, sp[0, 0]); @@ -1085,9 +1177,10 @@ public static void ReadOnlyTensorSpanSliceTest() { Assert.Equal(slice[index++], enumerator.Current); } + ReadOnlyTensorSpan_TestEnumerator(sp); int[] numbers = [.. Enumerable.Range(0, 27)]; - spanInt = numbers.AsTensorSpan(3, 3, 3); + spanInt = numbers.AsReadOnlyTensorSpan(3, 3, 3); sp = spanInt.Slice(1..2, 1..2, 1..2); Assert.Equal(13, sp[0, 0, 0]); slice = [13]; @@ -1100,6 +1193,8 @@ public static void ReadOnlyTensorSpanSliceTest() { Assert.Equal(slice[index++], enumerator.Current); } + ReadOnlyTensorSpan_TestEnumerator(spanInt); + ReadOnlyTensorSpan_TestEnumerator(sp); sp = spanInt.Slice(1..3, 1..3, 1..3); Assert.Equal(13, sp[0, 0, 0]); @@ -1120,9 +1215,10 @@ public static void ReadOnlyTensorSpanSliceTest() { Assert.Equal(slice[index++], enumerator.Current); } + ReadOnlyTensorSpan_TestEnumerator(sp); numbers = [.. Enumerable.Range(0, 16)]; - spanInt = numbers.AsTensorSpan(2, 2, 2, 2); + spanInt = numbers.AsReadOnlyTensorSpan(2, 2, 2, 2); sp = spanInt.Slice(1..2, 0..2, 1..2, 0..2); Assert.Equal(10, sp[0, 0, 0, 0]); Assert.Equal(11, sp[0, 0, 0, 1]); @@ -1138,27 +1234,119 @@ public static void ReadOnlyTensorSpanSliceTest() { Assert.Equal(slice[index++], enumerator.Current); } + ReadOnlyTensorSpan_TestEnumerator(spanInt); + ReadOnlyTensorSpan_TestEnumerator(sp); } [Fact] - public static void LongArrayAsReadOnlyTensorSpan() + public static void ReadOnlyTensorSpan_LongArrayAs() { long[] b = { 91, -92, 93, 94, -95 }; - ReadOnlyTensorSpan spanLong = b.AsTensorSpan(5); + ReadOnlyTensorSpan spanLong = b.AsReadOnlyTensorSpan(5); Assert.Equal(91, spanLong[0]); Assert.Equal(-92, spanLong[1]); Assert.Equal(93, spanLong[2]); Assert.Equal(94, spanLong[3]); Assert.Equal(-95, spanLong[4]); + ReadOnlyTensorSpan_TestEnumerator(spanLong); } [Fact] - public static void NullArrayAsReadOnlyTensorSpan() + public static void ReadOnlyTensorSpan_NullArrayAs() { int[] a = null; - ReadOnlyTensorSpan span = a.AsTensorSpan(); + ReadOnlyTensorSpan span = a.AsReadOnlyTensorSpan(); ReadOnlyTensorSpan d = default; Assert.True(span == d); + ReadOnlyTensorSpan_TestEnumerator(span); + ReadOnlyTensorSpan_TestEnumerator(d); + } + + private static void ReadOnlyTensorSpan_TestEnumerator(ReadOnlyTensorSpan span) + where T: INumberBase + { + Span curIndexes = new nint[span.Rank]; + if (span.Rank > 0) + curIndexes[span.Rank - 1] = -1; + var enumerator = span.GetEnumerator(); + for (var i = 0; i < span.FlattenedLength; i++) + { + TensorSpanHelpers_AdjustIndexes(span.Rank - 1, 1, curIndexes, span.Lengths); + Assert.True(enumerator.MoveNext()); + ref readonly var current = ref enumerator.Current; + + Assert.Equal(span[curIndexes], current); + Unsafe.AsRef(in span[curIndexes])++; + Assert.Equal(span[curIndexes], current); + Unsafe.AsRef(in span[curIndexes])--; + Assert.Equal(span[curIndexes], current); + } + Assert.False(enumerator.MoveNext()); + + TestGI(span.GetEnumerator(), span); + TestI(span.GetEnumerator(), span); + + static void TestGI(TEnumerator enumerator, ReadOnlyTensorSpan span) + where TEnumerator : IEnumerator, allows ref struct + { + Test(enumerator, span); + _ = enumerator.MoveNext(); + enumerator.Reset(); + Test(enumerator, span); + + static void Test(TEnumerator enumerator, ReadOnlyTensorSpan span) + { + Span curIndexes = new nint[span.Rank]; + if (span.Rank > 0) + curIndexes[span.Rank - 1] = -1; + for (var i = 0; i < span.FlattenedLength; i++) + { + TensorSpanHelpers_AdjustIndexes(span.Rank - 1, 1, curIndexes, span.Lengths); + Assert.True(enumerator.MoveNext()); + Assert.Equal(span[curIndexes], enumerator.Current); + enumerator.Dispose(); + Assert.Equal(span[curIndexes], enumerator.Current); + } + Assert.False(enumerator.MoveNext()); + enumerator.Dispose(); + Assert.False(enumerator.MoveNext()); + } + } + + static void TestI(TEnumerator enumerator, ReadOnlyTensorSpan span) + where TEnumerator : IEnumerator, allows ref struct + { + Test(enumerator, span); + _ = enumerator.MoveNext(); + enumerator.Reset(); + Test(enumerator, span); + + static void Test(TEnumerator enumerator, ReadOnlyTensorSpan span) + { + Span curIndexes = new nint[span.Rank]; + if (span.Rank > 0) + curIndexes[span.Rank - 1] = -1; + for (var i = 0; i < span.FlattenedLength; i++) + { + TensorSpanHelpers_AdjustIndexes(span.Rank - 1, 1, curIndexes, span.Lengths); + Assert.True(enumerator.MoveNext()); + Assert.Equal(span[curIndexes], enumerator.Current); + } + Assert.False(enumerator.MoveNext()); + } + } + } + + private static void TensorSpanHelpers_AdjustIndexes(int curIndex, nint addend, Span curIndexes, scoped ReadOnlySpan length) + { + if (addend <= 0 || curIndex < 0 || length[curIndex] <= 0) + return; + curIndexes[curIndex] += addend; + + (nint Quotient, nint Remainder) result = Math.DivRem(curIndexes[curIndex], length[curIndex]); + + TensorSpanHelpers_AdjustIndexes(curIndex - 1, result.Quotient, curIndexes, length); + curIndexes[curIndex] = result.Remainder; } } } diff --git a/src/libraries/System.Numerics.Tensors/tests/TensorSpanTests.cs b/src/libraries/System.Numerics.Tensors/tests/TensorSpanTests.cs index b57472302f98a..356073c525398 100644 --- a/src/libraries/System.Numerics.Tensors/tests/TensorSpanTests.cs +++ b/src/libraries/System.Numerics.Tensors/tests/TensorSpanTests.cs @@ -2,9 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Buffers; +using System.Collections; using System.Collections.Generic; using System.Linq; -using System.Reflection; using System.Runtime.InteropServices; using Xunit; @@ -127,6 +127,9 @@ public void TensorExtensionsSpanInSpanOut(TensorPrimitivesSpanInSpanO Assert.Equal(expectedOutput[i], span[i]); Assert.Equal(expectedOutput[i], destSpan[i]); } + TensorSpan_TestEnumerator(x); + TensorSpan_TestEnumerator(destination); + TensorSpan_TestEnumerator(tensorResults); // Now test if the source is sliced to be smaller then the destination that the destination is also sliced // to the correct size. @@ -136,6 +139,7 @@ public void TensorExtensionsSpanInSpanOut(TensorPrimitivesSpanInSpanO TIn[] sliceData = new TIn[sliceFlattenedLength]; x.FlattenTo(sliceData); expectedOutput = new TOut[sliceFlattenedLength]; + TensorSpan_TestEnumerator(x); if (TensorHelpers.IsContiguousAndDense(x)) { @@ -171,6 +175,8 @@ public void TensorExtensionsSpanInSpanOut(TensorPrimitivesSpanInSpanO Assert.Equal(expectedOutput[i], destEnum.Current); Assert.Equal(expectedOutput[i], tensorResultsEnum.Current); } + TensorSpan_TestEnumerator(destination); + TensorSpan_TestEnumerator(tensorResults); // Now test if the source and destination are sliced (so neither is continuous) it works correctly. destination = destination.Slice(sliceLengths); @@ -190,6 +196,7 @@ public void TensorExtensionsSpanInSpanOut(TensorPrimitivesSpanInSpanO } } + TensorSpan_TestEnumerator(destination); tensorResults = tensorOperation(x, destination); @@ -208,6 +215,8 @@ public void TensorExtensionsSpanInSpanOut(TensorPrimitivesSpanInSpanO Assert.Equal(expectedOutput[i], destEnum.Current); Assert.Equal(expectedOutput[i], tensorResultsEnum.Current); } + TensorSpan_TestEnumerator(destination); + TensorSpan_TestEnumerator(tensorResults); }); } @@ -323,6 +332,10 @@ public void TensorExtensionsTwoSpanInSpanOut(TensorPrimitivesTwoSpanInSpanOut TensorSpan destination = Tensor.Create(destData, tensorLengths, []); tensorPrimitivesOperation((ReadOnlySpan)data1, data2, expectedOutput); TensorSpan results = tensorOperation(x, y, destination); + TensorSpan_TestEnumerator(x); + TensorSpan_TestEnumerator(y); + TensorSpan_TestEnumerator(destination); + TensorSpan_TestEnumerator(results); Assert.Equal(tensorLengths, results.Lengths); nint[] startingIndex = new nint[tensorLengths.Length]; @@ -341,7 +354,12 @@ public void TensorExtensionsTwoSpanInSpanOut(TensorPrimitivesTwoSpanInSpanOut nint[] tempLengths = tensorLengths.Select(i => i + 1).ToArray(); T[] tempDestData = new T[CalculateTotalLength(tempLengths)]; destination = Tensor.Create(tempDestData, tempLengths, []); + TensorSpan_TestEnumerator(destination); results = tensorOperation(x, y, destination); + TensorSpan_TestEnumerator(x); + TensorSpan_TestEnumerator(y); + TensorSpan_TestEnumerator(destination); + TensorSpan_TestEnumerator(results); // Since the slice was internal the result lengths will be the extra large size. Assert.Equal(tempLengths, results.Lengths); @@ -371,6 +389,7 @@ public void TensorExtensionsTwoSpanInSpanOut(TensorPrimitivesTwoSpanInSpanOut destination = destination.Slice(tensorLengths); x.Slice(sliceLengths).BroadcastTo(x); x.FlattenTo(data1); + TensorSpan_TestEnumerator(x); if (TensorHelpers.IsContiguousAndDense(x.Slice(sliceLengths)) && TensorHelpers.IsContiguousAndDense(y)) { @@ -386,6 +405,10 @@ public void TensorExtensionsTwoSpanInSpanOut(TensorPrimitivesTwoSpanInSpanOut } results = tensorOperation(x.Slice(sliceLengths), y, destination); + TensorSpan_TestEnumerator(x.Slice(sliceLengths)); + TensorSpan_TestEnumerator(y); + TensorSpan_TestEnumerator(destination); + TensorSpan_TestEnumerator(results); // results lengths will still be the original tensorLength Assert.Equal(tensorLengths, results.Lengths); @@ -407,6 +430,7 @@ public void TensorExtensionsTwoSpanInSpanOut(TensorPrimitivesTwoSpanInSpanOut // Now test if the second source is sliced to be smaller than the first (but is broadcast compatible) that broadcasting happens). y.Slice(sliceLengths).BroadcastTo(y); y.FlattenTo(data2); + TensorSpan_TestEnumerator(y); if (TensorHelpers.IsContiguousAndDense(x) && TensorHelpers.IsContiguousAndDense(y.Slice(sliceLengths))) { @@ -422,6 +446,10 @@ public void TensorExtensionsTwoSpanInSpanOut(TensorPrimitivesTwoSpanInSpanOut } results = tensorOperation(x, y.Slice(sliceLengths), destination); + TensorSpan_TestEnumerator(x); + TensorSpan_TestEnumerator(y.Slice(sliceLengths)); + TensorSpan_TestEnumerator(destination); + TensorSpan_TestEnumerator(results); // results lengths will still be the original tensorLength Assert.Equal(tensorLengths, results.Lengths); @@ -462,6 +490,10 @@ public void TensorExtensionsTwoSpanInSpanOut(TensorPrimitivesTwoSpanInSpanOut } results = tensorOperation(x.Slice(sliceLengths), y.Slice(sliceLengths), destination); + TensorSpan_TestEnumerator(x.Slice(sliceLengths)); + TensorSpan_TestEnumerator(y.Slice(sliceLengths)); + TensorSpan_TestEnumerator(destination); + TensorSpan_TestEnumerator(results); Assert.Equal(tensorLengths, results.Lengths); @@ -513,11 +545,15 @@ public void TensorExtensionsTwoSpanInFloatOut(TensorPrimitivesTwoSpanInTOut new NRange(0, i)).ToArray(); TensorSpan broadcastX = Tensor.Create(broadcastData1, tensorLength, []); + TensorSpan_TestEnumerator(broadcastX); x.Slice(sliceLengths).BroadcastTo(broadcastX); + TensorSpan_TestEnumerator(broadcastX); TensorSpan.Enumerator enumerator = broadcastX.GetEnumerator(); bool cont = enumerator.MoveNext(); int i = 0; @@ -535,7 +571,9 @@ public void TensorExtensionsTwoSpanInFloatOut(TensorPrimitivesTwoSpanInTOut broadcastY = Tensor.Create(broadcastData2, tensorLength, []); + TensorSpan_TestEnumerator(broadcastY); y.Slice(sliceLengths).BroadcastTo(broadcastY); + TensorSpan_TestEnumerator(broadcastY); enumerator = broadcastY.GetEnumerator(); cont = enumerator.MoveNext(); @@ -556,7 +594,7 @@ public void TensorExtensionsTwoSpanInFloatOut(TensorPrimitivesTwoSpanInTOut(() => @@ -564,6 +602,9 @@ public static unsafe void TensorSpanSetSliceTests() var ab = new TensorSpan(array: [0d, 1, 2, 3, 0d, 1, 2, 3]); // [0, 1, 2, 3] var b = ab.Reshape(lengths: new IntPtr[] { 2, 2, 2 }); // [[0, 1], [2, 3]] var c = b.Slice(ranges: new NRange[] { 1.., 1..2, ..1 }); // [[0], [2]] + TensorSpan_TestEnumerator(ab); + TensorSpan_TestEnumerator(b); + TensorSpan_TestEnumerator(c); c.Reshape(lengths: new IntPtr[] { 1, 2, 1 }); }); @@ -572,18 +613,21 @@ public static unsafe void TensorSpanSetSliceTests() { var ar = new double[1]; var a = new TensorSpan(ar.AsSpan()[..1], new IntPtr[] { 2 }, new IntPtr[] { 0 }); + TensorSpan_TestEnumerator(a); a.SetSlice(new TensorSpan(new double[] { 1, 3 }), new NRange[] { ..2 }); }); // Make sure that slice range and the values are the same length var ar = new double[4]; var a = new TensorSpan(ar, new IntPtr[] { 2, 2 }, default); + TensorSpan_TestEnumerator(a); a.SetSlice(new TensorSpan(new double[] { 1, 3 }), new NRange[] { ..1, .. }); Assert.Equal(1, a[0, 0]); Assert.Equal(3, a[0, 1]); Assert.Equal(0, a[1, 0]); Assert.Equal(0, a[1, 1]); + TensorSpan_TestEnumerator(a); // Make sure we can use a stride of 0. a.SetSlice(new TensorSpan(new double[] { -1 }, [2], [0]), new NRange[] { 1.., .. }); @@ -591,6 +635,7 @@ public static unsafe void TensorSpanSetSliceTests() Assert.Equal(3, a[0, 1]); Assert.Equal(-1, a[1, 0]); Assert.Equal(-1, a[1, 1]); + TensorSpan_TestEnumerator(a); // Make sure we can use a multi dimensional span with multiple 0 strides a.SetSlice(new TensorSpan(new double[] { -10 }, [2, 2], [0, 0])); @@ -598,6 +643,7 @@ public static unsafe void TensorSpanSetSliceTests() Assert.Equal(-10, a[0, 1]); Assert.Equal(-10, a[1, 0]); Assert.Equal(-10, a[1, 1]); + TensorSpan_TestEnumerator(a); // Make sure if the slice is broadcastable to the correct size you don't need to set a size for SetSlice a.SetSlice(new TensorSpan(new double[] { -20 }, [1], [0])); @@ -605,12 +651,14 @@ public static unsafe void TensorSpanSetSliceTests() Assert.Equal(-20, a[0, 1]); Assert.Equal(-20, a[1, 0]); Assert.Equal(-20, a[1, 1]); + TensorSpan_TestEnumerator(a); //Assert.Throws } [Fact] - public static void TensorSpanSystemArrayConstructorTests() + public static void TensorSpan_SystemArrayConstructorTests() + { // When using System.Array constructor make sure the type of the array matches T[] Assert.Throws(() => new TensorSpan(array: new[] { 1 })); @@ -628,6 +676,7 @@ public static void TensorSpanSystemArrayConstructorTests() Assert.Equal(92, spanInt[0, 1]); Assert.Equal(-93, spanInt[0, 2]); Assert.Equal(94, spanInt[0, 3]); + TensorSpan_TestEnumerator(spanInt); // Make sure null works // Should be a tensor with 0 elements and Rank 0 and no strides or lengths @@ -636,6 +685,7 @@ public static void TensorSpanSystemArrayConstructorTests() Assert.Equal(0, spanInt.Rank); Assert.Equal(0, spanInt.Lengths.Length); Assert.Equal(0, spanInt.Strides.Length); + TensorSpan_TestEnumerator(spanInt); // Make sure empty array works // Should be a Tensor with 0 elements but Rank 2 with dimension 0 length 0 @@ -647,9 +697,11 @@ public static void TensorSpanSystemArrayConstructorTests() Assert.Equal(0, spanInt.FlattenedLength); Assert.Equal(0, spanInt.Strides[0]); Assert.Equal(0, spanInt.Strides[1]); + TensorSpan_TestEnumerator(spanInt); // Make sure it still throws on index 0, 0 Assert.Throws(() => { var spanInt = new TensorSpan(b); + TensorSpan_TestEnumerator(spanInt); var x = spanInt[0, 0]; }); @@ -662,6 +714,7 @@ public static void TensorSpanSystemArrayConstructorTests() Assert.Equal(92, spanInt[0, 1]); Assert.Equal(-93, spanInt[1, 0]); Assert.Equal(94, spanInt[1, 1]); + TensorSpan_TestEnumerator(spanInt); // Make sure can use only some of the array spanInt = new TensorSpan(a, (int[])[0, 0], [1, 2], default); @@ -670,27 +723,32 @@ public static void TensorSpanSystemArrayConstructorTests() Assert.Equal(2, spanInt.Lengths[1]); Assert.Equal(91, spanInt[0, 0]); Assert.Equal(92, spanInt[0, 1]); + TensorSpan_TestEnumerator(spanInt); Assert.Throws(() => { var spanInt = new TensorSpan(a, (int[])[0, 0], [1, 2], default); + TensorSpan_TestEnumerator(spanInt); var x = spanInt[1, 1]; }); Assert.Throws(() => { var spanInt = new TensorSpan(a, (int[])[0, 0], [1, 2], default); + TensorSpan_TestEnumerator(spanInt); var x = spanInt[0, -1]; }); Assert.Throws(() => { var spanInt = new TensorSpan(a, (int[])[0, 0], [1, 2], default); + TensorSpan_TestEnumerator(spanInt); var x = spanInt[-1, 0]; }); Assert.Throws(() => { var spanInt = new TensorSpan(a, (int[])[0, 0], [1, 2], default); + TensorSpan_TestEnumerator(spanInt); var x = spanInt[1, 0]; }); @@ -701,6 +759,7 @@ public static void TensorSpanSystemArrayConstructorTests() Assert.Equal(2, spanInt.Lengths[1]); Assert.Equal(92, spanInt[0, 0]); Assert.Equal(-93, spanInt[0, 1]); + TensorSpan_TestEnumerator(spanInt); // Make sure Index offset works correctly spanInt = new TensorSpan(a, (int[])[0, 2], [1, 2], default); @@ -709,6 +768,7 @@ public static void TensorSpanSystemArrayConstructorTests() Assert.Equal(2, spanInt.Lengths[1]); Assert.Equal(-93, spanInt[0, 0]); Assert.Equal(94, spanInt[0, 1]); + TensorSpan_TestEnumerator(spanInt); // Make sure 2D array works with strides of all 0 and initial offset to loop over last element again spanInt = new TensorSpan(a, (int[])[0, 3], [2, 2], [0, 0]); @@ -719,6 +779,7 @@ public static void TensorSpanSystemArrayConstructorTests() Assert.Equal(94, spanInt[0, 1]); Assert.Equal(94, spanInt[1, 0]); Assert.Equal(94, spanInt[1, 1]); + TensorSpan_TestEnumerator(spanInt); // Make sure we catch that there aren't enough elements in the array for the lengths Assert.Throws(() => @@ -735,6 +796,7 @@ public static void TensorSpanSystemArrayConstructorTests() Assert.Equal(92, spanInt[0, 1]); Assert.Equal(-93, spanInt[1, 0]); Assert.Equal(94, spanInt[1, 1]); + TensorSpan_TestEnumerator(spanInt); // Make sure 2D array works with stride of 0 to loop over first 2 elements again spanInt = new TensorSpan(a, (int[])[0, 0], [2, 2], [0, 1]); @@ -745,6 +807,7 @@ public static void TensorSpanSystemArrayConstructorTests() Assert.Equal(92, spanInt[0, 1]); Assert.Equal(91, spanInt[1, 0]); Assert.Equal(92, spanInt[1, 1]); + TensorSpan_TestEnumerator(spanInt); // Make sure 2D array works with stride of 0 and initial offset to loop over last 2 elements again spanInt = new TensorSpan(a, (int[])[0, 2], [2, 2], [0, 1]); @@ -755,6 +818,7 @@ public static void TensorSpanSystemArrayConstructorTests() Assert.Equal(94, spanInt[0, 1]); Assert.Equal(-93, spanInt[1, 0]); Assert.Equal(94, spanInt[1, 1]); + TensorSpan_TestEnumerator(spanInt); // Make sure strides can't be negative Assert.Throws(() => @@ -785,6 +849,7 @@ public static void TensorSpanSystemArrayConstructorTests() Assert.Equal(91, spanInt[0, 1]); Assert.Equal(-93, spanInt[1, 0]); Assert.Equal(-93, spanInt[1, 1]); + TensorSpan_TestEnumerator(spanInt); // Make sure you can't overlap elements using strides Assert.Throws(() => @@ -801,6 +866,7 @@ public static void TensorSpanSystemArrayConstructorTests() Assert.Equal(94, spanInt[0, 1]); Assert.Equal(94, spanInt[1, 0]); Assert.Equal(94, spanInt[1, 1]); + TensorSpan_TestEnumerator(spanInt); //Make sure it works with NIndex spanInt = new TensorSpan(a, (NIndex[])[1, 1], [2, 2], [0, 0]); @@ -811,6 +877,7 @@ public static void TensorSpanSystemArrayConstructorTests() Assert.Equal(94, spanInt[0, 1]); Assert.Equal(94, spanInt[1, 0]); Assert.Equal(94, spanInt[1, 1]); + TensorSpan_TestEnumerator(spanInt); //Make sure it works with NIndex spanInt = new TensorSpan(a, (NIndex[])[^1, ^1], [2, 2], [0, 0]); @@ -821,10 +888,11 @@ public static void TensorSpanSystemArrayConstructorTests() Assert.Equal(94, spanInt[0, 1]); Assert.Equal(94, spanInt[1, 0]); Assert.Equal(94, spanInt[1, 1]); + TensorSpan_TestEnumerator(spanInt); } [Fact] - public static void TensorSpanArrayConstructorTests() + public static void TensorSpan_ArrayConstructorTests() { // Make sure exception is thrown if lengths and strides would let you go past the end of the array Assert.Throws(() => new TensorSpan(new double[0], lengths: new IntPtr[] { 2 }, strides: new IntPtr[] { 1 })); @@ -840,6 +908,7 @@ public static void TensorSpanArrayConstructorTests() Assert.Equal(92, spanInt[1]); Assert.Equal(-93, spanInt[2]); Assert.Equal(94, spanInt[3]); + TensorSpan_TestEnumerator(spanInt); // Make sure null works // Should be a tensor with 0 elements and Rank 0 and no strides or lengths @@ -847,6 +916,7 @@ public static void TensorSpanArrayConstructorTests() Assert.Equal(0, spanInt.Rank); Assert.Equal(0, spanInt.Lengths.Length); Assert.Equal(0, spanInt.Strides.Length); + TensorSpan_TestEnumerator(spanInt); // Make sure empty array works // Should be a Tensor with 0 elements but Rank 1 with dimension 0 length 0 @@ -856,9 +926,11 @@ public static void TensorSpanArrayConstructorTests() Assert.Equal(0, spanInt.Lengths[0]); Assert.Equal(0, spanInt.FlattenedLength); Assert.Equal(0, spanInt.Strides[0]); + TensorSpan_TestEnumerator(spanInt); // Make sure it still throws on index 0 Assert.Throws(() => { var spanInt = new TensorSpan(b); + TensorSpan_TestEnumerator(spanInt); var x = spanInt[0]; }); @@ -869,6 +941,7 @@ public static void TensorSpanArrayConstructorTests() Assert.Equal(0, spanInt.Lengths[0]); Assert.Equal(0, spanInt.FlattenedLength); Assert.Equal(0, spanInt.Strides[0]); + TensorSpan_TestEnumerator(spanInt); // Make sure 2D array works spanInt = new TensorSpan(a, new Index(0), [2, 2], default); @@ -879,6 +952,7 @@ public static void TensorSpanArrayConstructorTests() Assert.Equal(92, spanInt[0, 1]); Assert.Equal(-93, spanInt[1, 0]); Assert.Equal(94, spanInt[1, 1]); + TensorSpan_TestEnumerator(spanInt); // Make sure can use only some of the array spanInt = new TensorSpan(a, new Index(0), [1, 2], default); @@ -887,13 +961,16 @@ public static void TensorSpanArrayConstructorTests() Assert.Equal(2, spanInt.Lengths[1]); Assert.Equal(91, spanInt[0, 0]); Assert.Equal(92, spanInt[0, 1]); + TensorSpan_TestEnumerator(spanInt); Assert.Throws(() => { var spanInt = new TensorSpan(a, new Index(0), [1, 2], default); + TensorSpan_TestEnumerator(spanInt); var x = spanInt[1, 1]; }); Assert.Throws(() => { var spanInt = new TensorSpan(a, new Index(0), [1, 2], default); + TensorSpan_TestEnumerator(spanInt); var x = spanInt[1, 0]; }); @@ -904,6 +981,7 @@ public static void TensorSpanArrayConstructorTests() Assert.Equal(2, spanInt.Lengths[1]); Assert.Equal(92, spanInt[0, 0]); Assert.Equal(-93, spanInt[0, 1]); + TensorSpan_TestEnumerator(spanInt); // Make sure Index offset works correctly spanInt = new TensorSpan(a, new Index(2), [1, 2], default); @@ -912,6 +990,7 @@ public static void TensorSpanArrayConstructorTests() Assert.Equal(2, spanInt.Lengths[1]); Assert.Equal(-93, spanInt[0, 0]); Assert.Equal(94, spanInt[0, 1]); + TensorSpan_TestEnumerator(spanInt); // Make sure we catch that there aren't enough elements in the array for the lengths Assert.Throws(() => { @@ -927,6 +1006,7 @@ public static void TensorSpanArrayConstructorTests() Assert.Equal(92, spanInt[0, 1]); Assert.Equal(-93, spanInt[1, 0]); Assert.Equal(94, spanInt[1, 1]); + TensorSpan_TestEnumerator(spanInt); // Make sure 2D array works with stride of 0 to loop over first 2 elements again spanInt = new TensorSpan(a, new Index(0), [2, 2], [0, 1]); @@ -937,6 +1017,7 @@ public static void TensorSpanArrayConstructorTests() Assert.Equal(92, spanInt[0, 1]); Assert.Equal(91, spanInt[1, 0]); Assert.Equal(92, spanInt[1, 1]); + TensorSpan_TestEnumerator(spanInt); // Make sure 2D array works with stride of 0 and initial offset to loop over last 2 elements again spanInt = new TensorSpan(a, new Index(2), [2, 2], [0, 1]); @@ -947,6 +1028,7 @@ public static void TensorSpanArrayConstructorTests() Assert.Equal(94, spanInt[0, 1]); Assert.Equal(-93, spanInt[1, 0]); Assert.Equal(94, spanInt[1, 1]); + TensorSpan_TestEnumerator(spanInt); // Make sure 2D array works with strides of all 0 and initial offset to loop over last element again spanInt = new TensorSpan(a, new Index(3), [2, 2], [0, 0]); @@ -957,6 +1039,7 @@ public static void TensorSpanArrayConstructorTests() Assert.Equal(94, spanInt[0, 1]); Assert.Equal(94, spanInt[1, 0]); Assert.Equal(94, spanInt[1, 1]); + TensorSpan_TestEnumerator(spanInt); // Make sure strides can't be negative Assert.Throws(() => { @@ -983,6 +1066,7 @@ public static void TensorSpanArrayConstructorTests() Assert.Equal(91, spanInt[0, 1]); Assert.Equal(-93, spanInt[1, 0]); Assert.Equal(-93, spanInt[1, 1]); + TensorSpan_TestEnumerator(spanInt); // Make sure you can't overlap elements using strides Assert.Throws(() => { @@ -991,7 +1075,7 @@ public static void TensorSpanArrayConstructorTests() } [Fact] - public static void TensorSpanSpanConstructorTests() + public static void TensorSpan_SpanConstructorTests() { // Make sure basic T[] constructor works Span a = [91, 92, -93, 94]; @@ -1002,6 +1086,7 @@ public static void TensorSpanSpanConstructorTests() Assert.Equal(92, spanInt[1]); Assert.Equal(-93, spanInt[2]); Assert.Equal(94, spanInt[3]); + TensorSpan_TestEnumerator(spanInt); // Make sure empty span works // Should be a Tensor with 0 elements but Rank 1 with dimension 0 length 0 @@ -1011,10 +1096,12 @@ public static void TensorSpanSpanConstructorTests() Assert.Equal(0, spanInt.Lengths[0]); Assert.Equal(0, spanInt.FlattenedLength); Assert.Equal(0, spanInt.Strides[0]); + TensorSpan_TestEnumerator(spanInt); // Make sure it still throws on index 0 Assert.Throws(() => { Span b = []; var spanInt = new TensorSpan(b); + TensorSpan_TestEnumerator(spanInt); var x = spanInt[0]; }); @@ -1027,6 +1114,7 @@ public static void TensorSpanSpanConstructorTests() Assert.Equal(92, spanInt[0, 1]); Assert.Equal(-93, spanInt[1, 0]); Assert.Equal(94, spanInt[1, 1]); + TensorSpan_TestEnumerator(spanInt); // Make sure can use only some of the array spanInt = new TensorSpan(a, [1, 2], default); @@ -1035,15 +1123,18 @@ public static void TensorSpanSpanConstructorTests() Assert.Equal(2, spanInt.Lengths[1]); Assert.Equal(91, spanInt[0, 0]); Assert.Equal(92, spanInt[0, 1]); + TensorSpan_TestEnumerator(spanInt); Assert.Throws(() => { Span a = [91, 92, -93, 94]; var spanInt = new TensorSpan(a, [1, 2], default); + TensorSpan_TestEnumerator(spanInt); var x = spanInt[1, 1]; }); Assert.Throws(() => { Span a = [91, 92, -93, 94]; var spanInt = new TensorSpan(a, [1, 2], default); + TensorSpan_TestEnumerator(spanInt); var x = spanInt[1, 0]; }); @@ -1054,6 +1145,7 @@ public static void TensorSpanSpanConstructorTests() Assert.Equal(2, spanInt.Lengths[1]); Assert.Equal(92, spanInt[0, 0]); Assert.Equal(-93, spanInt[0, 1]); + TensorSpan_TestEnumerator(spanInt); // Make sure Index offset works correctly spanInt = new TensorSpan(a.Slice(2), [1, 2], default); @@ -1062,6 +1154,7 @@ public static void TensorSpanSpanConstructorTests() Assert.Equal(2, spanInt.Lengths[1]); Assert.Equal(-93, spanInt[0, 0]); Assert.Equal(94, spanInt[0, 1]); + TensorSpan_TestEnumerator(spanInt); // Make sure we catch that there aren't enough elements in the array for the lengths Assert.Throws(() => { @@ -1078,6 +1171,7 @@ public static void TensorSpanSpanConstructorTests() Assert.Equal(92, spanInt[0, 1]); Assert.Equal(-93, spanInt[1, 0]); Assert.Equal(94, spanInt[1, 1]); + TensorSpan_TestEnumerator(spanInt); // Make sure 2D array works with stride of 0 to loop over first 2 elements again spanInt = new TensorSpan(a, [2, 2], [0, 1]); @@ -1088,6 +1182,7 @@ public static void TensorSpanSpanConstructorTests() Assert.Equal(92, spanInt[0, 1]); Assert.Equal(91, spanInt[1, 0]); Assert.Equal(92, spanInt[1, 1]); + TensorSpan_TestEnumerator(spanInt); // Make sure 2D array works with stride of 0 and initial offset to loop over last 2 elements again spanInt = new TensorSpan(a.Slice(2), [2, 2], [0, 1]); @@ -1098,6 +1193,7 @@ public static void TensorSpanSpanConstructorTests() Assert.Equal(94, spanInt[0, 1]); Assert.Equal(-93, spanInt[1, 0]); Assert.Equal(94, spanInt[1, 1]); + TensorSpan_TestEnumerator(spanInt); // Make sure 2D array works with strides of all 0 and initial offset to loop over last element again spanInt = new TensorSpan(a.Slice(3), [2, 2], [0, 0]); @@ -1108,6 +1204,7 @@ public static void TensorSpanSpanConstructorTests() Assert.Equal(94, spanInt[0, 1]); Assert.Equal(94, spanInt[1, 0]); Assert.Equal(94, spanInt[1, 1]); + TensorSpan_TestEnumerator(spanInt); // Make sure strides can't be negative Assert.Throws(() => { @@ -1138,6 +1235,7 @@ public static void TensorSpanSpanConstructorTests() Assert.Equal(91, spanInt[0, 1]); Assert.Equal(-93, spanInt[1, 0]); Assert.Equal(-93, spanInt[1, 1]); + TensorSpan_TestEnumerator(spanInt); // Make sure you can't overlap elements using strides Assert.Throws(() => { @@ -1147,7 +1245,7 @@ public static void TensorSpanSpanConstructorTests() } [Fact] - public static unsafe void TensorSpanPointerConstructorTests() + public static unsafe void TensorSpan_PointerConstructorTests() { // Make sure basic T[] constructor works Span a = [91, 92, -93, 94]; @@ -1162,6 +1260,7 @@ public static unsafe void TensorSpanPointerConstructorTests() Assert.Equal(-93, spanInt[2]); Assert.Equal(94, spanInt[3]); } + TensorSpan_TestEnumerator(spanInt); // Make sure empty span works // Should be a Tensor with 0 elements but Rank 1 with dimension 0 length 0 @@ -1173,6 +1272,7 @@ public static unsafe void TensorSpanPointerConstructorTests() Assert.Equal(0, spanInt.Lengths[0]); Assert.Equal(0, spanInt.FlattenedLength); Assert.Equal(0, spanInt.Strides[0]); + TensorSpan_TestEnumerator(spanInt); // Make sure it still throws on index 0 Assert.Throws(() => { @@ -1180,6 +1280,7 @@ public static unsafe void TensorSpanPointerConstructorTests() fixed (int* p = b) { var spanInt = new TensorSpan(p, 0); + TensorSpan_TestEnumerator(spanInt); var x = spanInt[0]; } }); @@ -1196,6 +1297,7 @@ public static unsafe void TensorSpanPointerConstructorTests() Assert.Equal(92, spanInt[0, 1]); Assert.Equal(-93, spanInt[1, 0]); Assert.Equal(94, spanInt[1, 1]); + TensorSpan_TestEnumerator(spanInt); // Make sure can use only some of the array spanInt = new TensorSpan(p, 4, [1, 2], default); @@ -1204,12 +1306,14 @@ public static unsafe void TensorSpanPointerConstructorTests() Assert.Equal(2, spanInt.Lengths[1]); Assert.Equal(91, spanInt[0, 0]); Assert.Equal(92, spanInt[0, 1]); + TensorSpan_TestEnumerator(spanInt); Assert.Throws(() => { Span a = [91, 92, -93, 94]; fixed (int* p = a) { var spanInt = new TensorSpan(p, 4, [1, 2], default); + TensorSpan_TestEnumerator(spanInt); var x = spanInt[1, 1]; } }); @@ -1220,6 +1324,7 @@ public static unsafe void TensorSpanPointerConstructorTests() fixed (int* p = a) { var spanInt = new TensorSpan(p, 4, [1, 2], default); + TensorSpan_TestEnumerator(spanInt); var x = spanInt[1, 0]; } }); @@ -1231,6 +1336,7 @@ public static unsafe void TensorSpanPointerConstructorTests() Assert.Equal(2, spanInt.Lengths[1]); Assert.Equal(92, spanInt[0, 0]); Assert.Equal(-93, spanInt[0, 1]); + TensorSpan_TestEnumerator(spanInt); // Make sure Index offset works correctly spanInt = new TensorSpan(p + 2, 2, [1, 2], default); @@ -1239,6 +1345,7 @@ public static unsafe void TensorSpanPointerConstructorTests() Assert.Equal(2, spanInt.Lengths[1]); Assert.Equal(-93, spanInt[0, 0]); Assert.Equal(94, spanInt[0, 1]); + TensorSpan_TestEnumerator(spanInt); // Make sure we catch that there aren't enough elements in the array for the lengths Assert.Throws(() => @@ -1259,6 +1366,7 @@ public static unsafe void TensorSpanPointerConstructorTests() Assert.Equal(92, spanInt[0, 1]); Assert.Equal(-93, spanInt[1, 0]); Assert.Equal(94, spanInt[1, 1]); + TensorSpan_TestEnumerator(spanInt); // Make sure 2D array works with stride of 0 to loop over first 2 elements again spanInt = new TensorSpan(p, 4, [2, 2], [0, 1]); @@ -1269,6 +1377,7 @@ public static unsafe void TensorSpanPointerConstructorTests() Assert.Equal(92, spanInt[0, 1]); Assert.Equal(91, spanInt[1, 0]); Assert.Equal(92, spanInt[1, 1]); + TensorSpan_TestEnumerator(spanInt); // Make sure 2D array works with stride of 0 and initial offset to loop over last 2 elements again spanInt = new TensorSpan(p + 2, 2, [2, 2], [0, 1]); @@ -1279,6 +1388,7 @@ public static unsafe void TensorSpanPointerConstructorTests() Assert.Equal(94, spanInt[0, 1]); Assert.Equal(-93, spanInt[1, 0]); Assert.Equal(94, spanInt[1, 1]); + TensorSpan_TestEnumerator(spanInt); // Make sure 2D array works with strides of all 0 and initial offset to loop over last element again spanInt = new TensorSpan(p + 3, 1, [2, 2], [0, 0]); @@ -1289,6 +1399,7 @@ public static unsafe void TensorSpanPointerConstructorTests() Assert.Equal(94, spanInt[0, 1]); Assert.Equal(94, spanInt[1, 0]); Assert.Equal(94, spanInt[1, 1]); + TensorSpan_TestEnumerator(spanInt); // Make sure strides can't be negative Assert.Throws(() => @@ -1345,6 +1456,7 @@ public static unsafe void TensorSpanPointerConstructorTests() Assert.Equal(91, spanInt[0, 1]); Assert.Equal(-93, spanInt[1, 0]); Assert.Equal(-93, spanInt[1, 1]); + TensorSpan_TestEnumerator(spanInt); // Make sure you can't overlap elements using strides Assert.Throws(() => @@ -1359,7 +1471,7 @@ public static unsafe void TensorSpanPointerConstructorTests() } [Fact] - public static void TensorSpanLargeDimensionsTests() + public static void TensorSpan_LargeDimensionsTests() { int[] a = { 91, 92, -93, 94, 95, -96 }; int[] results = new int[6]; @@ -1388,6 +1500,7 @@ public static void TensorSpanLargeDimensionsTests() Assert.Equal(-96, spanInt[0, 0, 0, 0, 0, 5]); spanInt.FlattenTo(results); Assert.Equal(a, results); + TensorSpan_TestEnumerator(spanInt); a = [91, 92, -93, 94, 95, -96, -91, -92, 93, -94, -95, 96]; results = new int[12]; @@ -1420,10 +1533,11 @@ public static void TensorSpanLargeDimensionsTests() Assert.Equal(96, spanInt[0, 1, 1, 0, 0, 2]); spanInt.FlattenTo(results); Assert.Equal(a, results); + TensorSpan_TestEnumerator(spanInt); } [Fact] - public static void IntArrayAsTensorSpan() + public static void TensorSpan_IntArrayAs() { int[] a = { 91, 92, -93, 94 }; int[] results = new int[4]; @@ -1440,15 +1554,16 @@ public static void IntArrayAsTensorSpan() Assert.Equal(94, spanInt[3]); spanInt.FlattenTo(results); Assert.Equal(a, results); + spanInt[0] = 100; spanInt[1] = 101; spanInt[2] = -102; spanInt[3] = 103; - Assert.Equal(100, spanInt[0]); Assert.Equal(101, spanInt[1]); Assert.Equal(-102, spanInt[2]); Assert.Equal(103, spanInt[3]); + TensorSpan_TestEnumerator(spanInt); a[0] = 91; a[1] = 92; @@ -1473,15 +1588,15 @@ public static void IntArrayAsTensorSpan() spanInt[0, 1] = 101; spanInt[1, 0] = -102; spanInt[1, 1] = 103; - Assert.Equal(100, spanInt[0, 0]); Assert.Equal(101, spanInt[0, 1]); Assert.Equal(-102, spanInt[1, 0]); Assert.Equal(103, spanInt[1, 1]); + TensorSpan_TestEnumerator(spanInt); } [Fact] - public static void TensorSpanFillTest() + public static void TensorSpan_FillTest() { int[] a = [1, 2, 3, 4, 5, 6, 7, 8, 9]; TensorSpan spanInt = a.AsTensorSpan(3, 3); @@ -1505,6 +1620,7 @@ public static void TensorSpanFillTest() { Assert.Equal(int.MaxValue, enumerator.Current); } + TensorSpan_TestEnumerator(spanInt); a = [1, 2, 3, 4, 5, 6, 7, 8, 9]; spanInt = a.AsTensorSpan(9); @@ -1514,6 +1630,7 @@ public static void TensorSpanFillTest() { Assert.Equal(-1, enumerator.Current); } + TensorSpan_TestEnumerator(spanInt); a = [.. Enumerable.Range(0, 27)]; spanInt = a.AsTensorSpan(3,3,3); @@ -1523,6 +1640,7 @@ public static void TensorSpanFillTest() { Assert.Equal(-1, enumerator.Current); } + TensorSpan_TestEnumerator(spanInt); a = [.. Enumerable.Range(0, 12)]; spanInt = a.AsTensorSpan(3, 2, 2); @@ -1532,6 +1650,7 @@ public static void TensorSpanFillTest() { Assert.Equal(-1, enumerator.Current); } + TensorSpan_TestEnumerator(spanInt); a = [.. Enumerable.Range(0, 16)]; spanInt = a.AsTensorSpan(2,2,2,2); @@ -1541,6 +1660,7 @@ public static void TensorSpanFillTest() { Assert.Equal(-1, enumerator.Current); } + TensorSpan_TestEnumerator(spanInt); a = [.. Enumerable.Range(0, 24)]; spanInt = a.AsTensorSpan(3, 2, 2, 2); @@ -1550,10 +1670,11 @@ public static void TensorSpanFillTest() { Assert.Equal(-1, enumerator.Current); } + TensorSpan_TestEnumerator(spanInt); } [Fact] - public static void TensorSpanClearTest() + public static void TensorSpan_ClearTest() { int[] a = [1, 2, 3, 4, 5, 6, 7, 8, 9]; TensorSpan spanInt = a.AsTensorSpan(3, 3); @@ -1583,6 +1704,8 @@ public static void TensorSpanClearTest() { Assert.Equal(0, enumerator.Current); } + TensorSpan_TestEnumerator(spanInt); + TensorSpan_TestEnumerator(slice); a = [1, 2, 3, 4, 5, 6, 7, 8, 9]; spanInt = a.AsTensorSpan(9); @@ -1608,6 +1731,8 @@ public static void TensorSpanClearTest() { Assert.Equal(0, enumerator.Current); } + TensorSpan_TestEnumerator(spanInt); + TensorSpan_TestEnumerator(slice); a = [.. Enumerable.Range(0, 27)]; spanInt = a.AsTensorSpan(3, 3, 3); @@ -1617,6 +1742,7 @@ public static void TensorSpanClearTest() { Assert.Equal(0, enumerator.Current); } + TensorSpan_TestEnumerator(spanInt); a = [.. Enumerable.Range(0, 12)]; spanInt = a.AsTensorSpan(3, 2, 2); @@ -1626,6 +1752,7 @@ public static void TensorSpanClearTest() { Assert.Equal(0, enumerator.Current); } + TensorSpan_TestEnumerator(spanInt); a = [.. Enumerable.Range(0, 16)]; spanInt = a.AsTensorSpan(2, 2, 2, 2); @@ -1635,6 +1762,7 @@ public static void TensorSpanClearTest() { Assert.Equal(0, enumerator.Current); } + TensorSpan_TestEnumerator(spanInt); a = [.. Enumerable.Range(0, 24)]; spanInt = a.AsTensorSpan(3, 2, 2, 2); @@ -1644,10 +1772,11 @@ public static void TensorSpanClearTest() { Assert.Equal(0, enumerator.Current); } + TensorSpan_TestEnumerator(spanInt); } [Fact] - public static void TensorSpanCopyTest() + public static void TensorSpan_CopyTest() { int[] leftData = [1, 2, 3, 4, 5, 6, 7, 8, 9]; int[] rightData = new int[9]; @@ -1672,9 +1801,13 @@ public static void TensorSpanCopyTest() rightData = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; TensorSpan leftSpan = leftData.AsTensorSpan(9); TensorSpan tensor = rightData.AsTensorSpan(rightData.Length); + TensorSpan_TestEnumerator(leftSpan); + TensorSpan_TestEnumerator(tensor); leftSpan.CopyTo(tensor); } ); + TensorSpan_TestEnumerator(leftSpan); + TensorSpan_TestEnumerator(rightSpan); leftData = [.. Enumerable.Range(0, 27)]; rightData = [.. Enumerable.Range(0, 27)]; @@ -1691,12 +1824,16 @@ public static void TensorSpanCopyTest() { var l = leftData.AsTensorSpan(3, 3, 3); var r = new TensorSpan(); + TensorSpan_TestEnumerator(l); + TensorSpan_TestEnumerator(r); l.CopyTo(r); }); + TensorSpan_TestEnumerator(leftSpan); + TensorSpan_TestEnumerator(rightSpan); } [Fact] - public static void TensorSpanTryCopyTest() + public static void TensorSpan_TryCopyTest() { int[] leftData = [1, 2, 3, 4, 5, 6, 7, 8, 9]; int[] rightData = new int[9]; @@ -1714,6 +1851,8 @@ public static void TensorSpanTryCopyTest() //Make sure its a copy leftSpan[0, 0] = 100; Assert.NotEqual(leftSpan[0, 0], rightSpan[0, 0]); + TensorSpan_TestEnumerator(leftSpan); + TensorSpan_TestEnumerator(rightSpan); leftData = [1, 2, 3, 4, 5, 6, 7, 8, 9]; rightData = new int[15]; @@ -1722,6 +1861,8 @@ public static void TensorSpanTryCopyTest() success = leftSpan.TryCopyTo(rightSpan); Assert.False(success); + TensorSpan_TestEnumerator(leftSpan); + TensorSpan_TestEnumerator(rightSpan); leftData = [.. Enumerable.Range(0, 27)]; rightData = [.. Enumerable.Range(0, 27)]; @@ -1734,24 +1875,29 @@ public static void TensorSpanTryCopyTest() { Assert.Equal(leftEnum.Current, rightEnum.Current); } + TensorSpan_TestEnumerator(leftSpan); + TensorSpan_TestEnumerator(rightSpan); var l = leftData.AsTensorSpan(3, 3, 3); var r = new TensorSpan(); success = l.TryCopyTo(r); Assert.False(success); + TensorSpan_TestEnumerator(l); + TensorSpan_TestEnumerator(r); success = new TensorSpan(new double[1]).TryCopyTo(Array.Empty()); Assert.False(success); } [Fact] - public static void TensorSpanSliceTest() + public static void TensorSpan_SliceTest() { // Make sure slicing an empty TensorSpan works TensorSpan emptyTensorSpan = new TensorSpan(Array.Empty()).Slice(new NRange[] { .. }); Assert.Equal([0], emptyTensorSpan.Lengths); Assert.Equal(1, emptyTensorSpan.Rank); Assert.Equal(0, emptyTensorSpan.FlattenedLength); + TensorSpan_TestEnumerator(emptyTensorSpan); // Make sure slicing a multi-dimensional empty TensorSpan works int[,] empty2dArray = new int[2, 0]; @@ -1760,11 +1906,14 @@ public static void TensorSpanSliceTest() Assert.Equal([2, 0], slicedEmptyTensorSpan.Lengths); Assert.Equal(2, slicedEmptyTensorSpan.Rank); Assert.Equal(0, slicedEmptyTensorSpan.FlattenedLength); + TensorSpan_TestEnumerator(emptyTensorSpan); + TensorSpan_TestEnumerator(slicedEmptyTensorSpan); slicedEmptyTensorSpan = emptyTensorSpan.Slice(new NRange[] { 0..1, .. }); Assert.Equal([1, 0], slicedEmptyTensorSpan.Lengths); Assert.Equal(2, slicedEmptyTensorSpan.Rank); Assert.Equal(0, slicedEmptyTensorSpan.FlattenedLength); + TensorSpan_TestEnumerator(slicedEmptyTensorSpan); // Make sure slicing a multi-dimensional empty TensorSpan works int[,,,] empty4dArray = new int[2, 5, 1, 0]; @@ -1773,18 +1922,24 @@ public static void TensorSpanSliceTest() Assert.Equal([2, 5, 1, 0], slicedEmptyTensorSpan.Lengths); Assert.Equal(4, slicedEmptyTensorSpan.Rank); Assert.Equal(0, slicedEmptyTensorSpan.FlattenedLength); + TensorSpan_TestEnumerator(emptyTensorSpan); + TensorSpan_TestEnumerator(slicedEmptyTensorSpan); emptyTensorSpan = new TensorSpan(empty4dArray); slicedEmptyTensorSpan = emptyTensorSpan.Slice(new NRange[] { 0..1, .., .., .. }); Assert.Equal([1, 5, 1, 0], slicedEmptyTensorSpan.Lengths); Assert.Equal(4, slicedEmptyTensorSpan.Rank); Assert.Equal(0, slicedEmptyTensorSpan.FlattenedLength); + TensorSpan_TestEnumerator(emptyTensorSpan); + TensorSpan_TestEnumerator(slicedEmptyTensorSpan); emptyTensorSpan = new TensorSpan(empty4dArray); slicedEmptyTensorSpan = emptyTensorSpan.Slice(new NRange[] { 0..1, 2..3, .., .. }); Assert.Equal([1, 1, 1, 0], slicedEmptyTensorSpan.Lengths); Assert.Equal(4, slicedEmptyTensorSpan.Rank); Assert.Equal(0, slicedEmptyTensorSpan.FlattenedLength); + TensorSpan_TestEnumerator(emptyTensorSpan); + TensorSpan_TestEnumerator(slicedEmptyTensorSpan); empty4dArray = new int[2, 0, 1, 5]; emptyTensorSpan = new TensorSpan(empty4dArray); @@ -1792,6 +1947,8 @@ public static void TensorSpanSliceTest() Assert.Equal([2, 0, 1, 5], slicedEmptyTensorSpan.Lengths); Assert.Equal(4, slicedEmptyTensorSpan.Rank); Assert.Equal(0, slicedEmptyTensorSpan.FlattenedLength); + TensorSpan_TestEnumerator(emptyTensorSpan); + TensorSpan_TestEnumerator(slicedEmptyTensorSpan); int[] a = [1, 2, 3, 4, 5, 6, 7, 8, 9]; int[] results = new int[9]; @@ -1800,6 +1957,7 @@ public static void TensorSpanSliceTest() Assert.Throws(() => a.AsTensorSpan(2, 3).Slice(0..1)); Assert.Throws(() => a.AsTensorSpan(2, 3).Slice(1..2)); Assert.Throws(() => a.AsTensorSpan(2, 3).Slice(0..1, 5..6)); + TensorSpan_TestEnumerator(spanInt); var sp = spanInt.Slice(1..3, 1..3); Assert.Equal(5, sp[0, 0]); @@ -1816,6 +1974,7 @@ public static void TensorSpanSliceTest() { Assert.Equal(slice[index++], enumerator.Current); } + TensorSpan_TestEnumerator(sp); sp = spanInt.Slice(0..3, 0..3); Assert.Equal(1, sp[0, 0]); @@ -1836,6 +1995,7 @@ public static void TensorSpanSliceTest() { Assert.Equal(a[index++], enumerator.Current); } + TensorSpan_TestEnumerator(sp); sp = spanInt.Slice(0..1, 0..1); Assert.Equal(1, sp[0, 0]); @@ -1850,6 +2010,7 @@ public static void TensorSpanSliceTest() { Assert.Equal(slice[index++], enumerator.Current); } + TensorSpan_TestEnumerator(sp); sp = spanInt.Slice(0..2, 0..2); Assert.Equal(1, sp[0, 0]); @@ -1866,6 +2027,7 @@ public static void TensorSpanSliceTest() { Assert.Equal(slice[index++], enumerator.Current); } + TensorSpan_TestEnumerator(sp); int[] numbers = [.. Enumerable.Range(0, 27)]; spanInt = numbers.AsTensorSpan(3, 3, 3); @@ -1881,6 +2043,8 @@ public static void TensorSpanSliceTest() { Assert.Equal(slice[index++], enumerator.Current); } + TensorSpan_TestEnumerator(spanInt); + TensorSpan_TestEnumerator(sp); sp = spanInt.Slice(1..3, 1..3, 1..3); Assert.Equal(13, sp[0, 0, 0]); @@ -1901,6 +2065,7 @@ public static void TensorSpanSliceTest() { Assert.Equal(slice[index++], enumerator.Current); } + TensorSpan_TestEnumerator(sp); numbers = [.. Enumerable.Range(0, 16)]; spanInt = numbers.AsTensorSpan(2, 2, 2, 2); @@ -1919,10 +2084,12 @@ public static void TensorSpanSliceTest() { Assert.Equal(slice[index++], enumerator.Current); } + TensorSpan_TestEnumerator(spanInt); + TensorSpan_TestEnumerator(sp); } [Fact] - public static void LongArrayAsTensorSpan() + public static void TensorSpan_LongArrayAs() { long[] b = { 91, -92, 93, 94, -95 }; TensorSpan spanLong = b.AsTensorSpan(5); @@ -1931,14 +2098,104 @@ public static void LongArrayAsTensorSpan() Assert.Equal(93, spanLong[2]); Assert.Equal(94, spanLong[3]); Assert.Equal(-95, spanLong[4]); + TensorSpan_TestEnumerator(spanLong); } [Fact] - public static void NullArrayAsTensorSpan() + public static void TensorSpan_NullArrayAs() { int[] a = null; TensorSpan span = a.AsTensorSpan(); Assert.True(span == default); + TensorSpan_TestEnumerator(span); + } + + private static void TensorSpan_TestEnumerator(TensorSpan span) + where T : INumberBase + { + Span curIndexes = new nint[span.Rank]; + if (span.Rank > 0) + curIndexes[span.Rank - 1] = -1; + var enumerator = span.GetEnumerator(); + for (var i = 0; i < span.FlattenedLength; i++) + { + TensorSpanHelpers_AdjustIndexes(span.Rank - 1, 1, curIndexes, span.Lengths); + Assert.True(enumerator.MoveNext()); + ref var current = ref enumerator.Current; + + var value = current; + Assert.Equal(span[curIndexes], current); + current++; + Assert.Equal(span[curIndexes], current); + current = value; + Assert.Equal(span[curIndexes], current); + } + Assert.False(enumerator.MoveNext()); + + TestGI(span.GetEnumerator(), span); + TestI(span.GetEnumerator(), span); + + static void TestGI(TEnumerator enumerator, TensorSpan span) + where TEnumerator : IEnumerator, allows ref struct + { + Test(enumerator, span); + _ = enumerator.MoveNext(); + enumerator.Reset(); + Test(enumerator, span); + + static void Test(TEnumerator enumerator, TensorSpan span) + { + Span curIndexes = new nint[span.Rank]; + if (span.Rank > 0) + curIndexes[span.Rank - 1] = -1; + for (var i = 0; i < span.FlattenedLength; i++) + { + TensorSpanHelpers_AdjustIndexes(span.Rank - 1, 1, curIndexes, span.Lengths); + Assert.True(enumerator.MoveNext()); + Assert.Equal(span[curIndexes], enumerator.Current); + enumerator.Dispose(); + Assert.Equal(span[curIndexes], enumerator.Current); + } + Assert.False(enumerator.MoveNext()); + enumerator.Dispose(); + Assert.False(enumerator.MoveNext()); + } + } + + static void TestI(TEnumerator enumerator, TensorSpan span) + where TEnumerator : IEnumerator, allows ref struct + { + Test(enumerator, span); + _ = enumerator.MoveNext(); + enumerator.Reset(); + Test(enumerator, span); + + static void Test(TEnumerator enumerator, TensorSpan span) + { + Span curIndexes = new nint[span.Rank]; + if (span.Rank > 0) + curIndexes[span.Rank - 1] = -1; + for (var i = 0; i < span.FlattenedLength; i++) + { + TensorSpanHelpers_AdjustIndexes(span.Rank - 1, 1, curIndexes, span.Lengths); + Assert.True(enumerator.MoveNext()); + Assert.Equal(span[curIndexes], enumerator.Current); + } + Assert.False(enumerator.MoveNext()); + } + } + } + + private static void TensorSpanHelpers_AdjustIndexes(int curIndex, nint addend, Span curIndexes, scoped ReadOnlySpan length) + { + if (addend <= 0 || curIndex < 0 || length[curIndex] <= 0) + return; + curIndexes[curIndex] += addend; + + (nint Quotient, nint Remainder) result = Math.DivRem(curIndexes[curIndex], length[curIndex]); + + TensorSpanHelpers_AdjustIndexes(curIndex - 1, result.Quotient, curIndexes, length); + curIndexes[curIndex] = result.Remainder; } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.cs b/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.cs index 6a2798b537939..5811b0000eb1d 100644 --- a/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.cs +++ b/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Buffers; +using System.Collections; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; @@ -4030,7 +4031,7 @@ private static bool TryWrite(Span destination, IForma /// Enables enumerating each split within a that has been divided using one or more separators. /// /// The type of items in the . - public ref struct SpanSplitEnumerator where T : IEquatable + public ref struct SpanSplitEnumerator : IEnumerator where T : IEquatable { /// The input span being split. private readonly ReadOnlySpan _span; @@ -4168,6 +4169,13 @@ public bool MoveNext() return true; } + + /// Gets the current element of the enumeration. + /// Returns a instance that indicates the bounds of the current element withing the source span. + object IEnumerator.Current => Current; + + void IEnumerator.Reset() => throw new NotSupportedException(); + void IDisposable.Dispose() { } } /// Indicates in which mode is operating, with regards to how it should interpret its state. diff --git a/src/libraries/System.Private.CoreLib/src/System/ReadOnlySpan.cs b/src/libraries/System.Private.CoreLib/src/System/ReadOnlySpan.cs index 1402f8d2a3e4e..aba4649d47e81 100644 --- a/src/libraries/System.Private.CoreLib/src/System/ReadOnlySpan.cs +++ b/src/libraries/System.Private.CoreLib/src/System/ReadOnlySpan.cs @@ -1,6 +1,8 @@ // 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; +using System.Collections.Generic; using System.Diagnostics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -229,7 +231,7 @@ public static ReadOnlySpan CastUp(ReadOnlySpan items) whe public Enumerator GetEnumerator() => new Enumerator(this); /// Enumerates the elements of a . - public ref struct Enumerator + public ref struct Enumerator : IEnumerator { /// The span being enumerated. private readonly ReadOnlySpan _span; @@ -265,6 +267,23 @@ public ref readonly T Current [MethodImpl(MethodImplOptions.AggressiveInlining)] get => ref _span[_index]; } + + /// Gets the element at the current position of the enumerator. + T IEnumerator.Current + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => Current; + } + + /// Gets the element at the current position of the enumerator. + object IEnumerator.Current + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => Current!; + } + + void IEnumerator.Reset() => _index = -1; + void IDisposable.Dispose() { } } /// diff --git a/src/libraries/System.Private.CoreLib/src/System/Span.cs b/src/libraries/System.Private.CoreLib/src/System/Span.cs index 20609fd10bcdb..16cd132dde4f2 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Span.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Span.cs @@ -1,6 +1,8 @@ // 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; +using System.Collections.Generic; using System.Diagnostics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -222,7 +224,7 @@ public static implicit operator Span(ArraySegment segment) => public Enumerator GetEnumerator() => new Enumerator(this); /// Enumerates the elements of a . - public ref struct Enumerator + public ref struct Enumerator : IEnumerator { /// The span being enumerated. private readonly Span _span; @@ -258,6 +260,23 @@ public ref T Current [MethodImpl(MethodImplOptions.AggressiveInlining)] get => ref _span[_index]; } + + /// Gets the element at the current position of the enumerator. + T IEnumerator.Current + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => Current; + } + + /// Gets the element at the current position of the enumerator. + object IEnumerator.Current + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => Current!; + } + + void IEnumerator.Reset() => _index = -1; + void IDisposable.Dispose() { } } /// diff --git a/src/libraries/System.Private.CoreLib/src/System/Text/SpanLineEnumerator.cs b/src/libraries/System.Private.CoreLib/src/System/Text/SpanLineEnumerator.cs index 3741d16eaa200..e10caf2efeac0 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Text/SpanLineEnumerator.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Text/SpanLineEnumerator.cs @@ -1,6 +1,9 @@ // 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; +using System.Collections.Generic; + namespace System.Text { /// @@ -9,7 +12,7 @@ namespace System.Text /// /// To get an instance of this type, use . /// - public ref struct SpanLineEnumerator + public ref struct SpanLineEnumerator : IEnumerator> { private ReadOnlySpan _remaining; private ReadOnlySpan _current; @@ -43,6 +46,7 @@ public bool MoveNext() { if (!_isEnumeratorActive) { + _current = default; return false; // EOF previously reached or enumerator was never initialized } @@ -74,5 +78,9 @@ public bool MoveNext() return true; } + + object IEnumerator.Current => throw new NotSupportedException(); + void IEnumerator.Reset() => throw new NotSupportedException(); + void IDisposable.Dispose() { } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Text/SpanRuneEnumerator.cs b/src/libraries/System.Private.CoreLib/src/System/Text/SpanRuneEnumerator.cs index f27b22e4cee95..0446649faf7c1 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Text/SpanRuneEnumerator.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Text/SpanRuneEnumerator.cs @@ -1,11 +1,15 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System; +using System.Collections; +using System.Collections.Generic; + namespace System.Text { // An enumerator for retrieving System.Text.Rune instances from a ROS. // Methods are pattern-matched by compiler to allow using foreach pattern. - public ref struct SpanRuneEnumerator + public ref struct SpanRuneEnumerator : IEnumerator { private ReadOnlySpan _remaining; private Rune _current; @@ -16,10 +20,23 @@ internal SpanRuneEnumerator(ReadOnlySpan buffer) _current = default; } + /// + /// Gets the rune at the current position of the enumerator. + /// public Rune Current => _current; + /// + /// Returns this instance as an enumerator. + /// public SpanRuneEnumerator GetEnumerator() => this; + /// + /// Advances the enumerator to the next rune of the span. + /// + /// + /// True if the enumerator successfully advanced to the next rune; false if + /// the enumerator has advanced past the end of the span. + /// public bool MoveNext() { if (_remaining.IsEmpty) @@ -46,5 +63,13 @@ public bool MoveNext() _remaining = _remaining.Slice(_current.Utf16SequenceLength); return true; } + + /// + /// Gets the rune at the current position of the enumerator. + /// + object IEnumerator.Current => Current; + + void IEnumerator.Reset() => throw new NotSupportedException(); + void IDisposable.Dispose() { } } } diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index af640b0339a44..474acebbab5f6 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -5093,12 +5093,16 @@ public void CopyTo(System.Span destination) { } public T[] ToArray() { throw null; } public override string ToString() { throw null; } public bool TryCopyTo(System.Span destination) { throw null; } - public ref partial struct Enumerator + public ref partial struct Enumerator : System.Collections.Generic.IEnumerator, System.Collections.IEnumerator, System.IDisposable { private object _dummy; private int _dummyPrimitive; public ref readonly T Current { get { throw null; } } public bool MoveNext() { throw null; } + T System.Collections.Generic.IEnumerator.Current { get { throw null; } } + object System.Collections.IEnumerator.Current { get { throw null; } } + void System.Collections.IEnumerator.Reset() { throw null; } + void System.IDisposable.Dispose() { throw null; } } } public partial class ResolveEventArgs : System.EventArgs @@ -5550,12 +5554,16 @@ public void Fill(T value) { } public T[] ToArray() { throw null; } public override string ToString() { throw null; } public bool TryCopyTo(System.Span destination) { throw null; } - public ref partial struct Enumerator + public ref partial struct Enumerator : System.Collections.Generic.IEnumerator, System.Collections.IEnumerator, System.IDisposable { private object _dummy; private int _dummyPrimitive; public ref T Current { get { throw null; } } public bool MoveNext() { throw null; } + T System.Collections.Generic.IEnumerator.Current { get { throw null; } } + object System.Collections.IEnumerator.Current { get { throw null; } } + void System.Collections.IEnumerator.Reset() { throw null; } + void System.IDisposable.Dispose() { throw null; } } } public sealed partial class StackOverflowException : System.SystemException diff --git a/src/libraries/System.Text.RegularExpressions/ref/System.Text.RegularExpressions.cs b/src/libraries/System.Text.RegularExpressions/ref/System.Text.RegularExpressions.cs index dd5f8af53bb5a..bb8537c5bd0a3 100644 --- a/src/libraries/System.Text.RegularExpressions/ref/System.Text.RegularExpressions.cs +++ b/src/libraries/System.Text.RegularExpressions/ref/System.Text.RegularExpressions.cs @@ -241,21 +241,27 @@ void System.Runtime.Serialization.ISerializable.GetObjectData(System.Runtime.Ser [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Never)] protected bool UseOptionR() { throw null; } protected internal static void ValidateMatchTimeout(System.TimeSpan matchTimeout) { } - public ref partial struct ValueMatchEnumerator + public ref partial struct ValueMatchEnumerator : System.Collections.Generic.IEnumerator, System.Collections.IEnumerator, System.IDisposable { private object _dummy; private int _dummyPrimitive; public readonly System.Text.RegularExpressions.ValueMatch Current { get { throw null; } } public readonly System.Text.RegularExpressions.Regex.ValueMatchEnumerator GetEnumerator() { throw null; } public bool MoveNext() { throw null; } + readonly object System.Collections.IEnumerator.Current { get { throw null; } } + void System.Collections.IEnumerator.Reset() { throw null; } + void System.IDisposable.Dispose() { throw null; } } - public ref partial struct ValueSplitEnumerator + public ref partial struct ValueSplitEnumerator : System.Collections.Generic.IEnumerator, System.Collections.IEnumerator, System.IDisposable { private object _dummy; private int _dummyPrimitive; public readonly System.Range Current { get { throw null; } } public readonly System.Text.RegularExpressions.Regex.ValueSplitEnumerator GetEnumerator() { throw null; } public bool MoveNext() { throw null; } + readonly object System.Collections.IEnumerator.Current { get { throw null; } } + void System.Collections.IEnumerator.Reset() { throw null; } + void System.IDisposable.Dispose() { throw null; } } } [System.ObsoleteAttribute("Regex.CompileToAssembly is obsolete and not supported. Use the GeneratedRegexAttribute with the regular expression source generator instead.", DiagnosticId = "SYSLIB0036", UrlFormat = "https://aka.ms/dotnet-warnings/{0}")] diff --git a/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/Regex.EnumerateMatches.cs b/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/Regex.EnumerateMatches.cs index 22f1f7bf35e8d..2ea913486cf7d 100644 --- a/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/Regex.EnumerateMatches.cs +++ b/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/Regex.EnumerateMatches.cs @@ -1,6 +1,8 @@ // 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; +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; namespace System.Text.RegularExpressions @@ -103,7 +105,7 @@ public ValueMatchEnumerator EnumerateMatches(ReadOnlySpan input, int start /// /// This type is a ref struct since it stores the input span as a field in order to be able to lazily iterate over it. /// - public ref struct ValueMatchEnumerator + public ref struct ValueMatchEnumerator : IEnumerator { private readonly Regex _regex; private readonly ReadOnlySpan _input; @@ -157,6 +159,10 @@ public bool MoveNext() /// /// Enumeration has either not started or has already finished. public readonly ValueMatch Current => _current; + + readonly object IEnumerator.Current => throw new NotSupportedException(); + void IEnumerator.Reset() => throw new NotSupportedException(); + void IDisposable.Dispose() { } } } } diff --git a/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/Regex.EnumerateSplits.cs b/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/Regex.EnumerateSplits.cs index e4dbc232118f8..391bd3d7bc338 100644 --- a/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/Regex.EnumerateSplits.cs +++ b/src/libraries/System.Text.RegularExpressions/src/System/Text/RegularExpressions/Regex.EnumerateSplits.cs @@ -1,6 +1,8 @@ // 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; +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; @@ -173,7 +175,7 @@ public ValueSplitEnumerator EnumerateSplits(ReadOnlySpan input, int count, /// Represents an enumerator containing the set of splits around successful matches found by iteratively applying a regular expression pattern to the input span. /// [StructLayout(LayoutKind.Auto)] - public ref struct ValueSplitEnumerator + public ref struct ValueSplitEnumerator : IEnumerator { private readonly Regex _regex; private readonly ReadOnlySpan _input; @@ -271,6 +273,15 @@ public bool MoveNext() /// /// Enumeration has either not started or has already finished. public readonly Range Current => _currentSplit; + + /// + /// Gets the element at the current position of the enumerator. + /// + /// Enumeration has either not started or has already finished. + readonly object IEnumerator.Current => Current; + + void IEnumerator.Reset() => throw new NotSupportedException(); + void IDisposable.Dispose() { } } } } diff --git a/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/Regex.EnumerateMatches.Tests.cs b/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/Regex.EnumerateMatches.Tests.cs index cac2753615e17..aeddd0e688b71 100644 --- a/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/Regex.EnumerateMatches.Tests.cs +++ b/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/Regex.EnumerateMatches.Tests.cs @@ -1,7 +1,9 @@ // 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; using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using Xunit; @@ -65,6 +67,8 @@ static void Test(string input, Regex r, string[] expectedMatches) } Assert.Equal(expectedMatches.Length, count); + + EnumerateMatches_ExpectedGI(r.EnumerateMatches(span), span, expectedMatches); } } @@ -93,6 +97,8 @@ static void Test(string input, Regex r, string[] expectedMatches) } Assert.Equal(expectedMatches.Length, count); + + EnumerateMatches_ExpectedGI(r.EnumerateMatches(span), span, expectedMatches); } } @@ -117,15 +123,25 @@ static void Test(string input, Regex r, string[] expectedMatches, int[] expected count++; } + + EnumerateMatches_ExpectedGI(r.EnumerateMatches(span), span, expectedMatches, expectedIndex); } } + + internal static void EnumerateMatches_ExpectedGI(TEnumerator enumerator, ReadOnlySpan span, + string[] expectedMatches, int[]? expectedIndex = default) + where TEnumerator : IEnumerator, allows ref struct + { + var expected = expectedMatches.Select((val, index) => new CaptureData(val, expectedIndex?[index] ?? -1, -1)); + RegexMultipleMatchTests.EnumerateMatches_ExpectedGI(enumerator, span, expected); + } } public partial class RegexMultipleMatchTests { [Theory] [MemberData(nameof(Matches_TestData))] - public async Task EnumerateMatches(RegexEngine engine, string pattern, string input, RegexOptions options, CaptureData[] expected) + public async Task EnumerateMatches_Tests(RegexEngine engine, string pattern, string input, RegexOptions options, CaptureData[] expected) { Test(input, expected, await RegexHelpers.GetRegexAsync(engine, pattern, options)); @@ -142,8 +158,53 @@ static void Test(string input, CaptureData[] expected, Regex regexAdvanced) } Assert.Equal(expected.Length, count); + + EnumerateMatches_ExpectedGI(regexAdvanced.EnumerateMatches(span), span, expected); } } + + internal static void EnumerateMatches_ExpectedGI(TEnumerator enumerator, ReadOnlySpan span, IEnumerable expected) + where TEnumerator : IEnumerator, allows ref struct + { + try { enumerator.Reset(); } catch (NotSupportedException) { }; + enumerator.Dispose(); + + foreach (var capture in expected) + { + Assert.True(enumerator.MoveNext()); + var match = enumerator.Current; + EnumerateMatches_CurrentI(enumerator); + if (capture.Index >= 0) + Assert.Equal(capture.Index, match.Index); + if (capture.Length >= 0) + Assert.Equal(capture.Length, match.Length); + if (capture.Value is not null) + Assert.Equal(capture.Value, span.Slice(match.Index, match.Length).ToString()); + + try { enumerator.Reset(); } catch (NotSupportedException) { }; + enumerator.Dispose(); + match = enumerator.Current; + EnumerateMatches_CurrentI(enumerator); + if (capture.Index >= 0) + Assert.Equal(capture.Index, match.Index); + if (capture.Length >= 0) + Assert.Equal(capture.Length, match.Length); + if (capture.Value is not null) + Assert.Equal(capture.Value, span.Slice(match.Index, match.Length).ToString()); + } + + Assert.False(enumerator.MoveNext()); + + try { enumerator.Reset(); } catch (NotSupportedException) { }; + enumerator.Dispose(); + Assert.False(enumerator.MoveNext()); + } + + internal static void EnumerateMatches_CurrentI(TEnumerator enumerator) + where TEnumerator : IEnumerator, allows ref struct + { + try { _ = enumerator.Current; } catch (NotSupportedException) { }; + } } public partial class RegexMatchTests @@ -163,6 +224,9 @@ static void Test(string input, int expectedCount, Regex r) } Assert.Equal(expectedCount, count); + + RegexMultipleMatchTests.EnumerateMatches_ExpectedGI(r.EnumerateMatches(input), input, + Enumerable.Range(0, expectedCount).Select(_ => new CaptureData(default, -1, -1))); } } } @@ -184,6 +248,9 @@ static void Test(RegexEngine engine, string pattern, string input, int startat, } Assert.Equal(expectedCount, count); + RegexMultipleMatchTests.EnumerateMatches_ExpectedGI(r.EnumerateMatches(input, startat), input, + Enumerable.Range(0, expectedCount).Select(_ => new CaptureData(default, -1, -1))); + bool isDefaultStartAt = startat == ((options & RegexOptions.RightToLeft) != 0 ? input.Length : 0); if (!isDefaultStartAt) { @@ -198,6 +265,9 @@ static void Test(RegexEngine engine, string pattern, string input, int startat, count++; } Assert.Equal(expectedCount, count); + + RegexMultipleMatchTests.EnumerateMatches_ExpectedGI(Regex.EnumerateMatches(input, pattern), input, + Enumerable.Range(0, expectedCount).Select(_ => new CaptureData(default, -1, -1))); } switch (engine) @@ -212,6 +282,8 @@ static void Test(RegexEngine engine, string pattern, string input, int startat, count++; } Assert.Equal(expectedCount, count); + RegexMultipleMatchTests.EnumerateMatches_ExpectedGI(Regex.EnumerateMatches(input, pattern, options | engineOptions), input, + Enumerable.Range(0, expectedCount).Select(_ => new CaptureData(default, -1, -1))); count = 0; foreach (ValueMatch _ in Regex.EnumerateMatches(input, pattern, options | engineOptions, Regex.InfiniteMatchTimeout)) @@ -219,6 +291,8 @@ static void Test(RegexEngine engine, string pattern, string input, int startat, count++; } Assert.Equal(expectedCount, count); + RegexMultipleMatchTests.EnumerateMatches_ExpectedGI(Regex.EnumerateMatches(input, pattern, options | engineOptions, Regex.InfiniteMatchTimeout), input, + Enumerable.Range(0, expectedCount).Select(_ => new CaptureData(default, -1, -1))); break; } } diff --git a/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/Regex.EnumerateSplits.Tests.cs b/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/Regex.EnumerateSplits.Tests.cs index 47a8a9aacc14e..bced922d98458 100644 --- a/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/Regex.EnumerateSplits.Tests.cs +++ b/src/libraries/System.Text.RegularExpressions/tests/FunctionalTests/Regex.EnumerateSplits.Tests.cs @@ -1,6 +1,7 @@ // 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; using System.Collections.Generic; using System.Threading.Tasks; using Xunit; @@ -28,15 +29,24 @@ public async Task Split(RegexEngine engine, string pattern, string input, RegexO if (isDefaultStart && isDefaultCount) { Validate(options, input, r.Split(input), r.EnumerateSplits(input)); + ValidateGI(options, input, r.Split(input), r.EnumerateSplits(input)); + ValidateI(options, input, r.Split(input), r.EnumerateSplits(input)); + Validate(options, input, Regex.Split(input, pattern, options | RegexHelpers.OptionsFromEngine(engine)), Regex.EnumerateSplits(input, pattern, options | RegexHelpers.OptionsFromEngine(engine))); + ValidateGI(options, input, Regex.Split(input, pattern, options | RegexHelpers.OptionsFromEngine(engine)), Regex.EnumerateSplits(input, pattern, options | RegexHelpers.OptionsFromEngine(engine))); + ValidateI(options, input, Regex.Split(input, pattern, options | RegexHelpers.OptionsFromEngine(engine)), Regex.EnumerateSplits(input, pattern, options | RegexHelpers.OptionsFromEngine(engine))); } if (isDefaultStart) { Validate(options, input, r.Split(input, count), r.EnumerateSplits(input, count)); + ValidateGI(options, input, r.Split(input, count), r.EnumerateSplits(input, count)); + ValidateI(options, input, r.Split(input, count), r.EnumerateSplits(input, count)); } Validate(options, input, r.Split(input, count, start), r.EnumerateSplits(input, count, start)); + ValidateGI(options, input, r.Split(input, count, start), r.EnumerateSplits(input, count, start)); + ValidateI(options, input, r.Split(input, count, start), r.EnumerateSplits(input, count, start)); static void Validate(RegexOptions options, string input, string[] expected, Regex.ValueSplitEnumerator enumerator) { @@ -53,6 +63,59 @@ static void Validate(RegexOptions options, string input, string[] expected, Rege Assert.Equal(expected, actual.ToArray()); } + + static void ValidateGI(RegexOptions options, string input, string[] expected, TEnumerator enumerator) + where TEnumerator : IEnumerator, allows ref struct + { + Assert.Equal(default, enumerator.Current); + try { enumerator.Reset(); } catch (NotSupportedException) { }; + enumerator.Dispose(); + Assert.Equal(default, enumerator.Current); + + var actual = new List(); + while (enumerator.MoveNext()) + { + try { enumerator.Reset(); } catch (NotSupportedException) { }; + enumerator.Dispose(); + actual.Add(input[enumerator.Current]); + } + + try { enumerator.Reset(); } catch (NotSupportedException) { }; + enumerator.Dispose(); + Assert.False(enumerator.MoveNext()); + + if ((options & RegexOptions.RightToLeft) != 0) + { + actual.Reverse(); + } + + Assert.Equal(expected, actual.ToArray()); + } + + static void ValidateI(RegexOptions options, string input, string[] expected, TEnumerator enumerator) + where TEnumerator : IEnumerator, allows ref struct + { + Assert.Equal(default(Range), enumerator.Current); + try { enumerator.Reset(); } catch (NotSupportedException) { }; + Assert.Equal(default(Range), enumerator.Current); + + var actual = new List(); + while (enumerator.MoveNext()) + { + try { enumerator.Reset(); } catch (NotSupportedException) { }; + actual.Add(input[(Range)enumerator.Current]); + } + + try { enumerator.Reset(); } catch (NotSupportedException) { }; + Assert.False(enumerator.MoveNext()); + + if ((options & RegexOptions.RightToLeft) != 0) + { + actual.Reverse(); + } + + Assert.Equal(expected, actual.ToArray()); + } } [Fact]