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

StringSegment: more AsSpan overloads #53463

Merged
merged 7 commits into from
Jun 3, 2021
Merged

Conversation

gfoidl
Copy link
Member

@gfoidl gfoidl commented May 29, 2021

Fixes #50428

@dotnet-issue-labeler
Copy link

Note regarding the new-api-needs-documentation label:

This serves as a reminder for when your PR is modifying a ref *.cs file and adding/modifying public APIs, to please make sure the API implementation in the src *.cs file is documented with triple slash comments, so the PR reviewers can sign off that change.

@ghost
Copy link

ghost commented May 29, 2021

Tagging subscribers to this area: @eerhardt, @maryamariyan
See info in area-owners.md if you want to be subscribed.

Issue Details

Fixes #50428

Author: gfoidl
Assignees: -
Labels:

area-Extensions-Primitives, new-api-needs-documentation

Milestone: -

/// </exception>
public ReadOnlySpan<char> AsSpan(int start, int length)
{
if (!HasValue || (start | length) < 0 || (uint)(start + length) > (uint)Length)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (!HasValue || (start | length) < 0 || (uint)(start + length) > (uint)Length)
if (!HasValue || start < 0 || (uint)(start + length) > (uint)Length)

MemoryExtensions.AsSpan(this string text, int start, int length) throws when length is negative, therefore this check is redundant.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch!

The difference is that MemoryExtensions.AsSpan throws AOR with paramName start only, whilst the check here distinguishes between start and length to be on par with SubSegment, and SubString.

So what to do 🤷‍♂️

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, I missed that paramName was always start in MemoryExtensions.AsSpan. This seems like a bug?

Span(T[] array, int start, int length) just avoids specifying the paramName, which is maybe the best approach?

Copy link
Member Author

@gfoidl gfoidl Jun 1, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@eerhardt do you think this is a bug in MemoryExtensions.AsSpan?
Or who is the right person to ask this?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@GrabYourPitchforks is the person I'd ask...

It feels like a bug to me. It definitely doesn't seem correct that MemoryExtensions.AsSpan and [ReadOnly]Span.Slice act differently here. I assume the Span.Slice behavior is "more correct" because you can't definitively tell which parameter is really at fault here without doing more checks which just adds code to a very hot path.

Maybe the best approach would be to open an issue describing the difference. I don't think we should try to solve it here.

Copy link
Member

@eerhardt eerhardt Jun 1, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also FYI @tarekgh. Tracing back to dotnet/corefx@a68803c, it appears Span.Slice used start before that commit, but during that commit Span got split into Fast and Portable. The Portable path still used start, but the Fast path dropped the parameter name.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Filed #53622 for this.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The MemoryExtensions behavior is intentional, but I agree it's weird. We want the API to be as fast as possible, which means we don't have the luxury of figuring out which of start or length was incorrect. We could pass a nullptr argument name to the ArgumentOutOfRangeException ctor, but honestly nobody expects ArgumentException.ParamName to be nullptr. So always passing "start" seems like the best solution given these constraints. If it's really a problem in practice, we could always create a detailed error message, just for this one scenario.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could pass a nullptr argument name to the ArgumentOutOfRangeException ctor, but honestly nobody expects ArgumentException.ParamName to be nullptr.

But it is OK for Span?

public ReadOnlySpan<T> Slice(int start, int length)
{
#if TARGET_64BIT
// See comment in Span<T>.Slice for how this works.
if ((ulong)(uint)start + (ulong)(uint)length > (ulong)(uint)_length)
ThrowHelper.ThrowArgumentOutOfRangeException();
#else
if ((uint)start > (uint)_length || (uint)length > (uint)(_length - start))
ThrowHelper.ThrowArgumentOutOfRangeException();
#endif

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(I guess we can have this discussion in #53622 😄)

Copy link
Member

@eerhardt eerhardt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good to me. Thanks for the contribution here @gfoidl!

@GrabYourPitchforks
Copy link
Member

I'll be the problem child. :)

Nit: I think these parameters should be called offset and length to match the parameter names on the existing StringSegment.Subsegment and StringSegment.Substring instance methods. Symmetry with existing methods on the type is generally preferred over symmetry with the MemoryExtensions.AsSpan extension methods.

@bartonjs @terrajobst @eerhardt thoughts?

@bartonjs
Copy link
Member

bartonjs commented Jun 2, 2021

I think these parameters should be called offset and length

My recollection is that this came up in the meeting, and we decided that "AsSpan"-consistency was the way to go.

string.Substring is startIndex/length; string.AsSpan is start/length. So AsSpan-consistency matches what we did for String.

@eerhardt
Copy link
Member

eerhardt commented Jun 2, 2021

@GrabYourPitchforks - I'm satisfied with this PR and think it is ready to merge. Let me know if you have any objections.

@GrabYourPitchforks
Copy link
Member

@eerhardt Go for it!

@eerhardt eerhardt merged commit 253a253 into dotnet:main Jun 3, 2021
@gfoidl gfoidl deleted the stringsegment-asspan branch June 4, 2021 07:08
@ghost ghost locked as resolved and limited conversation to collaborators Jul 4, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add more AsSpan overloads to StringSegment
6 participants