diff --git a/src/libraries/System.Memory/ref/System.Memory.cs b/src/libraries/System.Memory/ref/System.Memory.cs index 32e28be4e9d2f7..ec71b26d9bef8a 100644 --- a/src/libraries/System.Memory/ref/System.Memory.cs +++ b/src/libraries/System.Memory/ref/System.Memory.cs @@ -208,6 +208,8 @@ public static partial class MemoryExtensions public static System.ReadOnlySpan AsSpan(this string? text) { throw null; } public static System.ReadOnlySpan AsSpan(this string? text, int start) { throw null; } public static System.ReadOnlySpan AsSpan(this string? text, int start, int length) { throw null; } + public static System.ReadOnlySpan AsSpan(this string? text, System.Index startIndex) { throw null; } + public static System.ReadOnlySpan AsSpan(this string? text, System.Range range) { throw null; } public static System.Span AsSpan(this System.ArraySegment segment) { throw null; } public static System.Span AsSpan(this System.ArraySegment segment, System.Index startIndex) { throw null; } public static System.Span AsSpan(this System.ArraySegment segment, int start) { throw null; } diff --git a/src/libraries/System.Memory/tests/ReadOnlySpan/AsSpan.cs b/src/libraries/System.Memory/tests/ReadOnlySpan/AsSpan.cs index ab4ac6e1d6d4b4..d6070d47c3ba99 100644 --- a/src/libraries/System.Memory/tests/ReadOnlySpan/AsSpan.cs +++ b/src/libraries/System.Memory/tests/ReadOnlySpan/AsSpan.cs @@ -55,36 +55,46 @@ public static void StringAsSpanNullNonZeroStartAndLength() Assert.Throws(() => str.AsSpan(1, 0).DontBox()); Assert.Throws(() => str.AsSpan(1, 1).DontBox()); Assert.Throws(() => str.AsSpan(-1, -1).DontBox()); + + Assert.Throws(() => str.AsSpan(new Index(1)).DontBox()); + Assert.Throws(() => str.AsSpan(new Index(0, fromEnd: true)).DontBox()); + + Assert.Throws(() => str.AsSpan(0..1).DontBox()); + Assert.Throws(() => str.AsSpan(new Range(new Index(0), new Index(0, fromEnd: true))).DontBox()); + Assert.Throws(() => str.AsSpan(new Range(new Index(0, fromEnd: true), new Index(0))).DontBox()); + Assert.Throws(() => str.AsSpan(new Range(new Index(0, fromEnd: true), new Index(0, fromEnd: true))).DontBox()); } [Theory] [MemberData(nameof(TestHelpers.StringSliceTestData), MemberType = typeof(TestHelpers))] - public static unsafe void AsSpan_StartAndLength(string text, int start, int length) + public static void AsSpan_StartAndLength(string text, int start, int length) { - ReadOnlySpan span; if (start == -1) { - start = 0; - length = text.Length; - span = text.AsSpan(); + Validate(text, 0, text.Length, text.AsSpan()); + Validate(text, 0, text.Length, text.AsSpan(0)); + Validate(text, 0, text.Length, text.AsSpan(0..^0)); } else if (length == -1) { - length = text.Length - start; - span = text.AsSpan(start); + Validate(text, start, text.Length - start, text.AsSpan(start)); + Validate(text, start, text.Length - start, text.AsSpan(start..)); } else { - span = text.AsSpan(start, length); + Validate(text, start, length, text.AsSpan(start, length)); + Validate(text, start, length, text.AsSpan(start..(start+length))); } - Assert.Equal(length, span.Length); - - fixed (char* pText = text) + static unsafe void Validate(string text, int start, int length, ReadOnlySpan span) { - char* expected = pText + start; - void* actual = Unsafe.AsPointer(ref MemoryMarshal.GetReference(span)); - Assert.Equal((IntPtr)expected, (IntPtr)actual); + Assert.Equal(length, span.Length); + fixed (char* pText = text) + { + char* expected = pText + start; + void* actual = Unsafe.AsPointer(ref MemoryMarshal.GetReference(span)); + Assert.Equal((IntPtr)expected, (IntPtr)actual); + } } } @@ -93,6 +103,10 @@ public static unsafe void AsSpan_StartAndLength(string text, int start, int leng public static unsafe void AsSpan_2Arg_OutOfRange(string text, int start) { AssertExtensions.Throws("start", () => text.AsSpan(start).DontBox()); + if (start >= 0) + { + AssertExtensions.Throws("startIndex", () => text.AsSpan(new Index(start)).DontBox()); + } } [Theory] @@ -100,6 +114,10 @@ public static unsafe void AsSpan_2Arg_OutOfRange(string text, int start) public static unsafe void AsSpan_3Arg_OutOfRange(string text, int start, int length) { AssertExtensions.Throws("start", () => text.AsSpan(start, length).DontBox()); + if (start >= 0 && length >= 0 && start + length >= 0) + { + AssertExtensions.Throws("length", () => text.AsSpan(start..(start + length)).DontBox()); + } } } } diff --git a/src/libraries/System.Net.HttpListener/src/System/Net/Managed/HttpListenerRequest.Managed.cs b/src/libraries/System.Net.HttpListener/src/System/Net/Managed/HttpListenerRequest.Managed.cs index f8e5897fec82d0..3a872b717b08af 100644 --- a/src/libraries/System.Net.HttpListener/src/System/Net/Managed/HttpListenerRequest.Managed.cs +++ b/src/libraries/System.Net.HttpListener/src/System/Net/Managed/HttpListenerRequest.Managed.cs @@ -92,7 +92,7 @@ internal void SetRequestLine(string req) _rawUrl = req[parts[1]]; - ReadOnlySpan version = req.AsSpan()[parts[2]]; + ReadOnlySpan version = req.AsSpan(parts[2]); if (version.Length != 8 || !version.StartsWith("HTTP/", StringComparison.Ordinal)) { _context.ErrorMessage = "Invalid request line (version)."; diff --git a/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.cs b/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.cs index a718a1461d91e7..402bdfc88f9155 100644 --- a/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.cs +++ b/src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.cs @@ -127,6 +127,58 @@ public static ReadOnlySpan AsSpan(this string? text, int start) return new ReadOnlySpan(ref Unsafe.Add(ref text.GetRawStringData(), (nint)(uint)start /* force zero-extension */), text.Length - start); } + /// Creates a new over a portion of the target string from a specified position to the end of the string. + /// The target string. + /// The index at which to begin this slice. + /// is less than 0 or greater than .Length. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ReadOnlySpan AsSpan(this string? text, Index startIndex) + { + if (text is null) + { + if (!startIndex.Equals(Index.Start)) + { + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.startIndex); + } + + return default; + } + + int actualIndex = startIndex.GetOffset(text.Length); + if ((uint)actualIndex > (uint)text.Length) + { + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.startIndex); + } + + return new ReadOnlySpan(ref Unsafe.Add(ref text.GetRawStringData(), (nint)(uint)actualIndex /* force zero-extension */), text.Length - actualIndex); + } + + /// Creates a new over a portion of a target string using the range start and end indexes. + /// The target string. + /// The range which has start and end indexes to use for slicing the string. + /// is null. + /// 's start or end index is not within the bounds of the string. + /// 's start index is greater than its end index. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ReadOnlySpan AsSpan(this string? text, Range range) + { + if (text is 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 ReadOnlySpan(ref Unsafe.Add(ref text.GetRawStringData(), (nint)(uint)start /* force zero-extension */), length); + } + /// /// Creates a new readonly span over the portion of the target string. ///