Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Span.StartsWith(T) and EndsWith(T) #103458

Merged
merged 4 commits into from
Jun 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/libraries/Common/src/System/Net/CookieComparer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ internal static bool Equals(Cookie left, Cookie right)

internal static bool EqualDomains(ReadOnlySpan<char> left, ReadOnlySpan<char> right)
{
if (left.Length != 0 && left[0] == '.') left = left.Slice(1);
if (right.Length != 0 && right[0] == '.') right = right.Slice(1);
if (left.StartsWith('.')) left = left.Slice(1);
if (right.StartsWith('.')) right = right.Slice(1);

return left.Equals(right, StringComparison.OrdinalIgnoreCase);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -674,7 +674,7 @@ private static void BuildCommandLine(ProcessStartInfo startInfo, ref ValueString
// problems (it specifies exactly which part of the string
// is the file to execute).
ReadOnlySpan<char> fileName = startInfo.FileName.AsSpan().Trim();
bool fileNameIsQuoted = fileName.Length > 0 && fileName[0] == '\"' && fileName[fileName.Length - 1] == '\"';
bool fileNameIsQuoted = fileName.StartsWith('"') && fileName.EndsWith('"');
stephentoub marked this conversation as resolved.
Show resolved Hide resolved
if (!fileNameIsQuoted)
{
commandLine.Append('"');
Expand Down
2 changes: 2 additions & 0 deletions src/libraries/System.Memory/ref/System.Memory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -268,6 +268,7 @@ public static void CopyTo<T>(this T[]? source, System.Span<T> destination) { }
public static bool EndsWith(this System.ReadOnlySpan<char> span, System.ReadOnlySpan<char> value, System.StringComparison comparisonType) { throw null; }
public static bool EndsWith<T>(this System.ReadOnlySpan<T> span, System.ReadOnlySpan<T> value) where T : System.IEquatable<T>? { throw null; }
public static bool EndsWith<T>(this System.Span<T> span, System.ReadOnlySpan<T> value) where T : System.IEquatable<T>? { throw null; }
public static bool EndsWith<T>(this System.ReadOnlySpan<T> span, T value) where T : System.IEquatable<T>? { throw null; }
public static System.Text.SpanLineEnumerator EnumerateLines(this System.ReadOnlySpan<char> span) { throw null; }
public static System.Text.SpanLineEnumerator EnumerateLines(this System.Span<char> span) { throw null; }
public static System.Text.SpanRuneEnumerator EnumerateRunes(this System.ReadOnlySpan<char> span) { throw null; }
Expand Down Expand Up @@ -356,6 +357,7 @@ public static void Sort<TKey, TValue, TComparer>(this System.Span<TKey> keys, Sy
public static bool StartsWith(this System.ReadOnlySpan<char> span, System.ReadOnlySpan<char> value, System.StringComparison comparisonType) { throw null; }
public static bool StartsWith<T>(this System.ReadOnlySpan<T> span, System.ReadOnlySpan<T> value) where T : System.IEquatable<T>? { throw null; }
public static bool StartsWith<T>(this System.Span<T> span, System.ReadOnlySpan<T> value) where T : System.IEquatable<T>? { throw null; }
public static bool StartsWith<T>(this System.ReadOnlySpan<T> span, T value) where T : System.IEquatable<T>? { throw null; }
public static int ToLower(this System.ReadOnlySpan<char> source, System.Span<char> destination, System.Globalization.CultureInfo? culture) { throw null; }
public static int ToLowerInvariant(this System.ReadOnlySpan<char> source, System.Span<char> destination) { throw null; }
public static int ToUpper(this System.ReadOnlySpan<char> source, System.Span<char> destination, System.Globalization.CultureInfo? culture) { throw null; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ private bool TryReadToSlow(out ReadOnlySequence<T> sequence, T delimiter, T deli
else
{
// No delimiter, need to check the end of the span for odd number of escapes then advance
if (remaining.Length > 0 && remaining[remaining.Length - 1].Equals(delimiterEscape))
if (remaining.EndsWith(delimiterEscape))
{
int escapeCount = 1;
int i = remaining.Length - 2;
Expand Down
29 changes: 29 additions & 0 deletions src/libraries/System.Memory/tests/Span/EndsWith.T.cs
Original file line number Diff line number Diff line change
Expand Up @@ -88,5 +88,34 @@ public static void OnEndsWithOfEqualSpansMakeSureEveryElementIsCompared()
}
}
}

[Fact]
public static void EndsWithSingle()
{
ReadOnlySpan<char> chars = [];
Assert.False(chars.EndsWith('\0'));
Assert.False(chars.EndsWith('f'));

chars = "foo";
Assert.True(chars.EndsWith(chars[^1]));
Assert.True(chars.EndsWith('o'));
Assert.False(chars.EndsWith('f'));

scoped ReadOnlySpan<string> strings = [];
Assert.False(strings.EndsWith((string)null));
Assert.False(strings.EndsWith("foo"));

strings = ["foo", "bar"];
Assert.True(strings.EndsWith(strings[^1]));
Assert.True(strings.EndsWith("bar"));
Assert.True(strings.EndsWith("*bar".Substring(1)));
Assert.False(strings.EndsWith("foo"));
Assert.False(strings.EndsWith((string)null));

strings = ["foo", null];
Assert.True(strings.EndsWith(strings[^1]));
Assert.True(strings.EndsWith((string)null));
Assert.False(strings.EndsWith("foo"));
}
}
}
29 changes: 29 additions & 0 deletions src/libraries/System.Memory/tests/Span/StartsWith.T.cs
Original file line number Diff line number Diff line change
Expand Up @@ -150,5 +150,34 @@ public static void MakeSureNoStartsWithChecksGoOutOfRange()
Assert.True(b);
}
}

[Fact]
public static void StartsWithSingle()
{
ReadOnlySpan<char> chars = [];
Assert.False(chars.StartsWith('\0'));
Assert.False(chars.StartsWith('f'));

chars = "foo";
Assert.True(chars.StartsWith(chars[0]));
Assert.True(chars.StartsWith('f'));
Assert.False(chars.StartsWith('o'));

scoped ReadOnlySpan<string> strings = [];
Assert.False(strings.StartsWith((string)null));
Assert.False(strings.StartsWith("foo"));

strings = ["foo", "bar"];
Assert.True(strings.StartsWith(strings[0]));
Assert.True(strings.StartsWith("foo"));
Assert.True(strings.StartsWith("*foo".Substring(1)));
Assert.False(strings.StartsWith("bar"));
Assert.False(strings.StartsWith((string)null));

strings = [null, "bar"];
Assert.True(strings.StartsWith(strings[0]));
Assert.True(strings.StartsWith((string)null));
Assert.False(strings.StartsWith("bar"));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ internal bool IsFileSystemEntryHidden(ReadOnlySpan<char> path, ReadOnlySpan<char
return HasHiddenFlag;
}

internal static bool IsNameHidden(ReadOnlySpan<char> fileName) => fileName.Length > 0 && fileName[0] == '.';
internal static bool IsNameHidden(ReadOnlySpan<char> fileName) => fileName.StartsWith('.');

// Returns true if the path points to a directory, or if the path is a symbolic link
// that points to a directory
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ public static bool IsPathRooted([NotNullWhen(true)] string? path)

public static bool IsPathRooted(ReadOnlySpan<char> path)
{
return path.Length > 0 && path[0] == PathInternal.DirectorySeparatorChar;
return path.StartsWith(PathInternal.DirectorySeparatorChar);
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2619,6 +2619,24 @@ ref MemoryMarshal.GetReference(value),
valueLength);
}

/// <summary>
/// Determines whether the specified value appears at the start of the span.
/// </summary>
/// <param name="span">The span to search.</param>
/// <param name="value">The value to compare.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool StartsWith<T>(this ReadOnlySpan<T> span, T value) where T : IEquatable<T>? =>
span.Length != 0 && (span[0]?.Equals(value) ?? (object?)value is null);

/// <summary>
/// Determines whether the specified value appears at the end of the span.
/// </summary>
/// <param name="span">The span to search.</param>
/// <param name="value">The value to compare.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool EndsWith<T>(this ReadOnlySpan<T> span, T value) where T : IEquatable<T>? =>
span.Length != 0 && (span[^1]?.Equals(value) ?? (object?)value is null);
stephentoub marked this conversation as resolved.
Show resolved Hide resolved

/// <summary>
/// Reverses the sequence of the elements in the entire span.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -508,7 +508,7 @@ private static bool FilterNameImpl(MemberInfo m, object filterCriteria, StringCo
}

// Check to see if this is a prefix or exact match requirement
if (str.Length > 0 && str[str.Length - 1] == '*')
if (str.EndsWith('*'))
{
str = str.Slice(0, str.Length - 1);
return name.StartsWith(str, comparison);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ internal static byte[] LaxDecodeHexString(this string hexString)

ReadOnlySpan<char> s = hexString;

if (s.Length != 0 && s[0] == '\u200E')
if (s.StartsWith('\u200E'))
{
s = s.Slice(1);
}
Expand Down
Loading