Skip to content

New lint: needless_path_new - #14895

Open
ada4a wants to merge 2 commits into
rust-lang:masterfrom
ada4a:needless_path_new
Open

ada4a wants to merge 2 commits into
rust-lang:masterfrom
ada4a:needless_path_new

Conversation

@ada4a

@ada4a ada4a commented May 25, 2025 •

Copy link
Copy Markdown
Contributor

View all comments

fs::read_to_string(Path::new("foo.txt")) -> fs::read_to_string("foo.txt")

Resolves #14668

changelog: [needless_path_new]: new lint

@rustbot

rustbot commented May 25, 2025

Copy link
Copy Markdown
Collaborator

r? @blyxyas

rustbot has assigned @blyxyas.
They will have a look at your PR within the next two weeks and either review your PR or reassign to another reviewer.

Use r? to explicitly pick a reviewer

@rustbot rustbot added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties label May 25, 2025
Comment thread clippy_lints/src/needless_path_new.rs Outdated
Comment thread clippy_lints/src/needless_path_new.rs Outdated
@samueltardieu

samueltardieu commented May 27, 2025 •

Copy link
Copy Markdown
Member

I think you need to check that you can use a AsRef<Path>, otherwise you might suggest removing Path::new() while you really need a &Path.

Don't forget to look at the lintcheck logs for your PR. You'll see the first hit is probably wrong, as it requires building a Option<&Path>, not a Option<dyn AsRef<Path>>.

@ada4a

ada4a commented May 27, 2025 •

Copy link
Copy Markdown
Contributor Author

Don't forget to look at the lintcheck logs for your PR.

oh, didn't know about that! thank you, that's very helpful

I think you need to check that you can use a AsRef<Path>, otherwise you might suggest removing Path::new() while you really need a &Path.

You'll see the first hit [in lintcheck] is probably wrong, as it requires building a Option<&Path>, not a Option<dyn AsRef<Path>>.

I thought I already covered that with the following line:

&& implements_asref_path(*parameter)

I even have a test case for exactly this!

// no warning
fn takes_path(_: &Path) {}
takes_path(Path::new("foo"));

But now that I think about it, the check above doesn't actually work?... It just checks that whatever type parameter has itself implements AsRef<Path>, and that is the case for Path. I wonder why the test even works in that case... Maybe something to do with Path vs &Path?

I guess what I need to check instead is that parameter is impl AsRef<Path>. is_type_diagnostic_item seems to be closest to what I want, but it wouldn't include the impl part? Btw that makes me wonder if I should check for dyn AsRef<Path> as well

@ada4a

ada4a commented May 27, 2025 •

Copy link
Copy Markdown
Contributor Author

But the weirdest thing is that Some(..) is not even a function/method call?... The lint is supposed to only check expressions that are ExprKind::Call or ExprKind::MethodCall -- but maybe the former actually includes anything of the form foo(bar)?...

EDIT: indeed it does. but I guess this is not actually the root cause of the problem, so we could keep it? Maybe the function/method restriction should not even be necessary, maybe we should just check that the place the expression is in requires an impl AsRef<Path>, be that place a parameter in a function call, or e.g. a RHS of an assignment, like

let _: Option<impl AsRef<Path>>: Some(Path::new("foo.txt"));

(assuming impl Trait in this position would be valid, but this is just an example)

Comment thread clippy_lints/src/needless_path_new.rs Outdated
let implements_asref_path = |arg| implements_trait(cx, arg, asref_def_id, &[path_ty.into()]);

if let ty::FnDef(..) | ty::FnPtr(..) = type_definition.kind() {
let parameters = type_definition.fn_sig(tcx).skip_binder().inputs();

@ada4a ada4a May 29, 2025 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I thought about this whole thing a bit more.

As I said towards the end of this comment, what we want to check is whether a particular parameter of a function is impl AsRef<Path>, i.e. find a p such that fn foo(p: impl AsRef<Path>).

But the latter is just syntax sugar for fn foo<P: AsRef<Path>>(p: P), so we are in fact looking for a parameter whose type is defined in a generic type parameter of the function.

Therefore I think we shouldn't actually calling skip_binder here. Without that, I get the following chain:

  1. The type of type_definition.fn_sig(tcx) is PolyFnSig, which is an alias to Binder<'tcx, FnSig<'tcx>>
  2. that one has a Binder::inputs method, which returns a Binder<'tcx, &'tcx [Ty<'tcx>]>
  3. I can kind of iterate over that using Binder::iter, which finally gives me impl Iterator<Item=Binder<'tcx, Ty<'tcx>>>
  4. and now I'm stuck. What I think I have achieved at this point is turn a function like fn foo<P, T>(p: P, t: T, n: u32) into something like [ p<P,T>: P, t<P, T>: T, n<P, T>: u32 ] (syntax very much pseudocode-ish). So from there I need to first "shed" the unnecessary generics, and then?... No idea to be honest

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Sorry, this slipped through, indeed Zulip would be a better choice for those kind of discussion. Have you read the documentation on ParamEnv? You could get the caller bounds that the caller has to adhere to for the parameter index you're interested in, and check whether the type of the argument would meet those bounds (so that for example a bound of impl AsRef<Path> + Clone would not receive an object which implements AsRef<Path> but is not Cloneable).

Also, you will have to check that the type of the parameter you're interested in does not appear in another parameter or a clause applying to another parameter type, as for example

fn foo<P: AsRef<Path>>(x: P, y: P) { … }

must use the same type for both parameters, and you better let this untouched.

Does that help?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Oh, the whole Analysis section from the dev-guide seems to be exactly what I need! Thank you very much for this and all the other pointers, I'll read them and see what I come up with –and ask on Zulip if I don't understand something

@rustbot

This comment has been minimized.

@ada4a
ada4a force-pushed the needless_path_new branch from a0b0291 to d6f2bc2 Compare June 2, 2025 12:05
@ada4a

ada4a commented Jun 2, 2025

Copy link
Copy Markdown
Contributor Author

@samueltardieu just pinging in case the notifications slipped through. If you can't/don't want to deal with this, I'll probably ask the Zulip folks for help again?

@rustbot

This comment has been minimized.

@ada4a
ada4a force-pushed the needless_path_new branch from dfe7581 to c91bf12 Compare June 5, 2025 19:59
@ada4a
ada4a force-pushed the needless_path_new branch from c91bf12 to 8915aff Compare June 20, 2025 23:13
@blyxyas

blyxyas commented Jun 24, 2025

Copy link
Copy Markdown
Member

Currently making the feature freeze feature and the performance project perform, I don't have the capacity right now to review this :/

r? clippy

@rustbot rustbot assigned Centri3 and unassigned blyxyas Jun 24, 2025
@ada4a

ada4a commented Jun 24, 2025

Copy link
Copy Markdown
Contributor Author

Oh hi! All good, I'm still very much not done with this 😅 We're slowly making progress over on Zulip: https://rust-lang.zulipchat.com/#narrow/channel/257328-clippy/topic/needless_path_new/with/525402801

@blyxyas

blyxyas commented Jun 24, 2025

Copy link
Copy Markdown
Member

I'm still very much not done with this

No problem, when you consider that you're in a ready-to-review state, you can mention the reviewer assigned to take a look

@rustbot author

@rustbot rustbot added S-waiting-on-author Status: This is awaiting some action from the author. (Use `@rustbot ready` to update this status) and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties labels Jun 24, 2025
@rustbot

rustbot commented Jun 24, 2025

Copy link
Copy Markdown
Collaborator

Reminder, once the PR becomes ready for a review, use @rustbot ready.

@Jarcho Jarcho left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

r? Jarcho

I can take over the review for this.

Comment thread clippy_lints/src/needless_path_new.rs Outdated
Comment thread clippy_lints/src/needless_path_new.rs Outdated
Comment thread clippy_lints/src/needless_path_new.rs Outdated
Comment thread clippy_lints/src/needless_path_new.rs Outdated
Comment on lines +100 to +109
let generic_args_we_can_change: Vec<_> = generic_args
.iter()
.filter_map(|g| g.as_type())
// if a generic is used in multiple places, we should better not touch it,
// since we'd need to suggest changing both parameters that using it at once,
// which might not be possible
.filter(|g| {
let inputs_and_output = fn_sig.inputs().iter().copied().chain([fn_sig.output()]);
inputs_and_output.filter(|i| i.contains(*g)).count() < 2
})

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This is better determined once we find a call to Path::new lines up with a generic argument.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I agree, this was just a WIP thing to see if it'll even work (and have something to link to for the folks on Zulip)

Comment thread clippy_lints/src/needless_path_new.rs Outdated
@rustbot rustbot assigned Jarcho and unassigned Centri3 Jun 25, 2025
@ada4a
ada4a force-pushed the needless_path_new branch 2 times, most recently from 99a3e7b to 599b3d7 Compare June 27, 2025 10:16
@ada4a
ada4a force-pushed the needless_path_new branch from 599b3d7 to b8a1e85 Compare July 14, 2025 17:26
@ada4a
ada4a force-pushed the needless_path_new branch from b8a1e85 to 84862cf Compare July 25, 2025 06:39
@github-actions

github-actions Bot commented Jul 25, 2025 •

Copy link
Copy Markdown

Lintcheck changes for 945d5e4

Lint Added Removed Changed
clippy::needless_path_new 3 0 0

This comment will be updated if you push new changes

@ada4a
ada4a force-pushed the needless_path_new branch from 84862cf to d014c46 Compare August 7, 2025 21:52
@ada4a
ada4a force-pushed the needless_path_new branch from d014c46 to a5dd45b Compare August 22, 2025 13:37
@rustbot

This comment has been minimized.

@ada4a
ada4a force-pushed the needless_path_new branch from a5dd45b to a7fb9ec Compare September 8, 2025 17:43
@rustbot

This comment has been minimized.

@rustbot

This comment has been minimized.

@ada4a ada4a left a comment •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I've certainly learned a lot in these.. 4 months, wow.

This pretty much works now, here are some things I'm personally not fully sure about.

@rustbot ready

View changes since this review

Comment on lines +137 to +161
.filter_map(|(clause, _)| {
let clause = clause.kind();
#[expect(clippy::match_same_arms, reason = "branches have different reasons to be `None`")]
match clause.skip_binder() {
// This is what we analyze
// NOTE: repeats the contents of `Clause::as_trait_clause`,
// except we don't `Binder::rebind` as we don't care about the binder
ClauseKind::Trait(trait_clause) => Some(trait_clause),

// Trivially holds for `P`: `Path::new` has signature `&S -> &Path`, so any "outlives" that holds for
// `P` does so for `S` as well
ClauseKind::TypeOutlives(_) | ClauseKind::RegionOutlives(_) => None,

// Irrelevant to us: neither `AsRef` nor `Sized` have associated types
ClauseKind::Projection(_) => None,

// Irrelevant: we don't have anything to do with consts
ClauseKind::ConstArgHasType(..) | ClauseKind::ConstEvaluatable(_) | ClauseKind::HostEffect(_) => None,

// Irrelevant?: we don't deal with unstable impls
ClauseKind::UnstableFeature(_) => None,

ClauseKind::WellFormed(_) => None,
}
})

@ada4a ada4a Sep 19, 2025 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

See last commit -- should I keep this fully explicit match, or should I just revert to clause.as_trait_clause()? The main reason I like this is that I can enumerate why I dismiss all the clause kinds other than the trait predicates -- but maybe there is a more concise way of doing that

Comment on lines +176 to +182
// match cx.tcx.get_diagnostic_name(pred.def_id()) {
// Some(sym::AsRef) => {
// // TODO: check if it's `AsRef<Path>` in paricular
// },
// Some(sym::Sized) => todo!(),
// _ => return false,
// };

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I decided to forego checking for exactly AsRef and Sized, because a) imo that makes the lint more correct, and b) is just easier to explain in the lint description lol.

If you agree, I'd remove this WIP code, otherwise I'll continue on from it

@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 from the author. (Use `@rustbot ready` to update this status) labels Sep 19, 2025
@ada4a
ada4a force-pushed the needless_path_new branch 2 times, most recently from b5660db to 945d5e4 Compare September 19, 2025 14:24
@rustbot

This comment has been minimized.

@samueltardieu samueltardieu added the needs-fcp PRs that add, remove, or rename lints and need an FCP label Oct 17, 2025
@Jarcho

Jarcho commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Ping @ada4a are you willing to continue working on this?

@ada4a

ada4a commented Sep 9, 2026

Copy link
Copy Markdown
Contributor Author

I vaguely remember hearing that there was some other, more generic lint in plans, which would highlight all instances of "foo(x) used as an argument to a function which would've accepted just x as well", but I can't seem to be able to find it now... But if that's no longer pursued, I'd be happy to finish this up. It should be mostly ready, with just a couple of open questions.

@ada4a
ada4a force-pushed the needless_path_new branch from 945d5e4 to f322ec6 Compare September 9, 2026 21:57
@rustbot

rustbot commented Sep 9, 2026

Copy link
Copy Markdown
Collaborator

This PR was rebased onto a different master commit. Here's a range-diff highlighting what actually changed.

Rebasing is a normal part of keeping PRs up to date, so no action is needed—this note is just to help reviewers.

@ada4a
ada4a force-pushed the needless_path_new branch from f322ec6 to 70c5589 Compare September 9, 2026 23:10
@ada4a
ada4a force-pushed the needless_path_new branch from 70c5589 to 5d460e0 Compare September 9, 2026 23:17
@Jarcho

Jarcho commented Sep 10, 2026

Copy link
Copy Markdown
Contributor

You could lint as_ref calls in general and catch this; Path::new is just an alternative spelling of as_ref in that case. Generally linting when foo(x) isn't needed just can't be done.

I'll look at this next week when I'm back from rust conf.

@ada4a

ada4a commented Sep 11, 2026

Copy link
Copy Markdown
Contributor Author

You could lint as_ref calls in general and catch this

Would you be okay with that being a separate lint? Mostly because a needless .as_ref() call sounds like something you'd want to remove pretty much always, so that lint could be warn-by-default, probably in complexity.

I'll look at this next week when I'm back from rust conf.

Enjoy the conference:)

RE: lintcheck: please do help with the panic in ty_based_def – should I be calling e.res(cx) or something beforehand in order to avoid it?

@ada4a ada4a changed the title WIP: New lint: needless_path_new New lint: needless_path_new Sep 11, 2026
@rustbot

rustbot commented Oct 11, 2026

Copy link
Copy Markdown
Collaborator

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

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs-fcp PRs that add, remove, or rename lints and need an FCP S-waiting-on-review Status: Awaiting review from the assignee but also interested parties

Projects

None yet

Development

Successfully merging this pull request may close these issues.

New lint: needless Path::new

6 participants