diff options
author | Tarek Mahmoud Sayed <tarekms@microsoft.com> | 2019-02-06 15:15:46 -0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-02-06 15:15:46 -0800 |
commit | fe1185343cd02559824fcbfaf69967166978e8e5 (patch) | |
tree | 84c26b6a1c1a05dcb211665a9ed8ddf339179aa6 | |
parent | 17f4677cd45e2feed716d2b63046fa24a66bf662 (diff) | |
download | coreclr-fe1185343cd02559824fcbfaf69967166978e8e5.tar.gz coreclr-fe1185343cd02559824fcbfaf69967166978e8e5.tar.bz2 coreclr-fe1185343cd02559824fcbfaf69967166978e8e5.zip |
Index and Range updates (#22331)
* Index and Range updates
* Address @mikedn feedback
* Address Feedback
* more feedback
* Use Deconstruct in Range.GetOffsetAndLength
* Rename GetArrayRange to GetSubArray
* Temporary disable the old Corefx Range tests
* Return back the TimeSpan test disabling
* Fix Range jit test
* Exclude the jit test
* revert the changes in the jit Range test
* Address Suggested Feedback
15 files changed, 568 insertions, 99 deletions
diff --git a/src/System.Private.CoreLib/shared/System/Index.cs b/src/System.Private.CoreLib/shared/System/Index.cs index 887506ec62..9767b981ef 100644 --- a/src/System.Private.CoreLib/shared/System/Index.cs +++ b/src/System.Private.CoreLib/shared/System/Index.cs @@ -3,34 +3,138 @@ // See the LICENSE file in the project root for more information. using System.Diagnostics; +using System.Runtime.CompilerServices; namespace System { + /// <summary>Represent a type can be used to index a collection either from the start or the end.</summary> + /// <remarks> + /// Index is used by the C# compiler to support the new index syntax + /// <code> + /// int[] someArray = new int[5] { 1, 2, 3, 4, 5 } ; + /// int lastElement = someArray[^1]; // lastElement = 5 + /// </code> + /// </remarks> public readonly struct Index : IEquatable<Index> { private readonly int _value; - public Index(int value, bool fromEnd) + /// <summary>Construct an Index using a value and indicating if the index is from the start or from the end.</summary> + /// <param name="value">The index value. it has to be zero or positive number.</param> + /// <param name="fromEnd">Indicating if the index is from the start or from the end.</param> + /// <remarks> + /// If the Index constructed from the end, index value 1 means pointing at the last element and index value 0 means pointing at beyond last element. + /// </remarks> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Index(int value, bool fromEnd = false) { if (value < 0) { ThrowHelper.ThrowValueArgumentOutOfRange_NeedNonNegNumException(); } - _value = fromEnd ? ~value : value; + if (fromEnd) + _value = ~value; + else + _value = value; } - public int Value => _value < 0 ? ~_value : _value; - public bool FromEnd => _value < 0; + // The following private constructors mainly created for perf reason to avoid the checks + private Index(int value) + { + _value = value; + } + + /// <summary>Create an Index pointing at first element.</summary> + public static Index Start => new Index(0); + + /// <summary>Create an Index pointing at beyond last element.</summary> + public static Index End => new Index(~0); + + /// <summary>Create an Index from the start at the position indicated by the value.</summary> + /// <param name="value">The index value from the start.</param> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Index FromStart(int value) + { + if (value < 0) + { + ThrowHelper.ThrowValueArgumentOutOfRange_NeedNonNegNumException(); + } + + return new Index(value); + } + + /// <summary>Create an Index from the end at the position indicated by the value.</summary> + /// <param name="value">The index value from the end.</param> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Index FromEnd(int value) + { + if (value < 0) + { + ThrowHelper.ThrowValueArgumentOutOfRange_NeedNonNegNumException(); + } + + return new Index(~value); + } + + /// <summary>Returns the index value.</summary> + public int Value + { + get + { + if (_value < 0) + return ~_value; + else + return _value; + } + } + + /// <summary>Indicates whether the index is from the start or the end.</summary> + public bool IsFromEnd => _value < 0; + + /// <summary>Calculate the offset from the start using the giving collection length.</summary> + /// <param name="length">The length of the collection that the Index will be used with. length has to be a positive value</param> + /// <remarks> + /// For performance reason, we don't validate the input length parameter and the returned offset value against negative values. + /// we don't validate either the returned offset is greater than the input length. + /// It is expected Index will be used with collections which always have non negative length/count. If the returned offset is negative and + /// then used to index a collection will get out of range exception which will be same affect as the validation. + /// </remarks> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int GetOffset(int length) + { + int offset; + + if (IsFromEnd) + offset = length - (~_value); + else + offset = _value; + + return offset; + } + + /// <summary>Indicates whether the current Index object is equal to another object of the same type.</summary> + /// <param name="value">An object to compare with this object</param> public override bool Equals(object value) => value is Index && _value == ((Index)value)._value; + + /// <summary>Indicates whether the current Index object is equal to another Index object.</summary> + /// <param name="other">An object to compare with this object</param> public bool Equals (Index other) => _value == other._value; - public override int GetHashCode() + /// <summary>Returns the hash code for this instance.</summary> + public override int GetHashCode() => _value; + + /// <summary>Converts integer number to an Index.</summary> + public static implicit operator Index(int value) => FromStart(value); + + /// <summary>Converts the value of the current Index object to its equivalent string representation.</summary> + public override string ToString() { - return _value; - } + if (IsFromEnd) + return ToStringFromEnd(); - public override string ToString() => FromEnd ? ToStringFromEnd() : ((uint)Value).ToString(); + return ((uint)Value).ToString(); + } private string ToStringFromEnd() { @@ -41,7 +145,5 @@ namespace System return new string(span.Slice(0, charsWritten + 1)); } - public static implicit operator Index(int value) - => new Index(value, fromEnd: false); } } diff --git a/src/System.Private.CoreLib/shared/System/Memory.cs b/src/System.Private.CoreLib/shared/System/Memory.cs index 033b806c20..ba31a6aeae 100644 --- a/src/System.Private.CoreLib/shared/System/Memory.cs +++ b/src/System.Private.CoreLib/shared/System/Memory.cs @@ -30,7 +30,7 @@ namespace System private readonly object _object; private readonly int _index; private readonly int _length; - + /// <summary> /// Creates a new memory over the entirety of the target array. /// </summary> @@ -259,6 +259,35 @@ namespace System } /// <summary> + /// Forms a slice out of the given memory, beginning at 'startIndex' + /// </summary> + /// <param name="startIndex">The index at which to begin this slice.</param> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Memory<T> Slice(Index startIndex) + { + int actualIndex = startIndex.GetOffset(_length); + return Slice(actualIndex); + } + + /// <summary> + /// Forms a slice out of the given memory using the range start and end indexes. + /// </summary> + /// <param name="range">The range used to slice the memory using its start and end indexes.</param> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Memory<T> Slice(Range range) + { + (int start, int length) = range.GetOffsetAndLength(_length); + // It is expected for _index + start to be negative if the memory is already pre-pinned. + return new Memory<T>(_object, _index + start, length); + } + + /// <summary> + /// Forms a slice out of the given memory using the range start and end indexes. + /// </summary> + /// <param name="range">The range used to slice the memory using its start and end indexes.</param> + public Memory<T> this[Range range] => Slice(range); + + /// <summary> /// Returns a span from the memory. /// </summary> public unsafe Span<T> Span @@ -336,7 +365,7 @@ namespace System ThrowHelper.ThrowArgumentOutOfRangeException(); } #endif - + refToReturn = ref Unsafe.Add(ref refToReturn, desiredStartIndex); lengthOfUnderlyingSpan = desiredLength; } diff --git a/src/System.Private.CoreLib/shared/System/MemoryExtensions.Fast.cs b/src/System.Private.CoreLib/shared/System/MemoryExtensions.Fast.cs index 11980fbcac..1e1fd90e44 100644 --- a/src/System.Private.CoreLib/shared/System/MemoryExtensions.Fast.cs +++ b/src/System.Private.CoreLib/shared/System/MemoryExtensions.Fast.cs @@ -401,6 +401,54 @@ namespace System } /// <summary> + /// Creates a new span over the portion of the target array. + /// </summary> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Span<T> AsSpan<T>(this T[] array, Index startIndex) + { + if (array == null) + { + if (!startIndex.Equals(Index.Start)) + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array); + + return default; + } + + if (default(T) == null && array.GetType() != typeof(T[])) + ThrowHelper.ThrowArrayTypeMismatchException(); + + int actualIndex = startIndex.GetOffset(array.Length); + if ((uint)actualIndex > (uint)array.Length) + ThrowHelper.ThrowArgumentOutOfRangeException(); + + return new Span<T>(ref Unsafe.Add(ref Unsafe.As<byte, T>(ref array.GetRawSzArrayData()), actualIndex), array.Length - actualIndex); + } + + /// <summary> + /// Creates a new span over the portion of the target array. + /// </summary> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Span<T> AsSpan<T>(this T[] array, Range range) + { + if (array == null) + { + Index startIndex = range.Start; + Index endIndex = range.End; + + if (!startIndex.Equals(Index.Start) || !endIndex.Equals(Index.Start)) + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array); + + return default; + } + + if (default(T) == null && array.GetType() != typeof(T[])) + ThrowHelper.ThrowArrayTypeMismatchException(); + + (int start, int length) = range.GetOffsetAndLength(array.Length); + return new Span<T>(ref Unsafe.Add(ref Unsafe.As<byte, T>(ref array.GetRawSzArrayData()), start), length); + } + + /// <summary> /// Creates a new readonly span over the portion of the target string. /// </summary> /// <param name="text">The target string.</param> @@ -506,6 +554,26 @@ namespace System /// <summary>Creates a new <see cref="ReadOnlyMemory{T}"/> over the portion of the target string.</summary> /// <param name="text">The target string.</param> + /// <param name="startIndex">The index at which to begin this slice.</param> + public static ReadOnlyMemory<char> AsMemory(this string text, Index startIndex) + { + if (text == null) + { + if (!startIndex.Equals(Index.Start)) + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.text); + + return default; + } + + int actualIndex = startIndex.GetOffset(text.Length); + if ((uint)actualIndex > (uint)text.Length) + ThrowHelper.ThrowArgumentOutOfRangeException(); + + return new ReadOnlyMemory<char>(text, actualIndex, text.Length - actualIndex); + } + + /// <summary>Creates a new <see cref="ReadOnlyMemory{T}"/> over the portion of the target string.</summary> + /// <param name="text">The target string.</param> /// <param name="start">The index at which to begin this slice.</param> /// <param name="length">The desired length for the slice (exclusive).</param> /// <remarks>Returns default when <paramref name="text"/> is null.</remarks> @@ -532,5 +600,25 @@ namespace System return new ReadOnlyMemory<char>(text, start, length); } + + /// <summary>Creates a new <see cref="ReadOnlyMemory{T}"/> over the portion of the target string.</summary> + /// <param name="text">The target string.</param> + /// <param name="range">The range used to indicate the start and length of the sliced string.</param> + public static ReadOnlyMemory<char> AsMemory(this string text, Range range) + { + if (text == null) + { + Index startIndex = range.Start; + Index endIndex = range.End; + + if (!startIndex.Equals(Index.Start) || !endIndex.Equals(Index.Start)) + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.text); + + return default; + } + + (int start, int length) = range.GetOffsetAndLength(text.Length); + return new ReadOnlyMemory<char>(text, start, length); + } } } diff --git a/src/System.Private.CoreLib/shared/System/MemoryExtensions.cs b/src/System.Private.CoreLib/shared/System/MemoryExtensions.cs index 34b49d4123..869123a81d 100644 --- a/src/System.Private.CoreLib/shared/System/MemoryExtensions.cs +++ b/src/System.Private.CoreLib/shared/System/MemoryExtensions.cs @@ -125,7 +125,7 @@ namespace System } /// <summary> - /// Removes all leading and trailing occurrences of a set of characters specified + /// Removes all leading and trailing occurrences of a set of characters specified /// in a readonly span from the span. /// </summary> /// <param name="span">The source span from which the characters are removed.</param> @@ -137,7 +137,7 @@ namespace System } /// <summary> - /// Removes all leading occurrences of a set of characters specified + /// Removes all leading occurrences of a set of characters specified /// in a readonly span from the span. /// </summary> /// <param name="span">The source span from which the characters are removed.</param> @@ -166,7 +166,7 @@ namespace System } /// <summary> - /// Removes all trailing occurrences of a set of characters specified + /// Removes all trailing occurrences of a set of characters specified /// in a readonly span from the span. /// </summary> /// <param name="span">The source span from which the characters are removed.</param> @@ -258,7 +258,7 @@ namespace System } /// <summary> - /// Searches for the specified value and returns the index of its first occurrence. If not found, returns -1. Values are compared using IEquatable{T}.Equals(T). + /// Searches for the specified value and returns the index of its first occurrence. If not found, returns -1. Values are compared using IEquatable{T}.Equals(T). /// </summary> /// <param name="span">The span to search.</param> /// <param name="value">The value to search for.</param> @@ -282,7 +282,7 @@ namespace System } /// <summary> - /// Searches for the specified sequence and returns the index of its first occurrence. If not found, returns -1. Values are compared using IEquatable{T}.Equals(T). + /// Searches for the specified sequence and returns the index of its first occurrence. If not found, returns -1. Values are compared using IEquatable{T}.Equals(T). /// </summary> /// <param name="span">The span to search.</param> /// <param name="value">The sequence to search for.</param> @@ -307,7 +307,7 @@ namespace System } /// <summary> - /// Searches for the specified value and returns the index of its last occurrence. If not found, returns -1. Values are compared using IEquatable{T}.Equals(T). + /// Searches for the specified value and returns the index of its last occurrence. If not found, returns -1. Values are compared using IEquatable{T}.Equals(T). /// </summary> /// <param name="span">The span to search.</param> /// <param name="value">The value to search for.</param> @@ -331,7 +331,7 @@ namespace System } /// <summary> - /// Searches for the specified sequence and returns the index of its last occurrence. If not found, returns -1. Values are compared using IEquatable{T}.Equals(T). + /// Searches for the specified sequence and returns the index of its last occurrence. If not found, returns -1. Values are compared using IEquatable{T}.Equals(T). /// </summary> /// <param name="span">The span to search.</param> /// <param name="value">The sequence to search for.</param> @@ -350,7 +350,7 @@ namespace System } /// <summary> - /// Determines whether two sequences are equal by comparing the elements using IEquatable{T}.Equals(T). + /// Determines whether two sequences are equal by comparing the elements using IEquatable{T}.Equals(T). /// </summary> [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool SequenceEqual<T>(this Span<T> span, ReadOnlySpan<T> other) @@ -369,7 +369,7 @@ namespace System } /// <summary> - /// Determines the relative order of the sequences being compared by comparing the elements using IComparable{T}.CompareTo(T). + /// Determines the relative order of the sequences being compared by comparing the elements using IComparable{T}.CompareTo(T). /// </summary> public static int SequenceCompareTo<T>(this Span<T> span, ReadOnlySpan<T> other) where T : IComparable<T> @@ -392,7 +392,7 @@ namespace System } /// <summary> - /// Searches for the specified value and returns the index of its first occurrence. If not found, returns -1. Values are compared using IEquatable{T}.Equals(T). + /// Searches for the specified value and returns the index of its first occurrence. If not found, returns -1. Values are compared using IEquatable{T}.Equals(T). /// </summary> /// <param name="span">The span to search.</param> /// <param name="value">The value to search for.</param> @@ -416,7 +416,7 @@ namespace System } /// <summary> - /// Searches for the specified sequence and returns the index of its first occurrence. If not found, returns -1. Values are compared using IEquatable{T}.Equals(T). + /// Searches for the specified sequence and returns the index of its first occurrence. If not found, returns -1. Values are compared using IEquatable{T}.Equals(T). /// </summary> /// <param name="span">The span to search.</param> /// <param name="value">The sequence to search for.</param> @@ -441,7 +441,7 @@ namespace System } /// <summary> - /// Searches for the specified value and returns the index of its last occurrence. If not found, returns -1. Values are compared using IEquatable{T}.Equals(T). + /// Searches for the specified value and returns the index of its last occurrence. If not found, returns -1. Values are compared using IEquatable{T}.Equals(T). /// </summary> /// <param name="span">The span to search.</param> /// <param name="value">The value to search for.</param> @@ -465,7 +465,7 @@ namespace System } /// <summary> - /// Searches for the specified sequence and returns the index of its last occurrence. If not found, returns -1. Values are compared using IEquatable{T}.Equals(T). + /// Searches for the specified sequence and returns the index of its last occurrence. If not found, returns -1. Values are compared using IEquatable{T}.Equals(T). /// </summary> /// <param name="span">The span to search.</param> /// <param name="value">The sequence to search for.</param> @@ -539,7 +539,7 @@ namespace System } /// <summary> - /// Searches for the first index of any of the specified values similar to calling IndexOf several times with the logical OR operator. If not found, returns -1. + /// Searches for the first index of any of the specified values similar to calling IndexOf several times with the logical OR operator. If not found, returns -1. /// </summary> /// <param name="span">The span to search.</param> /// <param name="values">The set of values to search for.</param> @@ -661,7 +661,7 @@ namespace System } /// <summary> - /// Searches for the first index of any of the specified values similar to calling IndexOf several times with the logical OR operator. If not found, returns -1. + /// Searches for the first index of any of the specified values similar to calling IndexOf several times with the logical OR operator. If not found, returns -1. /// </summary> /// <param name="span">The span to search.</param> /// <param name="value0">One of the values to search for.</param> @@ -690,7 +690,7 @@ namespace System } /// <summary> - /// Searches for the first index of any of the specified values similar to calling IndexOf several times with the logical OR operator. If not found, returns -1. + /// Searches for the first index of any of the specified values similar to calling IndexOf several times with the logical OR operator. If not found, returns -1. /// </summary> /// <param name="span">The span to search.</param> /// <param name="values">The set of values to search for.</param> @@ -829,7 +829,7 @@ namespace System } /// <summary> - /// Searches for the last index of any of the specified values similar to calling LastIndexOf several times with the logical OR operator. If not found, returns -1. + /// Searches for the last index of any of the specified values similar to calling LastIndexOf several times with the logical OR operator. If not found, returns -1. /// </summary> /// <param name="span">The span to search.</param> /// <param name="values">The set of values to search for.</param> @@ -868,7 +868,7 @@ namespace System } /// <summary> - /// Searches for the last index of any of the specified values similar to calling LastIndexOf several times with the logical OR operator. If not found, returns -1. + /// Searches for the last index of any of the specified values similar to calling LastIndexOf several times with the logical OR operator. If not found, returns -1. /// </summary> /// <param name="span">The span to search.</param> /// <param name="value0">One of the values to search for.</param> @@ -890,7 +890,7 @@ namespace System } /// <summary> - /// Searches for the last index of any of the specified values similar to calling LastIndexOf several times with the logical OR operator. If not found, returns -1. + /// Searches for the last index of any of the specified values similar to calling LastIndexOf several times with the logical OR operator. If not found, returns -1. /// </summary> /// <param name="span">The span to search.</param> /// <param name="values">The set of values to search for.</param> @@ -909,7 +909,7 @@ namespace System } /// <summary> - /// Determines whether two sequences are equal by comparing the elements using IEquatable{T}.Equals(T). + /// Determines whether two sequences are equal by comparing the elements using IEquatable{T}.Equals(T). /// </summary> [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool SequenceEqual<T>(this ReadOnlySpan<T> span, ReadOnlySpan<T> other) @@ -927,7 +927,7 @@ namespace System } /// <summary> - /// Determines the relative order of the sequences being compared by comparing the elements using IComparable{T}.CompareTo(T). + /// Determines the relative order of the sequences being compared by comparing the elements using IComparable{T}.CompareTo(T). /// </summary> [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int SequenceCompareTo<T>(this ReadOnlySpan<T> span, ReadOnlySpan<T> other) @@ -1134,6 +1134,19 @@ namespace System /// <summary> /// Creates a new Span over the portion of the target array beginning + /// at 'startIndex' and ending at the end of the segment. + /// </summary> + /// <param name="segment">The target array.</param> + /// <param name="startIndex">The index at which to begin the Span.</param> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Span<T> AsSpan<T>(this ArraySegment<T> segment, Index startIndex) + { + int actualIndex = startIndex.GetOffset(segment.Count); + return AsSpan(segment, actualIndex); + } + + /// <summary> + /// Creates a new Span over the portion of the target array beginning /// at 'start' index and ending at 'end' index (exclusive). /// </summary> /// <param name="segment">The target array.</param> @@ -1156,6 +1169,18 @@ namespace System } /// <summary> + /// Creates a new Span over the portion of the target array using the range start and end indexes + /// </summary> + /// <param name="segment">The target array.</param> + /// <param name="range">The range which has start and end indexes to use for slicing the array.</param> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Span<T> AsSpan<T>(this ArraySegment<T> segment, Range range) + { + (int start, int length) = range.GetOffsetAndLength(segment.Count); + return new Span<T>(segment.Array, segment.Offset + start, length); + } + + /// <summary> /// Creates a new memory over the target array. /// </summary> public static Memory<T> AsMemory<T>(this T[] array) => new Memory<T>(array); @@ -1174,6 +1199,24 @@ namespace System public static Memory<T> AsMemory<T>(this T[] array, int start) => new Memory<T>(array, start); /// <summary> + /// Creates a new memory over the portion of the target array starting from + /// 'startIndex' to the end of the array. + /// </summary> + public static Memory<T> AsMemory<T>(this T[] array, Index startIndex) + { + if (array == null) + { + if (!startIndex.Equals(Index.Start)) + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array); + + return default; + } + + int actualIndex = startIndex.GetOffset(array.Length); + return new Memory<T>(array, actualIndex); + } + + /// <summary> /// Creates a new memory over the portion of the target array beginning /// at 'start' index and ending at 'end' index (exclusive). /// </summary> @@ -1188,6 +1231,26 @@ namespace System public static Memory<T> AsMemory<T>(this T[] array, int start, int length) => new Memory<T>(array, start, length); /// <summary> + /// Creates a new memory over the portion of the target array beginning at inclusive start index of the range + /// and ending at the exclusive end index of the range. + /// </summary> + public static Memory<T> AsMemory<T>(this T[] array, Range range) + { + if (array == null) + { + Index startIndex = range.Start; + Index endIndex = range.End; + if (!startIndex.Equals(Index.Start) || !endIndex.Equals(Index.Start)) + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array); + + return default; + } + + (int start, int length) = range.GetOffsetAndLength(array.Length); + return new Memory<T>(array, start, length); + } + + /// <summary> /// Creates a new memory over the portion of the target array. /// </summary> public static Memory<T> AsMemory<T>(this ArraySegment<T> segment) => new Memory<T>(segment.Array, segment.Offset, segment.Count); @@ -1237,7 +1300,7 @@ namespace System /// Copies the contents of the array into the span. If the source /// and destinations overlap, this method behaves as if the original values in /// a temporary location before the destination is overwritten. - /// + /// ///<param name="source">The array to copy items from.</param> /// <param name="destination">The span to copy items into.</param> /// <exception cref="System.ArgumentException"> @@ -1254,7 +1317,7 @@ namespace System /// Copies the contents of the array into the memory. If the source /// and destinations overlap, this method behaves as if the original values are in /// a temporary location before the destination is overwritten. - /// + /// ///<param name="source">The array to copy items from.</param> /// <param name="destination">The memory to copy items into.</param> /// <exception cref="System.ArgumentException"> @@ -1353,16 +1416,16 @@ namespace System // nuint x2 = xLength // nuint y1 = (nuint)Unsafe.ByteOffset(xRef, yRef) // nuint y2 = y1 + yLength - // + // // xRef relative to xRef is 0. - // + // // x2 is simply x1 + xLength. This cannot overflow. - // + // // yRef relative to xRef is (yRef - xRef). If (yRef - xRef) is // negative, casting it to an unsigned 32-bit integer turns it into // (yRef - xRef + 2³²). So, in the example above, y1 moves to the right // of x2. - // + // // y2 is simply y1 + yLength. Note that this can overflow, as in the // example above, which must be avoided. // @@ -1389,11 +1452,11 @@ namespace System // integers: // // == (y1 < xLength) || (y1 > -yLength) - // + // // Due to modulo arithmetic, this gives exactly same result *except* if // yLength is zero, since 2³² - 0 is 0 and not 2³². So the case // y.IsEmpty must be handled separately first. - // + // /// <summary> /// Determines whether two sequences overlap in memory. diff --git a/src/System.Private.CoreLib/shared/System/Range.cs b/src/System.Private.CoreLib/shared/System/Range.cs index b858da2fb4..0098dea17f 100644 --- a/src/System.Private.CoreLib/shared/System/Range.cs +++ b/src/System.Private.CoreLib/shared/System/Range.cs @@ -3,20 +3,38 @@ // See the LICENSE file in the project root for more information. using System.Diagnostics; +using System.Runtime.CompilerServices; namespace System { + /// <summary>Represent a range has start and end indexes.</summary> + /// <remarks> + /// Range is used by the C# compiler to support the range syntax. + /// <code> + /// int[] someArray = new int[5] { 1, 2, 3, 4, 5 }; + /// int[] subArray1 = someArray[0..2]; // { 1, 2 } + /// int[] subArray2 = someArray[1..^0]; // { 2, 3, 4, 5 } + /// </code> + /// </remarks> public readonly struct Range : IEquatable<Range> { + /// <summary>Represent the inclusive start index of the Range.</summary> public Index Start { get; } + + /// <summary>Represent the exclusive end index of the Range.</summary> public Index End { get; } - private Range(Index start, Index end) + /// <summary>Construct a Range object using the start and end indexes.</summary> + /// <param name="start">Represent the inclusive start index of the range.</param> + /// <param name="end">Represent the exclusive end index of the range.</param> + public Range(Index start, Index end) { Start = start; End = end; } + /// <summary>Indicates whether the current Range object is equal to another object of the same type.</summary> + /// <param name="value">An object to compare with this object</param> public override bool Equals(object value) { if (value is Range) @@ -28,20 +46,24 @@ namespace System return false; } + /// <summary>Indicates whether the current Range object is equal to another Range object.</summary> + /// <param name="other">An object to compare with this object</param> public bool Equals (Range other) => other.Start.Equals(Start) && other.End.Equals(End); + /// <summary>Returns the hash code for this instance.</summary> public override int GetHashCode() { return HashCode.Combine(Start.GetHashCode(), End.GetHashCode()); } + /// <summary>Converts the value of the current Range object to its equivalent string representation.</summary> public override string ToString() { Span<char> span = stackalloc char[2 + (2 * 11)]; // 2 for "..", then for each index 1 for '^' and 10 for longest possible uint int charsWritten; int pos = 0; - if (Start.FromEnd) + if (Start.IsFromEnd) { span[0] = '^'; pos = 1; @@ -53,7 +75,7 @@ namespace System span[pos++] = '.'; span[pos++] = '.'; - if (End.FromEnd) + if (End.IsFromEnd) { span[pos++] = '^'; } @@ -64,9 +86,63 @@ namespace System return new string(span.Slice(0, pos)); } - public static Range Create(Index start, Index end) => new Range(start, end); - public static Range FromStart(Index start) => new Range(start, new Index(0, fromEnd: true)); - public static Range ToEnd(Index end) => new Range(new Index(0, fromEnd: false), end); - public static Range All() => new Range(new Index(0, fromEnd: false), new Index(0, fromEnd: true)); + /// <summary>Create a Range object starting from start index to the end of the collection.</summary> + public static Range StartAt(Index start) => new Range(start, Index.End); + + /// <summary>Create a Range object starting from first element in the collection to the end Index.</summary> + public static Range EndAt(Index end) => new Range(Index.Start, end); + + /// <summary>Create a Range object starting from first element to the end.</summary> + public static Range All => new Range(Index.Start, Index.End); + + /// <summary>Destruct the range object according to a collection length and return the start offset from the beginning and the length of this range.</summary> + /// <param name="length">The length of the collection that the range will be used with. length has to be a positive value</param> + /// <remarks> + /// For performance reason, we don't validate the input length parameter against negative values. + /// It is expected Range will be used with collections which always have non negative length/count. + /// We validate the range is inside the length scope though. + /// </remarks> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public OffsetAndLength GetOffsetAndLength(int length) + { + int start; + Index startIndex = Start; + if (startIndex.IsFromEnd) + start = length - startIndex.Value; + else + start = startIndex.Value; + + int end; + Index endIndex = End; + if (endIndex.IsFromEnd) + end = length - endIndex.Value; + else + end = endIndex.Value; + + if ((uint)end > (uint)length || (uint)start > (uint)end) + { + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.length); + } + + return new OffsetAndLength(start, end - start); + } + + public readonly struct OffsetAndLength + { + public int Offset { get; } + public int Length { get; } + + public OffsetAndLength(int offset, int length) + { + Offset = offset; + Length = length; + } + + public void Deconstruct(out int offset, out int length) + { + offset = Offset; + length = Length; + } + } } } diff --git a/src/System.Private.CoreLib/shared/System/ReadOnlyMemory.cs b/src/System.Private.CoreLib/shared/System/ReadOnlyMemory.cs index 8fd659aeaa..6c598430ad 100644 --- a/src/System.Private.CoreLib/shared/System/ReadOnlyMemory.cs +++ b/src/System.Private.CoreLib/shared/System/ReadOnlyMemory.cs @@ -188,6 +188,35 @@ namespace System } /// <summary> + /// Forms a slice out of the given memory, beginning at 'startIndex' + /// </summary> + /// <param name="startIndex">The index at which to begin this slice.</param> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ReadOnlyMemory<T> Slice(Index startIndex) + { + int actualIndex = startIndex.GetOffset(_length); + return Slice(actualIndex); + } + + /// <summary> + /// Forms a slice out of the given memory using the range start and end indexes. + /// </summary> + /// <param name="range">The range used to slice the memory using its start and end indexes.</param> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ReadOnlyMemory<T> Slice(Range range) + { + (int start, int length) = range.GetOffsetAndLength(_length); + // It is expected for _index + start to be negative if the memory is already pre-pinned. + return new ReadOnlyMemory<T>(_object, _index + start, length); + } + + /// <summary> + /// Forms a slice out of the given memory using the range start and end indexes. + /// </summary> + /// <param name="range">The range used to slice the memory using its start and end indexes.</param> + public ReadOnlyMemory<T> this[Range range] => Slice(range); + + /// <summary> /// Returns a span from the memory. /// </summary> public unsafe ReadOnlySpan<T> Span @@ -386,7 +415,7 @@ namespace System // code is based on object identity and referential equality, not deep equality (as common with string). return (_object != null) ? HashCode.Combine(RuntimeHelpers.GetHashCode(_object), _index, _length) : 0; } - + /// <summary>Gets the state of the memory as individual fields.</summary> /// <param name="start">The offset.</param> /// <param name="length">The count.</param> diff --git a/src/System.Private.CoreLib/shared/System/ReadOnlySpan.Fast.cs b/src/System.Private.CoreLib/shared/System/ReadOnlySpan.Fast.cs index b2ce53be2c..eb3fd1464d 100644 --- a/src/System.Private.CoreLib/shared/System/ReadOnlySpan.Fast.cs +++ b/src/System.Private.CoreLib/shared/System/ReadOnlySpan.Fast.cs @@ -157,20 +157,12 @@ namespace System get { // Evaluate the actual index first because it helps performance - int actualIndex = index.FromEnd ? _length - index.Value : index.Value; + int actualIndex = index.GetOffset(_length); return ref this [actualIndex]; } } - public ReadOnlySpan<T> this[Range range] - { - get - { - int start = range.Start.FromEnd ? _length - range.Start.Value : range.Start.Value; - int end = range.End.FromEnd ? _length - range.End.Value : range.End.Value; - return Slice(start, end - start); - } - } + public ReadOnlySpan<T> this[Range range] => Slice(range); /// <summary> /// Returns a reference to the 0th element of the Span. If the Span is empty, returns null reference. @@ -297,6 +289,33 @@ namespace System } /// <summary> + /// Forms a slice out of the given read-only span, beginning at 'startIndex' + /// </summary> + /// <param name="startIndex">The index at which to begin this slice.</param> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ReadOnlySpan<T> Slice(Index startIndex) + { + int actualIndex; + if (startIndex.IsFromEnd) + actualIndex = _length - startIndex.Value; + else + actualIndex = startIndex.Value; + + return Slice(actualIndex); + } + + /// <summary> + /// Forms a slice out of the given read-only span, beginning at range start index to the range end + /// </summary> + /// <param name="range">The range which has the start and end indexes used to slice the span.</param> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ReadOnlySpan<T> Slice(Range range) + { + (int start, int length) = range.GetOffsetAndLength(_length); + return new ReadOnlySpan<T>(ref Unsafe.Add(ref _pointer.Value, start), length); + } + + /// <summary> /// Copies the contents of this read-only span into a new array. This heap /// allocates, so should generally be avoided, however it is sometimes /// necessary to bridge the gap with APIs written in terms of arrays. diff --git a/src/System.Private.CoreLib/shared/System/Span.Fast.cs b/src/System.Private.CoreLib/shared/System/Span.Fast.cs index 490767dc53..66de4fe3d3 100644 --- a/src/System.Private.CoreLib/shared/System/Span.Fast.cs +++ b/src/System.Private.CoreLib/shared/System/Span.Fast.cs @@ -163,20 +163,12 @@ namespace System get { // Evaluate the actual index first because it helps performance - int actualIndex = index.FromEnd ? _length - index.Value : index.Value; + int actualIndex = index.GetOffset(_length); return ref this [actualIndex]; } } - public Span<T> this[Range range] - { - get - { - int start = range.Start.FromEnd ? _length - range.Start.Value : range.Start.Value; - int end = range.End.FromEnd ? _length - range.End.Value : range.End.Value; - return Slice(start, end - start); - } - } + public Span<T> this[Range range] => Slice(range); /// <summary> /// Returns a reference to the 0th element of the Span. If the Span is empty, returns null reference. @@ -381,6 +373,28 @@ namespace System } /// <summary> + /// Forms a slice out of the given span, beginning at 'startIndex' + /// </summary> + /// <param name="startIndex">The index at which to begin this slice.</param> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Span<T> Slice(Index startIndex) + { + int actualIndex = startIndex.GetOffset(_length); + return Slice(actualIndex); + } + + /// <summary> + /// Forms a slice out of the given span, beginning at range start index to the range end + /// </summary> + /// <param name="range">The range which has the start and end indexes used to slice the span.</param> + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public Span<T> Slice(Range range) + { + (int start, int length) = range.GetOffsetAndLength(_length); + return new Span<T>(ref Unsafe.Add(ref _pointer.Value, start), length); + } + + /// <summary> /// Copies the contents of this span into a new array. This heap /// allocates, so should generally be avoided, however it is sometimes /// necessary to bridge the gap with APIs written in terms of arrays. diff --git a/src/System.Private.CoreLib/shared/System/String.Manipulation.cs b/src/System.Private.CoreLib/shared/System/String.Manipulation.cs index f183ed6a59..82d74225c2 100644 --- a/src/System.Private.CoreLib/shared/System/String.Manipulation.cs +++ b/src/System.Private.CoreLib/shared/System/String.Manipulation.cs @@ -1686,6 +1686,20 @@ namespace System return InternalSubString(startIndex, length); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public string Substring(Index startIndex) + { + int actualIndex = startIndex.GetOffset(Length); + return Substring(actualIndex); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public string Substring(Range range) + { + (int start, int length) = range.GetOffsetAndLength(Length); + return Substring(start, length); + } + private unsafe string InternalSubString(int startIndex, int length) { Debug.Assert(startIndex >= 0 && startIndex <= this.Length, "StartIndex is out of range!"); diff --git a/src/System.Private.CoreLib/shared/System/String.cs b/src/System.Private.CoreLib/shared/System/String.cs index 0958e865db..22f830a0e4 100644 --- a/src/System.Private.CoreLib/shared/System/String.cs +++ b/src/System.Private.CoreLib/shared/System/String.cs @@ -444,6 +444,19 @@ namespace System return (value == null || 0u >= (uint)value.Length) ? true : false; } + [System.Runtime.CompilerServices.IndexerName("Chars")] + public char this[Index index] + { + get + { + int actualIndex = index.GetOffset(Length); + return this[actualIndex]; + } + } + + [System.Runtime.CompilerServices.IndexerName("Chars")] + public string this[Range range] => Substring(range); + public static bool IsNullOrWhiteSpace(string value) { if (value == null) return true; @@ -672,7 +685,7 @@ namespace System // // IConvertible implementation - // + // public TypeCode GetTypeCode() { diff --git a/src/System.Private.CoreLib/shared/System/ThrowHelper.cs b/src/System.Private.CoreLib/shared/System/ThrowHelper.cs index b9276ca8a0..1bbc7e3f63 100644 --- a/src/System.Private.CoreLib/shared/System/ThrowHelper.cs +++ b/src/System.Private.CoreLib/shared/System/ThrowHelper.cs @@ -119,7 +119,7 @@ namespace System internal static void ThrowArgumentOutOfRangeException_ArgumentOutOfRange_Enum() { - throw GetArgumentOutOfRangeException(ExceptionArgument.type, + throw GetArgumentOutOfRangeException(ExceptionArgument.type, ExceptionResource.ArgumentOutOfRange_Enum); } diff --git a/src/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.cs b/src/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.cs index fe101e2740..94a6379f5f 100644 --- a/src/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.cs +++ b/src/System.Private.CoreLib/src/System/Runtime/CompilerServices/RuntimeHelpers.cs @@ -39,14 +39,14 @@ namespace System.Runtime.CompilerServices // GetObjectValue is intended to allow value classes to be manipulated as 'Object' // but have aliasing behavior of a value class. The intent is that you would use // this function just before an assignment to a variable of type 'Object'. If the - // value being assigned is a mutable value class, then a shallow copy is returned + // value being assigned is a mutable value class, then a shallow copy is returned // (because value classes have copy semantics), but otherwise the object itself - // is returned. + // is returned. // // Note: VB calls this method when they're about to assign to an Object - // or pass it as a parameter. The goal is to make sure that boxed - // value types work identical to unboxed value types - ie, they get - // cloned when you pass them around, and are always passed by value. + // or pass it as a parameter. The goal is to make sure that boxed + // value types work identical to unboxed value types - ie, they get + // cloned when you pass them around, and are always passed by value. // Of course, reference types are not cloned. // [MethodImplAttribute(MethodImplOptions.InternalCall)] @@ -57,8 +57,8 @@ namespace System.Runtime.CompilerServices // have at least been started by some thread. In the absence of class constructor // deadlock conditions, the call is further guaranteed to have completed. // - // This call will generate an exception if the specified class constructor threw an - // exception when it ran. + // This call will generate an exception if the specified class constructor threw an + // exception when it ran. [MethodImplAttribute(MethodImplOptions.InternalCall)] private static extern void _RunClassConstructor(RuntimeType type); @@ -73,8 +73,8 @@ namespace System.Runtime.CompilerServices // have at least been started by some thread. In the absence of module constructor // deadlock conditions, the call is further guaranteed to have completed. // - // This call will generate an exception if the specified module constructor threw an - // exception when it ran. + // This call will generate an exception if the specified module constructor threw an + // exception when it ran. [MethodImplAttribute(MethodImplOptions.InternalCall)] private static extern void _RunModuleConstructor(System.Reflection.RuntimeModule module); @@ -91,7 +91,7 @@ namespace System.Runtime.CompilerServices [MethodImplAttribute(MethodImplOptions.InternalCall)] private static extern unsafe void _PrepareMethod(IRuntimeMethodInfo method, IntPtr* pInstantiation, int cInstantiation); - public static void PrepareMethod(RuntimeMethodHandle method) + public static void PrepareMethod(RuntimeMethodHandle method) { unsafe { @@ -132,10 +132,10 @@ namespace System.Runtime.CompilerServices get { // Number of bytes from the address pointed to by a reference to - // a String to the first 16-bit character in the String. Skip - // over the MethodTable pointer, & String - // length. Of course, the String reference points to the memory - // after the sync block, so don't count that. + // a String to the first 16-bit character in the String. Skip + // over the MethodTable pointer, & String + // length. Of course, the String reference points to the memory + // after the sync block, so don't count that. // This property allows C#'s fixed statement to work on Strings. // On 64 bit platforms, this should be 12 (8+4) and on 32 bit 8 (4+4). #if BIT64 @@ -197,6 +197,26 @@ namespace System.Runtime.CompilerServices throw new InvalidOperationException(); } + /// <summary> + /// GetSubArray helper method for the compiler to slice an array using a range. + /// </summary> + public static T[] GetSubArray<T>(T[] array, Range range) + { + Type elementType = array.GetType().GetElementType(); + Span<T> source = array.AsSpan(range); + + if (elementType.IsValueType) + { + return source.ToArray(); + } + else + { + T[] newArray = (T[])Array.CreateInstance(elementType, source.Length); + source.CopyTo(newArray); + return newArray; + } + } + // Returns true iff the object has a component size; // i.e., is variable length like System.String or Array. [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -228,7 +248,7 @@ namespace System.Runtime.CompilerServices // This is not ideal in terms of minimizing instruction count but is the best we can do at the moment. return Unsafe.Add(ref Unsafe.As<byte, IntPtr>(ref obj.GetRawData()), -1); - + // The JIT currently implements this as: // lea tmp, [rax + 8h] ; assume rax contains the object reference, tmp is type IntPtr& // mov tmp, qword ptr [tmp - 8h] ; tmp now contains the MethodTable* pointer diff --git a/tests/CoreFX/CoreFX.issues.json b/tests/CoreFX/CoreFX.issues.json index c249ffa259..c76dbe762b 100644 --- a/tests/CoreFX/CoreFX.issues.json +++ b/tests/CoreFX/CoreFX.issues.json @@ -1054,7 +1054,6 @@ "name": "System.Tests.TimeSpanTests.Parse", "reason": "Temporary disabling till merging the PR https://github.com/dotnet/corefx/pull/34561" }, - { "name": "System.Tests.SingleTests.Test_ToString", "reason" : "https://github.com/dotnet/coreclr/pull/22040" diff --git a/tests/issues.targets b/tests/issues.targets index 022f1968c4..ff87ba76d0 100644 --- a/tests/issues.targets +++ b/tests/issues.targets @@ -47,6 +47,9 @@ <ExcludeList Include="$(XunitTestBinBase)/JIT/Regression/JitBlue/GitHub_11408/GitHub_11408/*"> <Issue>11408</Issue> </ExcludeList> + <ExcludeList Include="$(XunitTestBinBase)/JIT/Regression/JitBlue/GitHub_20958/GitHub_20958/*"> + <Issue>22410</Issue> + </ExcludeList> <ExcludeList Include="$(XunitTestBinBase)/baseservices/exceptions/StackTracePreserve/StackTracePreserveTests/*"> <Issue>20322</Issue> </ExcludeList> @@ -90,22 +93,22 @@ </ExcludeList> <ExcludeList Include="$(XunitTestBinBase)/JIT/Methodical/tailcall_v4/hijacking/*"> <Issue>needs triage</Issue> - </ExcludeList> + </ExcludeList> <ExcludeList Include="$(XunitTestBinBase)/JIT/Methodical/xxobj/sizeof/_il_dbgsizeof/*"> <Issue>needs triage</Issue> - </ExcludeList> + </ExcludeList> <ExcludeList Include="$(XunitTestBinBase)/JIT/Methodical/xxobj/sizeof/_il_dbgsizeof32/*"> <Issue>needs triage</Issue> </ExcludeList> <ExcludeList Include="$(XunitTestBinBase)/JIT/Methodical/xxobj/sizeof/_il_dbgsizeof64/*"> <Issue>needs triage</Issue> - </ExcludeList> + </ExcludeList> <ExcludeList Include="$(XunitTestBinBase)/JIT/Methodical/xxobj/sizeof/_il_relsizeof/*"> <Issue>needs triage</Issue> </ExcludeList> <ExcludeList Include="$(XunitTestBinBase)/JIT/Methodical/xxobj/sizeof/_il_relsizeof32/*"> <Issue>needs triage</Issue> - </ExcludeList> + </ExcludeList> <ExcludeList Include="$(XunitTestBinBase)/JIT/Methodical/xxobj/sizeof/_il_relsizeof64/*"> <Issue>needs triage</Issue> </ExcludeList> @@ -211,22 +214,22 @@ </ExcludeList> <ExcludeList Include="$(XunitTestBinBase)/JIT/jit64/opt/cse/HugeArray/*"> <Issue>needs triage</Issue> - </ExcludeList> + </ExcludeList> <ExcludeList Include="$(XunitTestBinBase)/JIT/jit64/opt/cse/HugeArray1/*"> <Issue>needs triage</Issue> - </ExcludeList> + </ExcludeList> <ExcludeList Include="$(XunitTestBinBase)/JIT/jit64/opt/cse/HugeField1/*"> <Issue>needs triage</Issue> </ExcludeList> <ExcludeList Include="$(XunitTestBinBase)/JIT/jit64/opt/cse/HugeField2/*"> <Issue>needs triage</Issue> - </ExcludeList> + </ExcludeList> <ExcludeList Include="$(XunitTestBinBase)/CoreMangLib/cti/system/string/StringFormat1/*"> <Issue>needs triage</Issue> </ExcludeList> <ExcludeList Include="$(XunitTestBinBase)/CoreMangLib/cti/system/string/StringFormat2/*"> <Issue>needs triage</Issue> - </ExcludeList> + </ExcludeList> <ExcludeList Include="$(XunitTestBinBase)/JIT/HardwareIntrinsics/Arm64/Simd/*"> <Issue>18895</Issue> </ExcludeList> @@ -293,7 +296,7 @@ <Issue>2420. x86 JIT doesn't support implicit tail call optimization or tail. call pop ret sequence</Issue> </ExcludeList> <ExcludeList Include="$(XunitTestBinBase)/JIT/Regression/JitBlue/DevDiv_255294/DevDiv_255294/*"> - <Issue>11469, The test causes OutOfMemory exception in crossgen mode.</Issue> + <Issue>11469, The test causes OutOfMemory exception in crossgen mode.</Issue> </ExcludeList> <ExcludeList Include="$(XunitTestBinBase)/baseservices/varargs/varargsupport/*"> <Issue>Varargs supported on this platform</Issue> @@ -596,7 +599,7 @@ <Issue>22015</Issue> </ExcludeList> </ItemGroup> - + <!-- Tests that need to be triaged for vararg usage as that is not supported --> <!-- Note these will only be excluded for unix platforms --> @@ -2072,7 +2075,7 @@ </ItemGroup> <!-- Failures while testing via ILLINK --> - + <ItemGroup Condition="'$(XunitTestBinBase)' != '' and '$(RunTestsViaIllink)' == 'true'"> <ExcludeList Include="$(XunitTestBinBase)/JIT/superpmi/superpmicollect/*"> <!-- Linker runs reset CORE_ROOT to the linked directory based on superpmicollect.exe. diff --git a/tests/src/JIT/Regression/JitBlue/GitHub_20958/GitHub_20958.cs b/tests/src/JIT/Regression/JitBlue/GitHub_20958/GitHub_20958.cs index 74ea90e0b3..cf5a4e318d 100644 --- a/tests/src/JIT/Regression/JitBlue/GitHub_20958/GitHub_20958.cs +++ b/tests/src/JIT/Regression/JitBlue/GitHub_20958/GitHub_20958.cs @@ -14,7 +14,7 @@ public class GitHub_20958 public static int IndexerWithRangeTest() { int returnVal = 100; - + ReadOnlySpan<char> span = "Hello".AsSpan(); ReadOnlySpan<char> sliced = span[Range.Create(new Index(1, fromEnd: false), new Index(1, fromEnd: true))]; if (span.Slice(1, 3) != sliced) |