-
Notifications
You must be signed in to change notification settings - Fork 1.7k
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?
Changes from all commits
bf8e204
5047489
999cd13
fcce0c8
9ca18c9
04b418c
b4f57b5
a1fb385
a4fec0e
2df64de
72ccaae
15ab960
8c72115
99bcb6d
aa87d44
6324a5d
cd6ff62
bd2625e
c40aaf9
ef2bf79
0441c54
4dd365f
580fa39
cee5d5b
1e0a3f2
5b20b1f
a5f908e
f630401
d38e30d
c91bf12
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
use clippy_utils::diagnostics::span_lint_and_sugg; | ||
use clippy_utils::path_res; | ||
use clippy_utils::source::snippet; | ||
use clippy_utils::ty::implements_trait; | ||
use rustc_errors::Applicability; | ||
use rustc_hir::{Expr, ExprKind, QPath}; | ||
use rustc_lint::{LateContext, LateLintPass}; | ||
use rustc_middle::ty::{self, List, Ty}; | ||
use rustc_session::declare_lint_pass; | ||
use rustc_span::sym; | ||
use std::iter; | ||
|
||
declare_clippy_lint! { | ||
/// ### What it does | ||
/// Detects expressions being enclosed in `Path::new` when passed to a function that accepts | ||
/// `impl AsRef<Path>`, when the enclosed expression could be used. | ||
/// | ||
/// ### Why is this bad? | ||
/// It is unnecessarily verbose | ||
/// | ||
/// ### Example | ||
/// ```no_run | ||
/// # use std::{fs, path::Path}; | ||
/// fs::write(Path::new("foo.txt"), "foo"); | ||
/// ``` | ||
/// Use instead: | ||
/// ```no_run | ||
/// # use std::{fs, path::Path}; | ||
/// fs::write("foo.txt", "foo"); | ||
/// ``` | ||
#[clippy::version = "1.89.0"] | ||
pub NEEDLESS_PATH_NEW, | ||
nursery, | ||
"an argument passed to a function that accepts `impl AsRef<Path>` \ | ||
being enclosed in `Path::new` when the argument implements the trait" | ||
} | ||
|
||
declare_lint_pass!(NeedlessPathNew => [NEEDLESS_PATH_NEW]); | ||
|
||
impl<'tcx> LateLintPass<'tcx> for NeedlessPathNew { | ||
fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'tcx>) { | ||
match e.kind { | ||
ExprKind::Call(fn_expr, args) => { | ||
check_arguments(cx, &mut args.iter(), cx.typeck_results().expr_ty(fn_expr)); | ||
}, | ||
ExprKind::MethodCall(_, receiver, arguments, _) | ||
if let Some(def_id) = cx.typeck_results().type_dependent_def_id(e.hir_id) => | ||
{ | ||
let args = cx.typeck_results().node_args(e.hir_id); | ||
let method_type = cx.tcx.type_of(def_id).instantiate(cx.tcx, args); | ||
check_arguments(cx, &mut iter::once(receiver).chain(arguments.iter()), method_type); | ||
}, | ||
_ => (), | ||
} | ||
} | ||
} | ||
|
||
fn check_arguments<'tcx>( | ||
cx: &LateContext<'tcx>, | ||
arguments: &mut dyn Iterator<Item = &'tcx Expr<'tcx>>, | ||
type_definition: Ty<'tcx>, | ||
) { | ||
let tcx = cx.tcx; | ||
// whether `func` is `Path::new` | ||
let is_path_new = |func: &Expr<'_>| { | ||
if let ExprKind::Path(ref qpath) = func.kind | ||
&& let QPath::TypeRelative(ty, path) = qpath | ||
&& let Some(did) = path_res(cx, *ty).opt_def_id() | ||
&& tcx.is_diagnostic_item(sym::Path, did) | ||
&& path.ident.name == sym::new | ||
{ | ||
true | ||
} else { | ||
false | ||
} | ||
}; | ||
|
||
let Some(path_def_id) = tcx.get_diagnostic_item(sym::Path) else { | ||
return; | ||
}; | ||
let path_ty = Ty::new_adt(tcx, tcx.adt_def(path_def_id), List::empty()); | ||
let Some(asref_def_id) = tcx.get_diagnostic_item(sym::AsRef) else { | ||
return; | ||
}; | ||
|
||
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 commentThe 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 But the latter is just syntax sugar for Therefore I think we shouldn't actually calling
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 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 commentThe 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 |
||
for (argument, parameter) in iter::zip(arguments, parameters) { | ||
if let ExprKind::Call(func, args) = argument.kind | ||
&& is_path_new(func) | ||
&& implements_asref_path(cx.typeck_results().expr_ty(&args[0])) | ||
&& implements_asref_path(*parameter) | ||
{ | ||
span_lint_and_sugg( | ||
cx, | ||
NEEDLESS_PATH_NEW, | ||
argument.span, | ||
"the expression enclosed in `Path::new` implements `AsRef<Path>`", | ||
"remove the enclosing `Path::new`", | ||
format!("{}", snippet(cx, args[0].span, "..")), | ||
Applicability::MachineApplicable, | ||
); | ||
} | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
#![warn(clippy::needless_path_new)] | ||
|
||
use std::fs; | ||
use std::path::Path; | ||
|
||
fn takes_path(_: &Path) {} | ||
|
||
fn takes_path_and_impl_path(_: &Path, _: impl AsRef<Path>) {} | ||
|
||
fn takes_two_impl_paths_with_the_same_generic<P: AsRef<Path>>(_: P, _: P) {} | ||
|
||
struct Foo; | ||
|
||
impl Foo { | ||
fn takes_path(_: &Path) {} | ||
fn takes_self_and_path(&self, _: &Path) {} | ||
fn takes_path_and_impl_path(_: &Path, _: impl AsRef<Path>) {} | ||
fn takes_self_and_path_and_impl_path(&self, _: &Path, _: impl AsRef<Path>) {} | ||
} | ||
|
||
fn main() { | ||
let f = Foo; | ||
|
||
fs::write("foo.txt", "foo"); //~ needless_path_new | ||
|
||
fs::copy( | ||
"foo", //~ needless_path_new | ||
"bar", //~ needless_path_new | ||
); | ||
|
||
Foo::takes_path(Path::new("foo")); | ||
|
||
f.takes_self_and_path_and_impl_path( | ||
Path::new("foo"), | ||
"bar", //~ needless_path_new | ||
); | ||
|
||
// we can and should change both at the same time | ||
takes_two_impl_paths_with_the_same_generic( | ||
"foo", //~ needless_path_new | ||
"bar", //~ needless_path_new | ||
); | ||
|
||
// no warning | ||
takes_path(Path::new("foo")); | ||
|
||
// the paramater that _could_ be passed directly, was | ||
// the parameter that could't, wasn't | ||
takes_path_and_impl_path(Path::new("foo"), "bar"); | ||
|
||
// same but as a method | ||
Foo::takes_path_and_impl_path(Path::new("foo"), "bar"); | ||
f.takes_self_and_path_and_impl_path(Path::new("foo"), "bar"); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
#![warn(clippy::needless_path_new)] | ||
|
||
use std::fs; | ||
use std::path::Path; | ||
|
||
fn takes_path(_: &Path) {} | ||
|
||
fn takes_impl_path(_: impl AsRef<Path>) {} | ||
|
||
fn takes_path_and_impl_path(_: &Path, _: impl AsRef<Path>) {} | ||
|
||
fn takes_two_impl_paths_with_the_same_generic<P: AsRef<Path>>(_: P, _: P) {} | ||
|
||
struct Foo; | ||
|
||
impl Foo { | ||
fn takes_path(_: &Path) {} | ||
fn takes_self_and_path(&self, _: &Path) {} | ||
fn takes_path_and_impl_path(_: &Path, _: impl AsRef<Path>) {} | ||
fn takes_self_and_path_and_impl_path(&self, _: &Path, _: impl AsRef<Path>) {} | ||
} | ||
|
||
fn main() { | ||
let f = Foo; | ||
|
||
fs::write(Path::new("foo.txt"), "foo"); //~ needless_path_new | ||
|
||
fs::copy( | ||
Path::new("foo"), //~ needless_path_new | ||
Path::new("bar"), //~ needless_path_new | ||
); | ||
|
||
Foo::takes_path(Path::new("foo")); | ||
|
||
f.takes_self_and_path_and_impl_path( | ||
Path::new("foo"), | ||
Path::new("bar"), //~ needless_path_new | ||
); | ||
|
||
// we can and should change both at the same time | ||
takes_two_impl_paths_with_the_same_generic( | ||
Path::new("foo"), //~ needless_path_new | ||
Path::new("bar"), //~ needless_path_new | ||
); | ||
|
||
let a = takes_impl_path; | ||
|
||
a(Path::new("foo.txt")); //~ needless_path_new | ||
|
||
// no warning | ||
takes_path(Path::new("foo")); | ||
|
||
// the paramater that _could_ be passed directly, was | ||
// the parameter that could't, wasn't | ||
takes_path_and_impl_path(Path::new("foo"), "bar"); | ||
|
||
// same but as a method | ||
Foo::takes_path_and_impl_path(Path::new("foo"), "bar"); | ||
f.takes_self_and_path_and_impl_path(Path::new("foo"), "bar"); | ||
|
||
fn foo() -> Option<&'static Path> { | ||
// Some(...) is `ExprKind::Call`, but we don't consider it | ||
Some(Path::new("foo.txt")) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
error: the expression enclosed in `Path::new` implements `AsRef<Path>` | ||
--> tests/ui/needless_path_new.rs:24:15 | ||
| | ||
LL | fs::write(Path::new("foo.txt"), "foo"); | ||
| ^^^^^^^^^^^^^^^^^^^^ help: remove the enclosing `Path::new`: `"foo.txt"` | ||
| | ||
= note: `-D clippy::needless-path-new` implied by `-D warnings` | ||
= help: to override `-D warnings` add `#[allow(clippy::needless_path_new)]` | ||
|
||
error: the expression enclosed in `Path::new` implements `AsRef<Path>` | ||
--> tests/ui/needless_path_new.rs:27:9 | ||
| | ||
LL | Path::new("foo"), | ||
| ^^^^^^^^^^^^^^^^ help: remove the enclosing `Path::new`: `"foo"` | ||
|
||
error: the expression enclosed in `Path::new` implements `AsRef<Path>` | ||
--> tests/ui/needless_path_new.rs:28:9 | ||
| | ||
LL | Path::new("bar"), | ||
| ^^^^^^^^^^^^^^^^ help: remove the enclosing `Path::new`: `"bar"` | ||
|
||
error: the expression enclosed in `Path::new` implements `AsRef<Path>` | ||
--> tests/ui/needless_path_new.rs:35:9 | ||
| | ||
LL | Path::new("bar"), | ||
| ^^^^^^^^^^^^^^^^ help: remove the enclosing `Path::new`: `"bar"` | ||
|
||
error: the expression enclosed in `Path::new` implements `AsRef<Path>` | ||
--> tests/ui/needless_path_new.rs:40:9 | ||
| | ||
LL | Path::new("foo"), | ||
| ^^^^^^^^^^^^^^^^ help: remove the enclosing `Path::new`: `"foo"` | ||
|
||
error: the expression enclosed in `Path::new` implements `AsRef<Path>` | ||
--> tests/ui/needless_path_new.rs:41:9 | ||
| | ||
LL | Path::new("bar"), | ||
| ^^^^^^^^^^^^^^^^ help: remove the enclosing `Path::new`: `"bar"` | ||
|
||
error: aborting due to 6 previous errors | ||
|
Uh oh!
There was an error while loading. Please reload this page.
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 have this function signature from
mut_reference
-- should I maybe replace thedyn
withimpl
here?