-
Notifications
You must be signed in to change notification settings - Fork 1.8k
WIP: New lint: needless_path_new
#14895
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
base: master
Are you sure you want to change the base?
Conversation
I think you need to check that you can use a 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 |
oh, didn't know about that! thank you, that's very helpful
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 I guess what I need to check instead is that |
But the weirdest thing is that 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 let _: Option<impl AsRef<Path>>: Some(Path::new("foo.txt")); (assuming |
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(); |
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 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:
- The type of
type_definition.fn_sig(tcx)
isPolyFnSig
, which is an alias toBinder<'tcx, FnSig<'tcx>>
- that one has a
Binder::inputs
method, which returns aBinder<'tcx, &'tcx [Ty<'tcx>]>
- I can kind of iterate over that using
Binder::iter
, which finally gives meimpl Iterator<Item=Binder<'tcx, Ty<'tcx>>>
- 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
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.
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 Clone
able).
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?
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.
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
This comment has been minimized.
This comment has been minimized.
@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? |
This comment has been minimized.
This comment has been minimized.
Currently making the feature freeze feature and the performance project perform, I don't have the capacity right now to review this :/ r? clippy |
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 |
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 |
Reminder, once the PR becomes ready for a review, use |
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.
r? Jarcho
I can take over the review for this.
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 | ||
}) |
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.
This is better determined once we find a call to Path::new
lines up with a generic argument.
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 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)
99a3e7b
to
599b3d7
Compare
Lintcheck changes for 945d5e4
This comment will be updated if you push new changes |
84862cf
to
d014c46
Compare
d014c46
to
a5dd45b
Compare
This comment has been minimized.
This comment has been minimized.
a5dd45b
to
a7fb9ec
Compare
This comment has been minimized.
This comment has been minimized.
a7fb9ec
to
2feab73
Compare
2feab73
to
148fda1
Compare
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. |
148fda1
to
c7a07b9
Compare
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 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
.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, | ||
} | ||
}) |
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.
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
// 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, | ||
// }; |
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 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
c7a07b9
to
b5660db
Compare
b5660db
to
945d5e4
Compare
fs::read_to_string(Path::new("foo.txt"))
->fs::read_to_string("foo.txt")
Resolves #14668
changelog: [
needless_path_new
]: new lint