From ac589fb267c3354aacb9b2b23affa1c1bd9ec2e5 Mon Sep 17 00:00:00 2001 From: Alexander Radchenko Date: Sun, 11 Aug 2024 02:30:58 +0700 Subject: [PATCH 1/7] SpanSplitEnumerator, ReadOnlySpanEnumerator, SpanEnumerator --- .../System.Memory/ref/System.Memory.cs | 9 ++- .../tests/ReadOnlySpan/GetEnumerator.cs | 73 ++++++++++++++++++ .../tests/ReadOnlySpan/Split.T.cs | 75 ++++++++++++++++++- .../System.Memory/tests/Span/GetEnumerator.cs | 73 ++++++++++++++++++ .../src/System/MemoryExtensions.cs | 9 ++- .../src/System/ReadOnlySpan.cs | 20 ++++- .../System.Private.CoreLib/src/System/Span.cs | 22 +++++- .../src/System/Text/SpanLineEnumerator.cs | 10 ++- .../System.Runtime/ref/System.Runtime.cs | 12 ++- 9 files changed, 292 insertions(+), 11 deletions(-) diff --git a/src/libraries/System.Memory/ref/System.Memory.cs b/src/libraries/System.Memory/ref/System.Memory.cs index bc4e19712a310..6c74ea267f2cc 100644 --- a/src/libraries/System.Memory/ref/System.Memory.cs +++ b/src/libraries/System.Memory/ref/System.Memory.cs @@ -5,6 +5,8 @@ // ------------------------------------------------------------------------------ #if !BUILDING_CORELIB_REFERENCE +using System.Collections.Generic; + namespace System { public readonly partial struct SequencePosition : System.IEquatable @@ -415,13 +417,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.IDisposable.Dispose() { throw null; } + void System.Collections.IEnumerator.Reset() { throw null; } } [System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)] [System.Runtime.CompilerServices.InterpolatedStringHandlerAttribute] @@ -735,7 +740,7 @@ public static void Write(System.Span destination, in T value) where T : } namespace System.Text { - public ref partial struct SpanLineEnumerator + public ref partial struct SpanLineEnumerator : IEnumerator> { private object _dummy; private int _dummyPrimitive; 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..192565f7ede87 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 DefaultSpanSplitEnumeratorBehavior() { 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] @@ -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/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.Private.CoreLib/src/System/MemoryExtensions.cs b/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.cs index 938639f7eee54..a30b011e05839 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; @@ -4286,7 +4287,7 @@ private static bool TryWrite(Span destination, IForma /// /// Enables enumerating each split within a that has been divided using one or more separators. /// - public ref struct SpanSplitEnumerator where T : IEquatable + public ref struct SpanSplitEnumerator : IEnumerator where T : IEquatable { /// The input span being split. private readonly ReadOnlySpan _span; @@ -4316,7 +4317,7 @@ private static bool TryWrite(Span destination, IForma /// Gets the current element of the enumeration. /// Returns a instance that indicates the bounds of the current element withing the source span. - public Range Current => new Range(_startCurrent, _endCurrent); + public readonly Range Current => new Range(_startCurrent, _endCurrent); /// Initializes the enumerator for . internal SpanSplitEnumerator(ReadOnlySpan span, SearchValues searchValues) @@ -4424,6 +4425,10 @@ public bool MoveNext() return true; } + + object IEnumerator.Current => Current; + void IDisposable.Dispose() { } + void IEnumerator.Reset() => throw new NotSupportedException(); } /// 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..c8f2021a06c34 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,22 @@ public ref readonly T Current [MethodImpl(MethodImplOptions.AggressiveInlining)] get => ref _span[_index]; } + + T IEnumerator.Current + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => Current; + } + + object IEnumerator.Current + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => Current!; + } + + void IDisposable.Dispose() { } + + void IEnumerator.Reset() => _index = -1; } /// diff --git a/src/libraries/System.Private.CoreLib/src/System/Span.cs b/src/libraries/System.Private.CoreLib/src/System/Span.cs index 92b90b82910e4..c4fe41dc400ee 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; @@ -253,11 +255,27 @@ public bool MoveNext() } /// Gets the element at the current position of the enumerator. - public ref T Current + public readonly ref T Current { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => ref _span[_index]; } + + T IEnumerator.Current + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => Current; + } + + object IEnumerator.Current + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => Current!; + } + + void IDisposable.Dispose() { } + + void IEnumerator.Reset() => _index = -1; } /// 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..bf72345d652f2 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; @@ -74,5 +77,10 @@ public bool MoveNext() return true; } + + object IEnumerator.Current => throw new NotSupportedException(); + + void IDisposable.Dispose() { } + void IEnumerator.Reset() => throw new NotSupportedException(); } } diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index db49bbfb30de2..ed1aa6294a780 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -5095,12 +5095,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.IDisposable.Dispose() { throw null; } + void System.Collections.IEnumerator.Reset() { throw null; } } } public partial class ResolveEventArgs : System.EventArgs @@ -5552,12 +5556,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.IDisposable.Dispose() { throw null; } + void System.Collections.IEnumerator.Reset() { throw null; } } } public sealed partial class StackOverflowException : System.SystemException From 501f8764c396d565ee33e456d94235e6d23ef684 Mon Sep 17 00:00:00 2001 From: Alexander Radchenko Date: Sun, 11 Aug 2024 19:42:34 +0700 Subject: [PATCH 2/7] SpanLineEnumerator, SpanRuneEnumerator --- .../System.Memory/ref/System.Memory.cs | 10 +- .../tests/ReadOnlySpan/Split.T.cs | 4 +- .../tests/Span/EnumerateLines.cs | 153 +++++++++++++++++- .../tests/Span/EnumerateRunes.cs | 110 ++++++++++++- .../src/System/MemoryExtensions.cs | 3 + .../src/System/ReadOnlySpan.cs | 3 +- .../System.Private.CoreLib/src/System/Span.cs | 3 +- .../src/System/Text/SpanLineEnumerator.cs | 2 +- .../src/System/Text/SpanRuneEnumerator.cs | 27 +++- 9 files changed, 302 insertions(+), 13 deletions(-) diff --git a/src/libraries/System.Memory/ref/System.Memory.cs b/src/libraries/System.Memory/ref/System.Memory.cs index 6c74ea267f2cc..69caba4e0cefc 100644 --- a/src/libraries/System.Memory/ref/System.Memory.cs +++ b/src/libraries/System.Memory/ref/System.Memory.cs @@ -740,20 +740,26 @@ public static void Write(System.Span destination, in T value) where T : } namespace System.Text { - public ref partial struct SpanLineEnumerator : IEnumerator> + 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 IDisposable.Dispose() { throw null; } + void System.Collections.IEnumerator.Reset() { 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 IDisposable.Dispose() { throw null; } + void System.Collections.IEnumerator.Reset() { throw null; } } } diff --git a/src/libraries/System.Memory/tests/ReadOnlySpan/Split.T.cs b/src/libraries/System.Memory/tests/ReadOnlySpan/Split.T.cs index 192565f7ede87..ce758a999abcb 100644 --- a/src/libraries/System.Memory/tests/ReadOnlySpan/Split.T.cs +++ b/src/libraries/System.Memory/tests/ReadOnlySpan/Split.T.cs @@ -12,7 +12,7 @@ namespace System.SpanTests public static partial class ReadOnlySpanTests { [Fact] - public static void DefaultSpanSplitEnumeratorBehavior() + public static void SpanSplitEnumerator_Default() { var charSpanEnumerator = new MemoryExtensions.SpanSplitEnumerator(); Assert.Equal(new Range(0, 0), charSpanEnumerator.Current); @@ -158,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 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.Private.CoreLib/src/System/MemoryExtensions.cs b/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.cs index a30b011e05839..f4c102e85e030 100644 --- a/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.cs +++ b/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.cs @@ -4426,7 +4426,10 @@ 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 IDisposable.Dispose() { } void IEnumerator.Reset() => throw new NotSupportedException(); } diff --git a/src/libraries/System.Private.CoreLib/src/System/ReadOnlySpan.cs b/src/libraries/System.Private.CoreLib/src/System/ReadOnlySpan.cs index c8f2021a06c34..566f1c255616e 100644 --- a/src/libraries/System.Private.CoreLib/src/System/ReadOnlySpan.cs +++ b/src/libraries/System.Private.CoreLib/src/System/ReadOnlySpan.cs @@ -268,12 +268,14 @@ public ref readonly T Current 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)] @@ -281,7 +283,6 @@ object IEnumerator.Current } void IDisposable.Dispose() { } - void IEnumerator.Reset() => _index = -1; } diff --git a/src/libraries/System.Private.CoreLib/src/System/Span.cs b/src/libraries/System.Private.CoreLib/src/System/Span.cs index c4fe41dc400ee..84411a75cc9aa 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Span.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Span.cs @@ -261,12 +261,14 @@ public readonly ref T Current 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)] @@ -274,7 +276,6 @@ object IEnumerator.Current } void IDisposable.Dispose() { } - void IEnumerator.Reset() => _index = -1; } 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 bf72345d652f2..7edf895a63bff 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Text/SpanLineEnumerator.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Text/SpanLineEnumerator.cs @@ -46,6 +46,7 @@ public bool MoveNext() { if (!_isEnumeratorActive) { + _current = _remaining; return false; // EOF previously reached or enumerator was never initialized } @@ -79,7 +80,6 @@ public bool MoveNext() } object IEnumerator.Current => throw new NotSupportedException(); - void IDisposable.Dispose() { } void IEnumerator.Reset() => throw new NotSupportedException(); } 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..6a9a5ec5a7553 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 IDisposable.Dispose() { } + void IEnumerator.Reset() => throw new NotSupportedException(); } } From 97722139bfcf9d0e9bb56846e6a7cca401c5ad1f Mon Sep 17 00:00:00 2001 From: Alexander Radchenko Date: Mon, 12 Aug 2024 16:36:10 +0700 Subject: [PATCH 3/7] ReadOnlyTensorSpan, TensorSpan --- .../ref/System.Numerics.Tensors.netcore.cs | 15 +- .../Tensors/netcore/ReadOnlyTensorSpan.cs | 32 ++- .../Numerics/Tensors/netcore/TensorSpan.cs | 33 ++- .../Tensors/netcore/TensorSpanHelpers.cs | 2 +- .../tests/ReadOnlyTensorSpanTests.cs | 219 +++++++++++++++-- .../tests/TensorSpanTests.cs | 227 ++++++++++++++++-- 6 files changed, 483 insertions(+), 45 deletions(-) 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 9a4b08633db1d..678ab057a9e82 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 @@ -4,6 +4,9 @@ // Changes to this file must follow the https://aka.ms/api-review process. // ------------------------------------------------------------------------------ +using System.Collections; +using System.Collections.Generic; + namespace System.Buffers { [System.Diagnostics.CodeAnalysis.Experimental("SYSLIB5001", UrlFormat = "https://aka.ms/dotnet-warnings/{0}")] @@ -154,12 +157,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 +741,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 5122ca3e8513a..7e4482d0a1868 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.Linq; @@ -450,7 +452,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; @@ -467,7 +469,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. @@ -488,6 +491,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() { } } /// 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 c2d12bd7e814c..041c3f3902653 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.Linq; @@ -444,7 +446,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; @@ -461,8 +463,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. @@ -483,6 +485,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 d84b1130c96c3..615e02d0e1796 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 @@ -222,7 +222,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 e5e4a8c60bdc7..31305361a63e9 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,7 +13,7 @@ namespace System.Numerics.Tensors.Tests public class ReadOnlyTensorSpanTests { [Fact] - public static void ReadOnlyTensorSpanSystemArrayConstructorTests() + public static void ReadOnlyTensorSpan_SystemArrayConstructorTests() { // Make sure basic T[,] constructor works int[,] a = new int[,] { { 91, 92, -93, 94 } }; @@ -26,6 +25,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 @@ -34,6 +34,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 @@ -45,9 +46,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]; }); @@ -60,6 +63,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); @@ -68,27 +72,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]; }); @@ -99,6 +108,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); @@ -107,6 +117,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]); @@ -117,6 +128,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(() => @@ -133,6 +145,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]); @@ -143,6 +156,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]); @@ -153,6 +167,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(() => @@ -183,6 +198,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(() => @@ -199,6 +215,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]); @@ -209,6 +226,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]); @@ -219,10 +237,11 @@ 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 basic T[] constructor works int[] a = { 91, 92, -93, 94 }; @@ -233,6 +252,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 @@ -240,6 +260,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 @@ -249,9 +270,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]; }); @@ -262,16 +285,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); @@ -280,13 +305,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]; }); @@ -297,6 +325,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); @@ -305,6 +334,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(() => { @@ -320,6 +350,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]); @@ -330,6 +361,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]); @@ -340,6 +372,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]); @@ -350,6 +383,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(() => { @@ -376,6 +410,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(() => { @@ -384,10 +419,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]); @@ -395,6 +430,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 @@ -404,10 +440,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]; }); @@ -420,6 +458,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); @@ -428,15 +467,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]; }); @@ -447,6 +489,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); @@ -455,6 +498,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(() => { @@ -471,6 +515,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]); @@ -481,6 +526,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]); @@ -491,6 +537,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]); @@ -501,6 +548,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(() => { @@ -531,6 +579,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(() => { @@ -540,7 +589,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]; @@ -554,6 +603,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 @@ -566,6 +616,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(() => { @@ -573,6 +624,7 @@ public static unsafe void ReadOnlyTensorSpanPointerConstructorTests() fixed (int* p = b) { var spanInt = new ReadOnlyTensorSpan(p, 0); + ReadOnlyTensorSpan_TestEnumerator(spanInt); var x = spanInt[0]; } }); @@ -590,6 +642,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); @@ -598,12 +651,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]; } }); @@ -614,6 +669,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]; } }); @@ -625,6 +681,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); @@ -633,6 +690,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(() => @@ -653,6 +711,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]); @@ -663,6 +722,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]); @@ -673,6 +733,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]); @@ -683,6 +744,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(() => @@ -739,6 +801,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(() => @@ -753,7 +816,7 @@ 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]; @@ -782,6 +845,7 @@ 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]; @@ -814,10 +878,11 @@ 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]; @@ -834,6 +899,7 @@ 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; @@ -853,10 +919,11 @@ 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]; @@ -873,6 +940,7 @@ public static void ReadOnlyTensorSpanCopyTest() //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]; @@ -895,6 +963,7 @@ public static void ReadOnlyTensorSpanCopyTest() //Make sure its a copy rightSpan[0] = 100; Assert.NotEqual(leftSpan[0], rightSpan[0]); + ReadOnlyTensorSpan_TestEnumerator(leftSpan); leftData = [.. Enumerable.Range(0, 27)]; rightData = [.. Enumerable.Range(0, 27)]; @@ -906,17 +975,19 @@ public static void ReadOnlyTensorSpanCopyTest() { Assert.Equal(leftEnum.Current, rightEnum.Current); } + ReadOnlyTensorSpan_TestEnumerator(leftSpan); Assert.Throws(() => { - var l = leftData.AsTensorSpan(3, 3, 3); + ReadOnlyTensorSpan l = leftData.AsTensorSpan(3, 3, 3); var r = new TensorSpan(); + ReadOnlyTensorSpan_TestEnumerator(l); l.CopyTo(r); }); } [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]; @@ -934,6 +1005,7 @@ 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]; @@ -957,6 +1029,7 @@ public static void ReadOnlyTensorSpanTryCopyTest() //Make sure its a copy rightSpan[0] = 100; Assert.NotEqual(leftSpan[0], rightSpan[0]); + ReadOnlyTensorSpan_TestEnumerator(leftSpan); leftData = [.. Enumerable.Range(0, 27)]; rightData = [.. Enumerable.Range(0, 27)]; @@ -969,19 +1042,22 @@ public static void ReadOnlyTensorSpanTryCopyTest() { Assert.Equal(leftEnum.Current, rightEnum.Current); } + ReadOnlyTensorSpan_TestEnumerator(leftSpan); - var l = leftData.AsTensorSpan(3, 3, 3); + ReadOnlyTensorSpan l = leftData.AsTensorSpan(3, 3, 3); var r = new TensorSpan(); success = l.TryCopyTo(r); Assert.False(success); + ReadOnlyTensorSpan_TestEnumerator(l); } [Fact] - public static void ReadOnlyTensorSpanSliceTest() + public static void ReadOnlyTensorSpan_SliceTest() { int[] a = [1, 2, 3, 4, 5, 6, 7, 8, 9]; int[] results = new int[9]; ReadOnlyTensorSpan spanInt = a.AsTensorSpan(3, 3); + ReadOnlyTensorSpan_TestEnumerator(spanInt); Assert.Throws(() => a.AsTensorSpan(2, 3).Slice(0..1)); Assert.Throws(() => a.AsTensorSpan(2, 3).Slice(1..2)); @@ -1002,6 +1078,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]); @@ -1022,6 +1099,7 @@ 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]); @@ -1036,6 +1114,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]); @@ -1052,6 +1131,7 @@ 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); @@ -1067,6 +1147,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]); @@ -1087,6 +1169,7 @@ 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); @@ -1105,10 +1188,12 @@ 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); @@ -1117,15 +1202,105 @@ public static void LongArrayAsReadOnlyTensorSpan() 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 d = default; Assert.True(span == d); + ReadOnlyTensorSpan_TestEnumerator(span); + ReadOnlyTensorSpan_TestEnumerator(d); + } + + private static void ReadOnlyTensorSpan_TestEnumerator(ReadOnlyTensorSpan span) + where T: INumber + { + 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 e43a7d55e7dfa..0c1257b883c33 100644 --- a/src/libraries/System.Numerics.Tensors/tests/TensorSpanTests.cs +++ b/src/libraries/System.Numerics.Tensors/tests/TensorSpanTests.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.Linq; using System.Runtime.InteropServices; @@ -250,7 +251,7 @@ public void TensorExtensionsTwoSpanInFloatOut(TensorPrimitivesTwoSpanInTOut(() => { var spanInt = new TensorSpan(b); + TensorSpan_TestEnumerator(spanInt); var x = spanInt[0, 0]; }); @@ -296,6 +301,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); @@ -304,27 +310,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]; }); @@ -335,6 +346,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); @@ -343,6 +355,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]); @@ -353,6 +366,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(() => @@ -369,6 +383,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]); @@ -379,6 +394,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]); @@ -389,6 +405,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(() => @@ -419,6 +436,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(() => @@ -435,6 +453,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]); @@ -445,6 +464,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]); @@ -455,10 +475,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 basic T[] constructor works int[] a = { 91, 92, -93, 94 }; @@ -469,6 +490,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 @@ -476,6 +498,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 @@ -485,9 +508,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]; }); @@ -498,6 +523,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); @@ -508,6 +534,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); @@ -516,13 +543,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]; }); @@ -533,6 +563,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); @@ -541,6 +572,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(() => { @@ -556,6 +588,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]); @@ -566,6 +599,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]); @@ -576,6 +610,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]); @@ -586,6 +621,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(() => { @@ -612,6 +648,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(() => { @@ -620,7 +657,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]; @@ -631,6 +668,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 @@ -640,10 +678,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]; }); @@ -656,6 +696,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); @@ -664,15 +705,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]; }); @@ -683,6 +727,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); @@ -691,6 +736,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(() => { @@ -707,6 +753,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]); @@ -717,6 +764,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]); @@ -727,6 +775,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]); @@ -737,6 +786,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(() => { @@ -767,6 +817,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(() => { @@ -776,7 +827,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]; @@ -790,6 +841,7 @@ public static unsafe void TensorSpanPointerConstructorTests() Assert.Equal(92, spanInt[1]); Assert.Equal(-93, spanInt[2]); Assert.Equal(94, spanInt[3]); + TensorSpan_TestEnumerator(spanInt); } // Make sure empty span works @@ -802,6 +854,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(() => { @@ -809,6 +862,7 @@ public static unsafe void TensorSpanPointerConstructorTests() fixed (int* p = b) { var spanInt = new TensorSpan(p, 0); + TensorSpan_TestEnumerator(spanInt); var x = spanInt[0]; } }); @@ -825,6 +879,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); @@ -833,12 +888,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]; } }); @@ -849,6 +906,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]; } }); @@ -860,6 +918,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); @@ -868,6 +927,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(() => @@ -888,6 +948,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]); @@ -898,6 +959,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]); @@ -908,6 +970,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]); @@ -918,6 +981,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(() => @@ -974,6 +1038,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(() => @@ -988,7 +1053,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]; @@ -1017,6 +1082,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]; @@ -1049,10 +1115,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]; @@ -1069,15 +1136,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; @@ -1102,15 +1170,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); @@ -1134,6 +1202,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); @@ -1143,6 +1212,7 @@ public static void TensorSpanFillTest() { Assert.Equal(-1, enumerator.Current); } + TensorSpan_TestEnumerator(spanInt); a = [.. Enumerable.Range(0, 27)]; spanInt = a.AsTensorSpan(3,3,3); @@ -1152,6 +1222,7 @@ public static void TensorSpanFillTest() { Assert.Equal(-1, enumerator.Current); } + TensorSpan_TestEnumerator(spanInt); a = [.. Enumerable.Range(0, 12)]; spanInt = a.AsTensorSpan(3, 2, 2); @@ -1161,6 +1232,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); @@ -1170,6 +1242,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); @@ -1179,10 +1252,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); @@ -1212,6 +1286,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); @@ -1237,6 +1313,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); @@ -1246,6 +1324,7 @@ public static void TensorSpanClearTest() { Assert.Equal(0, enumerator.Current); } + TensorSpan_TestEnumerator(spanInt); a = [.. Enumerable.Range(0, 12)]; spanInt = a.AsTensorSpan(3, 2, 2); @@ -1255,6 +1334,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); @@ -1264,6 +1344,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); @@ -1273,10 +1354,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]; @@ -1293,6 +1375,8 @@ public static void TensorSpanCopyTest() //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]; @@ -1315,6 +1399,8 @@ public static void TensorSpanCopyTest() //Make sure its a copy leftSpan[0] = 100; Assert.NotEqual(leftSpan[0], rightSpan[0]); + TensorSpan_TestEnumerator(leftSpan); + TensorSpan_TestEnumerator(rightSpan); leftData = [.. Enumerable.Range(0, 27)]; rightData = [.. Enumerable.Range(0, 27)]; @@ -1326,17 +1412,21 @@ public static void TensorSpanCopyTest() { Assert.Equal(leftEnum.Current, rightEnum.Current); } + TensorSpan_TestEnumerator(leftSpan); + TensorSpan_TestEnumerator(rightSpan); Assert.Throws(() => { var l = leftData.AsTensorSpan(3, 3, 3); var r = new TensorSpan(); + TensorSpan_TestEnumerator(l); + TensorSpan_TestEnumerator(r); l.CopyTo(r); }); } [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]; @@ -1354,6 +1444,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]; @@ -1377,6 +1469,8 @@ public static void TensorSpanTryCopyTest() //Make sure its a copy leftSpan[0] = 100; Assert.NotEqual(leftSpan[0], rightSpan[0]); + TensorSpan_TestEnumerator(leftSpan); + TensorSpan_TestEnumerator(rightSpan); leftData = [.. Enumerable.Range(0, 27)]; rightData = [.. Enumerable.Range(0, 27)]; @@ -1389,19 +1483,24 @@ 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); } [Fact] - public static void TensorSpanSliceTest() + public static void TensorSpan_SliceTest() { int[] a = [1, 2, 3, 4, 5, 6, 7, 8, 9]; int[] results = new int[9]; TensorSpan spanInt = a.AsTensorSpan(3, 3); + TensorSpan_TestEnumerator(spanInt); Assert.Throws(() => a.AsTensorSpan(2, 3).Slice(0..1)); Assert.Throws(() => a.AsTensorSpan(2, 3).Slice(1..2)); @@ -1422,6 +1521,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]); @@ -1442,6 +1542,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]); @@ -1456,6 +1557,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]); @@ -1472,6 +1574,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); @@ -1487,6 +1590,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]); @@ -1507,6 +1612,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); @@ -1525,10 +1631,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); @@ -1537,14 +1645,103 @@ 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 : INumber + { + 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; + + Assert.Equal(span[curIndexes], current); + current++; + Assert.Equal(span[curIndexes], current); + current--; + 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; } } } From 5f8e87474cea5fa54c29d4484902382ee1557f4f Mon Sep 17 00:00:00 2001 From: Alexander Radchenko Date: Tue, 13 Aug 2024 09:27:42 +0700 Subject: [PATCH 4/7] ValueMatchEnumerator, ValueSplitEnumerator --- .../ref/System.Text.RegularExpressions.cs | 13 ++- .../Regex.EnumerateMatches.cs | 11 ++- .../Regex.EnumerateSplits.cs | 13 ++- .../Regex.EnumerateMatches.Tests.cs | 97 ++++++++++++++++++- .../Regex.EnumerateSplits.Tests.cs | 63 ++++++++++++ 5 files changed, 192 insertions(+), 5 deletions(-) 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..4ef8521448a45 100644 --- a/src/libraries/System.Text.RegularExpressions/ref/System.Text.RegularExpressions.cs +++ b/src/libraries/System.Text.RegularExpressions/ref/System.Text.RegularExpressions.cs @@ -4,6 +4,9 @@ // Changes to this file must follow the https://aka.ms/api-review process. // ------------------------------------------------------------------------------ +using System.Collections; +using System.Collections.Generic; + namespace System.Text.RegularExpressions { public partial class Capture @@ -241,21 +244,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 IEnumerator.Current { get { throw null; } } + void IEnumerator.Reset() { throw null; } + void 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..cd6d8bf114b89 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; @@ -149,6 +151,9 @@ public bool MoveNext() return true; } + _current = new ValueMatch(_regex.RightToLeft ? 0 : _input.Length, 0); + _startAt = _current.Index; + _prevLen = 0; return false; } @@ -157,6 +162,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..ab763bf69ee2f 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,74 @@ 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 + { + var match = enumerator.Current; + EnumerateMatches_CurrentI(enumerator); + Assert.Equal(default, match.Index); + Assert.Equal(default, match.Length); + Assert.Empty(span.Slice(match.Index, match.Length).ToString()); + + try { enumerator.Reset(); } catch (NotSupportedException) { }; + enumerator.Dispose(); + match = enumerator.Current; + EnumerateMatches_CurrentI(enumerator); + Assert.Equal(default, match.Index); + Assert.Equal(default, match.Length); + Assert.Empty(span.Slice(match.Index, match.Length).ToString()); + + foreach (var capture in expected) + { + Assert.True(enumerator.MoveNext()); + 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()); + match = enumerator.Current; + EnumerateMatches_CurrentI(enumerator); + Assert.True(match.Index == 0 || match.Index == span.Length); + Assert.Equal(default, match.Length); + Assert.Empty(span.Slice(match.Index, match.Length).ToString()); + + try { enumerator.Reset(); } catch (NotSupportedException) { }; + enumerator.Dispose(); + Assert.False(enumerator.MoveNext()); + match = enumerator.Current; + EnumerateMatches_CurrentI(enumerator); + Assert.True(match.Index == 0 || match.Index == span.Length); + Assert.Equal(default, match.Length); + Assert.Empty(span.Slice(match.Index, match.Length).ToString()); + } + + internal static void EnumerateMatches_CurrentI(TEnumerator enumerator) + where TEnumerator : IEnumerator, allows ref struct + { + try { _ = enumerator.Current; } catch (NotSupportedException) { }; + } } public partial class RegexMatchTests @@ -163,6 +245,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 +269,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 +286,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 +303,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 +312,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] From ffc350e696b8061d15ea54414d969c740ee8c496 Mon Sep 17 00:00:00 2001 From: Alexander Radchenko Date: Tue, 13 Aug 2024 13:04:13 +0700 Subject: [PATCH 5/7] @huoyaoyuan Review --- src/libraries/System.Memory/ref/System.Memory.cs | 8 +++----- .../ref/System.Numerics.Tensors.netcore.cs | 3 --- .../System.Private.CoreLib/src/System/MemoryExtensions.cs | 4 ++-- .../System.Private.CoreLib/src/System/ReadOnlySpan.cs | 2 +- src/libraries/System.Private.CoreLib/src/System/Span.cs | 4 ++-- .../src/System/Text/SpanLineEnumerator.cs | 4 ++-- .../src/System/Text/SpanRuneEnumerator.cs | 2 +- src/libraries/System.Runtime/ref/System.Runtime.cs | 4 ++-- .../ref/System.Text.RegularExpressions.cs | 3 --- 9 files changed, 13 insertions(+), 21 deletions(-) diff --git a/src/libraries/System.Memory/ref/System.Memory.cs b/src/libraries/System.Memory/ref/System.Memory.cs index 69caba4e0cefc..a1e02350bd689 100644 --- a/src/libraries/System.Memory/ref/System.Memory.cs +++ b/src/libraries/System.Memory/ref/System.Memory.cs @@ -5,8 +5,6 @@ // ------------------------------------------------------------------------------ #if !BUILDING_CORELIB_REFERENCE -using System.Collections.Generic; - namespace System { public readonly partial struct SequencePosition : System.IEquatable @@ -425,8 +423,8 @@ public static void Sort(this System.Span keys, Sy public System.MemoryExtensions.SpanSplitEnumerator GetEnumerator() { throw null; } public bool MoveNext() { throw null; } object System.Collections.IEnumerator.Current { get { throw null; } } - void System.IDisposable.Dispose() { 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] @@ -748,8 +746,8 @@ namespace System.Text public System.Text.SpanLineEnumerator GetEnumerator() { throw null; } public bool MoveNext() { throw null; } object System.Collections.IEnumerator.Current { get { throw null; } } - void IDisposable.Dispose() { throw null; } void System.Collections.IEnumerator.Reset() { throw null; } + void IDisposable.Dispose() { throw null; } } public ref partial struct SpanRuneEnumerator : System.Collections.Generic.IEnumerator, System.Collections.IEnumerator, System.IDisposable { @@ -759,7 +757,7 @@ namespace System.Text public System.Text.SpanRuneEnumerator GetEnumerator() { throw null; } public bool MoveNext() { throw null; } object System.Collections.IEnumerator.Current { get { throw null; } } - void IDisposable.Dispose() { throw null; } void System.Collections.IEnumerator.Reset() { throw null; } + void IDisposable.Dispose() { throw null; } } } 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 678ab057a9e82..852aea9e2421b 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 @@ -4,9 +4,6 @@ // Changes to this file must follow the https://aka.ms/api-review process. // ------------------------------------------------------------------------------ -using System.Collections; -using System.Collections.Generic; - namespace System.Buffers { [System.Diagnostics.CodeAnalysis.Experimental("SYSLIB5001", UrlFormat = "https://aka.ms/dotnet-warnings/{0}")] diff --git a/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.cs b/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.cs index f4c102e85e030..26c2b331d26dc 100644 --- a/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.cs +++ b/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.cs @@ -4317,7 +4317,7 @@ private static bool TryWrite(Span destination, IForma /// Gets the current element of the enumeration. /// Returns a instance that indicates the bounds of the current element withing the source span. - public readonly Range Current => new Range(_startCurrent, _endCurrent); + public Range Current => new Range(_startCurrent, _endCurrent); /// Initializes the enumerator for . internal SpanSplitEnumerator(ReadOnlySpan span, SearchValues searchValues) @@ -4430,8 +4430,8 @@ public bool MoveNext() /// Returns a instance that indicates the bounds of the current element withing the source span. object IEnumerator.Current => Current; - void IDisposable.Dispose() { } 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 566f1c255616e..aba4649d47e81 100644 --- a/src/libraries/System.Private.CoreLib/src/System/ReadOnlySpan.cs +++ b/src/libraries/System.Private.CoreLib/src/System/ReadOnlySpan.cs @@ -282,8 +282,8 @@ object IEnumerator.Current get => Current!; } - void IDisposable.Dispose() { } 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 84411a75cc9aa..49d79a3dd9fb8 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Span.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Span.cs @@ -255,7 +255,7 @@ public bool MoveNext() } /// Gets the element at the current position of the enumerator. - public readonly ref T Current + public ref T Current { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => ref _span[_index]; @@ -275,8 +275,8 @@ object IEnumerator.Current get => Current!; } - void IDisposable.Dispose() { } 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 7edf895a63bff..e10caf2efeac0 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Text/SpanLineEnumerator.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Text/SpanLineEnumerator.cs @@ -46,7 +46,7 @@ public bool MoveNext() { if (!_isEnumeratorActive) { - _current = _remaining; + _current = default; return false; // EOF previously reached or enumerator was never initialized } @@ -80,7 +80,7 @@ public bool MoveNext() } object IEnumerator.Current => throw new NotSupportedException(); - void IDisposable.Dispose() { } 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 6a9a5ec5a7553..0446649faf7c1 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Text/SpanRuneEnumerator.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Text/SpanRuneEnumerator.cs @@ -69,7 +69,7 @@ public bool MoveNext() /// object IEnumerator.Current => Current; - void IDisposable.Dispose() { } 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 ed1aa6294a780..f9630bd4dc342 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -5103,8 +5103,8 @@ public void CopyTo(System.Span destination) { } public bool MoveNext() { throw null; } T System.Collections.Generic.IEnumerator.Current { get { throw null; } } object System.Collections.IEnumerator.Current { get { throw null; } } - void System.IDisposable.Dispose() { throw null; } void System.Collections.IEnumerator.Reset() { throw null; } + void System.IDisposable.Dispose() { throw null; } } } public partial class ResolveEventArgs : System.EventArgs @@ -5564,8 +5564,8 @@ public void Fill(T value) { } public bool MoveNext() { throw null; } T System.Collections.Generic.IEnumerator.Current { get { throw null; } } object System.Collections.IEnumerator.Current { get { throw null; } } - void System.IDisposable.Dispose() { 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 4ef8521448a45..ffcca213004bf 100644 --- a/src/libraries/System.Text.RegularExpressions/ref/System.Text.RegularExpressions.cs +++ b/src/libraries/System.Text.RegularExpressions/ref/System.Text.RegularExpressions.cs @@ -4,9 +4,6 @@ // Changes to this file must follow the https://aka.ms/api-review process. // ------------------------------------------------------------------------------ -using System.Collections; -using System.Collections.Generic; - namespace System.Text.RegularExpressions { public partial class Capture From 21154bc4e2e513c503d065073f558634be6a5817 Mon Sep 17 00:00:00 2001 From: Alexander Radchenko Date: Tue, 13 Aug 2024 14:21:29 +0700 Subject: [PATCH 6/7] ref\System.Text.RegularExpressions --- .../ref/System.Text.RegularExpressions.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 ffcca213004bf..bb8537c5bd0a3 100644 --- a/src/libraries/System.Text.RegularExpressions/ref/System.Text.RegularExpressions.cs +++ b/src/libraries/System.Text.RegularExpressions/ref/System.Text.RegularExpressions.cs @@ -259,9 +259,9 @@ protected internal static void ValidateMatchTimeout(System.TimeSpan matchTimeout 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 IEnumerator.Current { get { throw null; } } - void IEnumerator.Reset() { throw null; } - void IDisposable.Dispose() { 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}")] From 2f60a8bb8d17647482f546d61e86f2a53e69fa74 Mon Sep 17 00:00:00 2001 From: Alexander Radchenko Date: Fri, 6 Sep 2024 03:50:21 +0700 Subject: [PATCH 7/7] Revert some changes Regex.EnumerateMatches.cs after code review Calling MoveNext after it's already returned false is a programmer error. It is not something we need to optimize for. --- .../System/Text/RegularExpressions/Regex.EnumerateMatches.cs | 3 --- 1 file changed, 3 deletions(-) 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 cd6d8bf114b89..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 @@ -151,9 +151,6 @@ public bool MoveNext() return true; } - _current = new ValueMatch(_regex.RightToLeft ? 0 : _input.Length, 0); - _startAt = _current.Index; - _prevLen = 0; return false; }