-
Notifications
You must be signed in to change notification settings - Fork 2.7k
Conversation
@@ -10,8 +10,7 @@ internal static partial class PathInternal | |||
/// <summary> | |||
/// Returns true if the path ends in a directory separator. | |||
/// </summary> | |||
internal static bool EndsInDirectorySeparator(string path) => | |||
!string.IsNullOrEmpty(path) && IsDirectorySeparator(path[path.Length - 1]); | |||
internal static bool EndsInDirectorySeparator(ReadOnlySpan<char> path) => IsDirectorySeparator(path[path.Length - 1]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this need to handle path.Length == 0
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not currently, but I think it is better to have it.
return true; | ||
} | ||
|
||
if (path2.Length == 0) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you avoid this duplication by acting on temp, which is whichever of path1 and path2 is not empty?
return true; | ||
} | ||
|
||
bool needsSeparator = !(PathInternal.EndsInDirectorySeparator(path1) || PathInternal.EndsInDirectorySeparator(path2)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be checking path2 begins with directory separator?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, thanks
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing test I guess
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've got the tests, just trying to juggle multiple changes unsuccessfully.
public static string Join(ReadOnlySpan<char> path1, ReadOnlySpan<char> path2) | ||
{ | ||
if (path1.Length == 0) | ||
return path2.Length == 0 ? string.Empty : new string(path2); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we have the length == 0 check here and not for path2.Length in the second if?
The string ctor already does this check, so we shouldn't need it in either case:
https://github.com/dotnet/coreclr/blob/master/src/mscorlib/src/System/String.cs#L665
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, didn't know the optimization was there.
return JoinInternal(path1, path2, path3); | ||
} | ||
|
||
public static bool TryJoin(Span<char> destination, out int charsWritten, ReadOnlySpan<char> path1, ReadOnlySpan<char> path2) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about something like this to avoid the extra path1/path2.Length == 0 checks?
In the worst case, there are 3 extra checks in the current impl.
public static bool TryJoin(Span<char> destination, out int charsWritten, ReadOnlySpan<char> path1, ReadOnlySpan<char> path2)
{
charsWritten = 0;
if (path1.IsEmpty)
{
if (path2.IsEmpty)
return true;
if (destination.Length < path2.Length)
return false;
path2.CopyTo(destination);
charsWritten = path2.Length;
return true;
}
else if (path2.IsEmpty)
{
if (destination.Length < path1.Length)
return false;
path1.CopyTo(destination);
charsWritten = path1.Length;
return true;
}
Debug.Assert(!path1.IsEmpty && !path2.IsEmpty);
int charsNeeded = path1.Length + path2.Length;
if (!(IsDirectorySeparator(path1[path1.Length - 1]) || IsDirectorySeparator(path2[0])))
{
charsNeeded++;
if (destination.Length < charsNeeded)
return false;
path1.CopyTo(destination);
destination[path1.Length] = DirectorySeparatorChar;
path2.CopyTo(destination.Slice(path1.Length + 1));
}
else
{
if (destination.Length < charsNeeded)
return false;
path1.CopyTo(destination);
path2.CopyTo(destination.Slice(path1.Length));
}
charsWritten = charsNeeded;
return true;
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because it is a little more bulky & complicated. I can't imagine optimizing checking length being worth it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point. What about the needsSeparator branching? Can we just have a single if/else (which results in some code duplication).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There will be all sorts. The first path will typically be longer than the second. I don't want to add the complexity here unless I can see a real impact in a higher level scenario. Directory.GetFiles() over a large set, for example. I'll be switching to use these methods (as opposed to our current helpers) as soon as we drop an updated CLR and we can revisit.
@@ -10,8 +10,12 @@ internal static partial class PathInternal | |||
/// <summary> | |||
/// Returns true if the path ends in a directory separator. | |||
/// </summary> | |||
internal static bool EndsInDirectorySeparator(string path) => | |||
!string.IsNullOrEmpty(path) && IsDirectorySeparator(path[path.Length - 1]); | |||
internal static bool EndsInDirectorySeparator(ReadOnlySpan<char> path) => path.Length > 0 && IsDirectorySeparator(path[path.Length - 1]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We seem to only call these methods when path.Length is greater than 0 anyway. Can the length check be a Debug.Assert instead?
Same with StartsWithDirectorySeparator.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That is true now, but I expect that to change, particularly as I expect to include this file in a few CoreFX libraries.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We seem to have EndsInDirectorySeparator in already:
internal static bool EndsInDirectorySeparator(ReadOnlySpan<char> path) |
But not in Path.Unix.cs (and we don't have StartsWithDirectorySeparator)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, missed that. I'll get this one. I have a fair amount of cleanup to do- I intend to do so when I include the Path sources directly in System.Runtime.Extensions again.
3ec97fb
to
4a38da8
Compare
@@ -444,6 +444,40 @@ public static bool TryJoin(Span<char> destination, out int charsWritten, ReadOnl | |||
return true; | |||
} | |||
|
|||
public static bool TryJoin(ReadOnlySpan<char> path1, ReadOnlySpan<char> path2, ReadOnlySpan<char> path3, Span<char> destination, out int charsWritten) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any way this method can reuse the 2 path overload?
Join the first 2 paths, then combine the result with the 3rd?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do use the other overload. Part of the contract here is that I don't write into the buffer if I can't fit the results so I can't simplify beyond what I've done.
@@ -444,6 +444,40 @@ public static bool TryJoin(Span<char> destination, out int charsWritten, ReadOnl | |||
return true; | |||
} | |||
|
|||
public static bool TryJoin(ReadOnlySpan<char> path1, ReadOnlySpan<char> path2, ReadOnlySpan<char> path3, Span<char> destination, out int charsWritten) | |||
{ | |||
charsWritten = 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: Thoughts on moving this assignment only at the return sites? In this case, there are only two such places (line 451/467) since it is already set in other places.
@JeremyKuhne after this #16598 gets merged we will have to change this |
return true; | ||
} | ||
|
||
public static bool TryJoin(ReadOnlySpan<char> path1, ReadOnlySpan<char> path2, ReadOnlySpan<char> path3, Span<char> destination, out int charsWritten) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this can be optimized similar to the 2 path overload (probably even bigger impact), but I don't think it needs to block this PR. Feel free to file an issue/optimize outside this change.
I would suggest, however, that we do the following instead of calling the methods that do the length checks:
IsDirectorySeparator(path1[path1.Length - 1])
IsDirectorySeparator(path2[0])
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Other than perf, looks good to me.
2a1812a
to
2bba548
Compare
* Add Path.Join() methods. See dotnet#25536. * Address feedback and fix a couple issues now that I've got tests running correctly. * Update per final API approval * Fix Unix, remove redundant helper. * Merge and tweak join methods in GetFullPath(string, string) * Tweak again
* Expose Path.Join and test. Depends on dotnet/coreclr#16561 * Update for final API review changes * Address feedback * Address more feedback.
* Add Path.Join() methods. See #25536. * Address feedback and fix a couple issues now that I've got tests running correctly. * Update per final API approval * Fix Unix, remove redundant helper. * Merge and tweak join methods in GetFullPath(string, string) * Tweak again Signed-off-by: dotnet-bot <dotnet-bot@microsoft.com>
* Add Path.Join() methods. See #25536. * Address feedback and fix a couple issues now that I've got tests running correctly. * Update per final API approval * Fix Unix, remove redundant helper. * Merge and tweak join methods in GetFullPath(string, string) * Tweak again Signed-off-by: dotnet-bot-corefx-mirror <dotnet-bot@microsoft.com>
* Add Path.Join() methods. See #25536. * Address feedback and fix a couple issues now that I've got tests running correctly. * Update per final API approval * Fix Unix, remove redundant helper. * Merge and tweak join methods in GetFullPath(string, string) * Tweak again Signed-off-by: dotnet-bot <dotnet-bot@microsoft.com>
* Add Path.Join() methods. See #25536. * Address feedback and fix a couple issues now that I've got tests running correctly. * Update per final API approval * Fix Unix, remove redundant helper. * Merge and tweak join methods in GetFullPath(string, string) * Tweak again Signed-off-by: dotnet-bot-corefx-mirror <dotnet-bot@microsoft.com>
* Add Path.Join() methods. See #25536. * Address feedback and fix a couple issues now that I've got tests running correctly. * Update per final API approval * Fix Unix, remove redundant helper. * Merge and tweak join methods in GetFullPath(string, string) * Tweak again Signed-off-by: dotnet-bot <dotnet-bot@microsoft.com>
* Expose Path.Join and test. Depends on dotnet/coreclr#16561 * Update for final API review changes * Address feedback * Address more feedback. Commit migrated from dotnet/corefx@0a092a2
See https://github.com/dotnet/corefx/issues/25536.
cc: @bartonjs, @terrajobst, @nguerrera, @danmosemsft, @pjanotti, @Anipik