Skip to content

Make two Paths unequal if they differ in trailing slash #87339

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

Closed
wants to merge 2 commits into from

Conversation

dtolnay
Copy link
Member

@dtolnay dtolnay commented Jul 21, 2021

It seems problematic to consider two paths equal if one of them can exist when the other does not exist.

In the previous behavior, the following asserts pass:

use std::path::Path;

fn main() {
    let path1 = Path::new("/etc/passwd");
    let path2 = Path::new("/etc/passwd/");

    assert_eq!(path1, path2);

    assert!(path1.metadata().is_ok());

    // Err: Os { code: 20, kind: Other, message: "Not a directory" }
    assert!(path2.metadata().is_err());
}

In this PR path1 == path2 becomes false.

@rust-highfive
Copy link
Contributor

r? @yaahc

(rust-highfive has picked a reviewer for you, use r? to override)

@rust-highfive rust-highfive added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Jul 21, 2021
Copy link
Member

@the8472 the8472 left a comment

Choose a reason for hiding this comment

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

Couldn't this break some use-cases? E.g. shells autocomplete directories with a trailing slash, if a CLI tool then tries to deduplicate paths or exclude paths by comparing them to another set of paths that's derived in some other way that doesn't append the trailing slash they might get a false negative compared to previous behavior.

At the very least it should be documented and have a release note.

@@ -2686,7 +2694,8 @@ impl fmt::Display for Display<'_> {
impl cmp::PartialEq for Path {
#[inline]
fn eq(&self, other: &Path) -> bool {
self.components().eq(other.components())
self.components() == other.components()
&& self.ends_with_separator() == other.ends_with_separator()
Copy link
Member

Choose a reason for hiding this comment

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

Shouldn't this be implemented on the Components level instead of Path? Otherwise they'll give different results.

Copy link
Member Author

Choose a reason for hiding this comment

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

Giving different results is correct. The path components are the same i.e. the Components iterator would produce the identical sequence of Component enum values, even if one path ends in separator and the other does not.

Copy link
Member

Choose a reason for hiding this comment

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

If we say that a trailing slash is semantically different then shouldn't components preserve that semantic difference? E.g. the last component could include the trailing slash. Or there could be a final zero-length component. Ideally this would be expressed through a new enum variant but it's exhaustive so we can't extend it 🙁.

Is assert_eq!(foo, foo.components().as_path()) always true?

Copy link
Member Author

Choose a reason for hiding this comment

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

I don't think it follows that we need components to preserve that semantic difference.

It's often the case that code knows what kind of filesystem object it's expecting to manipulate, whether all files like a tests/ui/*.stderr glob expansion in the trybuild crate, or all dirs like ~/.rustup/toolchains/*. I don't see throwing an "empty" path component onto the end of the Components iterator in the dir case being helpful or desirable in the common case. The directoryness comes through based on what the code does with the paths after examining their components, such as using std::fs::remove_dir on it rather than remove_file.

Copy link
Member

Choose a reason for hiding this comment

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

I don't think it follows that we need components to preserve that semantic difference.

Well... the thing is that Components implements AsRef<Path>, so if you have a generic function taking it via AsRef then it'll behave differently depending on whether you pass in a Components or something else that implements AsRef<Path>.

It's consistent with existing documentation, it's clearly spelled out that path.components() will strip trailing slashes, but previously that wasn't considered a semantic difference (since Eq considered them equal), now it is.

@camelid camelid added the T-libs Relevant to the library team, which will review and decide on the PR/issue. label Jul 23, 2021
@JohnCSimon JohnCSimon added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Aug 15, 2021
@bors
Copy link
Collaborator

bors commented Aug 20, 2021

☔ The latest upstream changes (presumably #86898) made this pull request unmergeable. Please resolve the merge conflicts.

@JohnCSimon JohnCSimon added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Sep 6, 2021
@JohnCSimon JohnCSimon added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Sep 27, 2021
@JohnCSimon
Copy link
Member

merge conflicts

@dtolnay
Copy link
Member Author

dtolnay commented Sep 28, 2021

@rustbot label -S-waiting-on-author +S-waiting-on-review

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. and removed S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. labels Sep 28, 2021
@yaahc
Copy link
Member

yaahc commented Oct 8, 2021

Part of me feels like it would be nice to have eq work regardless of the trailing slash if the file being named is a directory, but I feel like we almost certainly don't want to be interacting with filesystem APIs to see if paths exist and are dirs when checking equality. So with all of that said this PR looks good to me.

I'm going to FCP this change since it changes runtime behavior.

@yaahc
Copy link
Member

yaahc commented Oct 8, 2021

@rfcbot merge

@rfcbot

This comment has been minimized.

@rfcbot rfcbot added proposed-final-comment-period Proposed to merge/close by relevant subteam, see T-<team> label. Will enter FCP once signed off. disposition-merge This issue / PR is in PFCP or FCP with a disposition to merge it. labels Oct 8, 2021
@m-ou-se

This comment has been minimized.

@rfcbot

This comment has been minimized.

@rfcbot rfcbot added to-announce Announce this issue on triage meeting and removed final-comment-period In the final comment period and will be merged soon unless new substantive objections are raised. labels Oct 22, 2021
@rfcbot
Copy link
Collaborator

rfcbot commented Oct 22, 2021

The final comment period, with a disposition to merge, as per the review above, is now complete.

As the automated representative of the governance process, I would like to thank the author for their work and everyone else who contributed.

This will be merged soon.

@yaahc
Copy link
Member

yaahc commented Oct 22, 2021

I do feel like we should resolve the two open comment threads before merging this

@yaahc yaahc added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Oct 22, 2021
@dtolnay
Copy link
Member Author

dtolnay commented Oct 27, 2021

Responded above.
@rustbot label -S-waiting-on-author +S-waiting-on-review

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. and removed S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. labels Oct 27, 2021
@yaahc
Copy link
Member

yaahc commented Nov 2, 2021

@the8472 are you satisfied by @dtolnay's explanations?

@the8472
Copy link
Member

the8472 commented Nov 2, 2021

The ordering seems fine now.

I still think the newly introduced semantic inconsistency between Path and Components is unfortunate, but at least components() does mention it is normalizing.

Maybe it just needs to be documented somewhere that Path considers a trailing slash semantically meaningful while Components does not.

@yaahc
Copy link
Member

yaahc commented Nov 2, 2021

Maybe it just needs to be documented somewhere that Path considers a trailing slash semantically meaningful while Components does not.

Sounds good, lets do this in a follow up PR?

@bors r+

@bors
Copy link
Collaborator

bors commented Nov 2, 2021

📌 Commit b2e3862 has been approved by yaahc

@bors bors added S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Nov 2, 2021
@bors
Copy link
Collaborator

bors commented Nov 3, 2021

⌛ Testing commit b2e3862 with merge 44a95033fa2615ffab1182da151fa3ed531eced3...

@rust-log-analyzer
Copy link
Collaborator

The job x86_64-gnu-aux failed! Check out the build log: (web) (plain)

Click to see the possible cause of the failure (guessed by this bot)
test lto::cdylib_and_rlib ... ok

failures:

---- fix::fix_in_existing_repo_weird_ignore stdout ----
running `/checkout/obj/build/x86_64-unknown-linux-gnu/stage2-tools/x86_64-unknown-linux-gnu/release/cargo fix`
thread 'fix::fix_in_existing_repo_weird_ignore' panicked at '
test failed running `/checkout/obj/build/x86_64-unknown-linux-gnu/stage2-tools/x86_64-unknown-linux-gnu/release/cargo fix`
error: process exited with code 101 (expected 0)

--- stderr
--- stderr
error: no VCS found for this package and `cargo fix` can potentially perform destructive changes; if you'd like to suppress this error pass `--allow-no-vcs`
', src/tools/cargo/tests/testsuite/fix.rs:1403:20


failures:
    fix::fix_in_existing_repo_weird_ignore
---
expected success, got: exit status: 101


Build completed unsuccessfully in 0:24:36
make: *** [check-aux] Error 1
Makefile:44: recipe for target 'check-aux' failed

@bors
Copy link
Collaborator

bors commented Nov 3, 2021

💔 Test failed - checks-actions

@bors bors added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. and removed S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. labels Nov 3, 2021
@dtolnay dtolnay added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Nov 5, 2021
@matthiaskrgr
Copy link
Member

@bors r- (bors having hiccup and trying to retest failed prs)

@apiraino apiraino removed the to-announce Announce this issue on triage meeting label Nov 11, 2021
@dtolnay
Copy link
Member Author

dtolnay commented Nov 21, 2021

I think upon reflection I am going to retract this for being more disruptive than I would have wanted.

An example of code that's negatively affected is this snippet from cargo where it's testing whether path happens to be the root of a git repo:

    if let Ok(repo) = git2::Repository::discover(path) {
        if repo.workdir().map_or(false, |workdir| workdir == path) {

It turns out path comes originally from std::env::current_dir with no trailing slash on my machine, while git2's Repository::workdir always comes back with a trailing slash. Thus the == comparison could now never be true.

I guess code that wants the non-equal behavior should use a newtype with PartialEq and PartialOrd which mirror the syntactically_dir logic from this PR.

@dtolnay dtolnay closed this Nov 21, 2021
@dtolnay dtolnay deleted the trailingsep branch November 21, 2021 21:32
@dtolnay dtolnay removed the S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. label Jan 14, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
disposition-merge This issue / PR is in PFCP or FCP with a disposition to merge it. finished-final-comment-period The final comment period is finished for this PR / Issue. relnotes Marks issues that should be documented in the release notes of the next release. T-libs Relevant to the library team, which will review and decide on the PR/issue. T-libs-api Relevant to the library API team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging this pull request may close these issues.