Add MemoryExtensions.AsSpan(string, Index/Range) (#82794)
authorStephen Toub <stoub@microsoft.com>
Thu, 2 Mar 2023 14:22:24 +0000 (09:22 -0500)
committerGitHub <noreply@github.com>
Thu, 2 Mar 2023 14:22:24 +0000 (09:22 -0500)
src/libraries/System.Memory/ref/System.Memory.cs
src/libraries/System.Memory/tests/ReadOnlySpan/AsSpan.cs
src/libraries/System.Net.HttpListener/src/System/Net/Managed/HttpListenerRequest.Managed.cs
src/libraries/System.Private.CoreLib/src/System/MemoryExtensions.cs

index 32e28be4e9d2f7ff4cd96331e5151dac6c0beba3..ec71b26d9bef8a26fc3b319a674a8fc2b54d657c 100644 (file)
@@ -208,6 +208,8 @@ namespace System
         public static System.ReadOnlySpan<char> AsSpan(this string? text) { throw null; }
         public static System.ReadOnlySpan<char> AsSpan(this string? text, int start) { throw null; }
         public static System.ReadOnlySpan<char> AsSpan(this string? text, int start, int length) { throw null; }
+        public static System.ReadOnlySpan<char> AsSpan(this string? text, System.Index startIndex) { throw null; }
+        public static System.ReadOnlySpan<char> AsSpan(this string? text, System.Range range) { throw null; }
         public static System.Span<T> AsSpan<T>(this System.ArraySegment<T> segment) { throw null; }
         public static System.Span<T> AsSpan<T>(this System.ArraySegment<T> segment, System.Index startIndex) { throw null; }
         public static System.Span<T> AsSpan<T>(this System.ArraySegment<T> segment, int start) { throw null; }
index ab4ac6e1d6d4b489b082a5f6e92f0f80956c2ac6..d6070d47c3ba99bd2a44a98161939603e3283eaa 100644 (file)
@@ -55,36 +55,46 @@ namespace System.SpanTests
             Assert.Throws<ArgumentOutOfRangeException>(() => str.AsSpan(1, 0).DontBox());
             Assert.Throws<ArgumentOutOfRangeException>(() => str.AsSpan(1, 1).DontBox());
             Assert.Throws<ArgumentOutOfRangeException>(() => str.AsSpan(-1, -1).DontBox());
+
+            Assert.Throws<ArgumentOutOfRangeException>(() => str.AsSpan(new Index(1)).DontBox());
+            Assert.Throws<ArgumentOutOfRangeException>(() => str.AsSpan(new Index(0, fromEnd: true)).DontBox());
+
+            Assert.Throws<ArgumentNullException>(() => str.AsSpan(0..1).DontBox());
+            Assert.Throws<ArgumentNullException>(() => str.AsSpan(new Range(new Index(0), new Index(0, fromEnd: true))).DontBox());
+            Assert.Throws<ArgumentNullException>(() => str.AsSpan(new Range(new Index(0, fromEnd: true), new Index(0))).DontBox());
+            Assert.Throws<ArgumentNullException>(() => 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<char> 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<char> 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 @@ namespace System.SpanTests
         public static unsafe void AsSpan_2Arg_OutOfRange(string text, int start)
         {
             AssertExtensions.Throws<ArgumentOutOfRangeException>("start", () => text.AsSpan(start).DontBox());
+            if (start >= 0)
+            {
+                AssertExtensions.Throws<ArgumentOutOfRangeException>("startIndex", () => text.AsSpan(new Index(start)).DontBox());
+            }
         }
 
         [Theory]
@@ -100,6 +114,10 @@ namespace System.SpanTests
         public static unsafe void AsSpan_3Arg_OutOfRange(string text, int start, int length)
         {
             AssertExtensions.Throws<ArgumentOutOfRangeException>("start", () => text.AsSpan(start, length).DontBox());
+            if (start >= 0 && length >= 0 && start + length >= 0)
+            {
+                AssertExtensions.Throws<ArgumentOutOfRangeException>("length", () => text.AsSpan(start..(start + length)).DontBox());
+            }
         }
     }
 }
index f8e5897fec82d0d661bda70eaaf4e02f8cc1ea63..3a872b717b08afb170b83e66a7af015a264ecbb8 100644 (file)
@@ -92,7 +92,7 @@ namespace System.Net
 
             _rawUrl = req[parts[1]];
 
-            ReadOnlySpan<char> version = req.AsSpan()[parts[2]];
+            ReadOnlySpan<char> version = req.AsSpan(parts[2]);
             if (version.Length != 8 || !version.StartsWith("HTTP/", StringComparison.Ordinal))
             {
                 _context.ErrorMessage = "Invalid request line (version).";
index a718a1461d91e7849cfe6ffdba9e4b4b0c755670..402bdfc88f9155c0a57b7c45e180f746e385e308 100644 (file)
@@ -127,6 +127,58 @@ namespace System
             return new ReadOnlySpan<char>(ref Unsafe.Add(ref text.GetRawStringData(), (nint)(uint)start /* force zero-extension */), text.Length - start);
         }
 
+        /// <summary>Creates a new <see cref="ReadOnlySpan{Char}"/> over a portion of the target string from a specified position to the end of the string.</summary>
+        /// <param name="text">The target string.</param>
+        /// <param name="startIndex">The index at which to begin this slice.</param>
+        /// <exception cref="ArgumentOutOfRangeException"><paramref name="startIndex"/> is less than 0 or greater than <paramref name="text"/>.Length.</exception>
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public static ReadOnlySpan<char> 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<char>(ref Unsafe.Add(ref text.GetRawStringData(), (nint)(uint)actualIndex /* force zero-extension */), text.Length - actualIndex);
+        }
+
+        /// <summary>Creates a new <see cref="ReadOnlySpan{Char}"/> over a portion of a target string using the range start and end indexes.</summary>
+        /// <param name="text">The target string.</param>
+        /// <param name="range">The range which has start and end indexes to use for slicing the string.</param>
+        /// <exception cref="ArgumentNullException"><paramref name="text"/> is null.</exception>
+        /// <exception cref="ArgumentOutOfRangeException"><paramref name="range"/>'s start or end index is not within the bounds of the string.</exception>
+        /// <exception cref="ArgumentOutOfRangeException"><paramref name="range"/>'s start index is greater than its end index.</exception>
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        public static ReadOnlySpan<char> 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<char>(ref Unsafe.Add(ref text.GetRawStringData(), (nint)(uint)start /* force zero-extension */), length);
+        }
+
         /// <summary>
         /// Creates a new readonly span over the portion of the target string.
         /// </summary>