Skip to content

Commit e899684

Browse files
committed
Auto merge of rust-lang#12105 - GuillaumeGomez:useless-asf-extension, r=llogiq
Extend `useless_asref` lint on `map(clone)` If you have code like: ```rust Some(String::new()).as_ref().map(Clone::clone) ``` the `as_ref` call is unneeded. Interestingly enough, this lint and `map_clone` are starting to share a same "space" where both lints warn about different things for the same code. Not sure what's the policy about such cases though... r? `@llogiq` changelog: Extend `useless_asref` lint on `map(clone)`
2 parents 3b8323d + f7ef9a6 commit e899684

File tree

8 files changed

+265
-50
lines changed

8 files changed

+265
-50
lines changed

clippy_lints/src/methods/map_clone.rs

+28-3
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use clippy_utils::ty::{is_copy, is_type_diagnostic_item};
55
use clippy_utils::{is_diag_trait_item, match_def_path, paths, peel_blocks};
66
use rustc_errors::Applicability;
77
use rustc_hir as hir;
8+
use rustc_hir::def_id::DefId;
89
use rustc_lint::LateContext;
910
use rustc_middle::mir::Mutability;
1011
use rustc_middle::ty;
@@ -14,11 +15,35 @@ use rustc_span::{sym, Span};
1415

1516
use super::MAP_CLONE;
1617

18+
// If this `map` is called on an `Option` or a `Result` and the previous call is `as_ref`, we don't
19+
// run this lint because it would overlap with `useless_asref` which provides a better suggestion
20+
// in this case.
21+
fn should_run_lint(cx: &LateContext<'_>, e: &hir::Expr<'_>, method_id: DefId) -> bool {
22+
if is_diag_trait_item(cx, method_id, sym::Iterator) {
23+
return true;
24+
}
25+
// We check if it's an `Option` or a `Result`.
26+
if let Some(id) = cx.tcx.impl_of_method(method_id) {
27+
let identity = cx.tcx.type_of(id).instantiate_identity();
28+
if !is_type_diagnostic_item(cx, identity, sym::Option) && !is_type_diagnostic_item(cx, identity, sym::Result) {
29+
return false;
30+
}
31+
} else {
32+
return false;
33+
}
34+
// We check if the previous method call is `as_ref`.
35+
if let hir::ExprKind::MethodCall(path1, receiver, _, _) = &e.kind
36+
&& let hir::ExprKind::MethodCall(path2, _, _, _) = &receiver.kind
37+
{
38+
return path2.ident.name != sym::as_ref || path1.ident.name != sym::map;
39+
}
40+
41+
true
42+
}
43+
1744
pub(super) fn check(cx: &LateContext<'_>, e: &hir::Expr<'_>, recv: &hir::Expr<'_>, arg: &hir::Expr<'_>, msrv: &Msrv) {
1845
if let Some(method_id) = cx.typeck_results().type_dependent_def_id(e.hir_id)
19-
&& (cx.tcx.impl_of_method(method_id).map_or(false, |id| {
20-
is_type_diagnostic_item(cx, cx.tcx.type_of(id).instantiate_identity(), sym::Option)
21-
}) || is_diag_trait_item(cx, method_id, sym::Iterator))
46+
&& should_run_lint(cx, e, method_id)
2247
{
2348
match arg.kind {
2449
hir::ExprKind::Closure(&hir::Closure { body, .. }) => {

clippy_lints/src/methods/useless_asref.rs

+120-3
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,52 @@
11
use clippy_utils::diagnostics::span_lint_and_sugg;
22
use clippy_utils::source::snippet_with_applicability;
33
use clippy_utils::ty::walk_ptrs_ty_depth;
4-
use clippy_utils::{get_parent_expr, is_trait_method};
4+
use clippy_utils::{get_parent_expr, is_diag_trait_item, match_def_path, paths, peel_blocks};
55
use rustc_errors::Applicability;
66
use rustc_hir as hir;
77
use rustc_lint::LateContext;
8-
use rustc_span::sym;
8+
use rustc_middle::ty::adjustment::Adjust;
9+
use rustc_middle::ty::{Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, TypeVisitor};
10+
use rustc_span::{sym, Span};
11+
12+
use core::ops::ControlFlow;
913

1014
use super::USELESS_ASREF;
1115

16+
/// Returns the first type inside the `Option`/`Result` type passed as argument.
17+
fn get_enum_ty(enum_ty: Ty<'_>) -> Option<Ty<'_>> {
18+
struct ContainsTyVisitor {
19+
level: usize,
20+
}
21+
22+
impl<'tcx> TypeVisitor<TyCtxt<'tcx>> for ContainsTyVisitor {
23+
type BreakTy = Ty<'tcx>;
24+
25+
fn visit_ty(&mut self, t: Ty<'tcx>) -> ControlFlow<Self::BreakTy> {
26+
self.level += 1;
27+
if self.level == 1 {
28+
t.super_visit_with(self)
29+
} else {
30+
ControlFlow::Break(t)
31+
}
32+
}
33+
}
34+
35+
match enum_ty.visit_with(&mut ContainsTyVisitor { level: 0 }) {
36+
ControlFlow::Break(ty) => Some(ty),
37+
ControlFlow::Continue(()) => None,
38+
}
39+
}
40+
1241
/// Checks for the `USELESS_ASREF` lint.
1342
pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, call_name: &str, recvr: &hir::Expr<'_>) {
1443
// when we get here, we've already checked that the call name is "as_ref" or "as_mut"
1544
// check if the call is to the actual `AsRef` or `AsMut` trait
16-
if is_trait_method(cx, expr, sym::AsRef) || is_trait_method(cx, expr, sym::AsMut) {
45+
let Some(def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) else {
46+
return;
47+
};
48+
49+
if is_diag_trait_item(cx, def_id, sym::AsRef) || is_diag_trait_item(cx, def_id, sym::AsMut) {
1750
// check if the type after `as_ref` or `as_mut` is the same as before
1851
let rcv_ty = cx.typeck_results().expr_ty(recvr);
1952
let res_ty = cx.typeck_results().expr_ty(expr);
@@ -39,5 +72,89 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, call_name: &str,
3972
applicability,
4073
);
4174
}
75+
} else if match_def_path(cx, def_id, &["core", "option", "Option", call_name])
76+
|| match_def_path(cx, def_id, &["core", "result", "Result", call_name])
77+
{
78+
let rcv_ty = cx.typeck_results().expr_ty(recvr).peel_refs();
79+
let res_ty = cx.typeck_results().expr_ty(expr).peel_refs();
80+
81+
if let Some(rcv_ty) = get_enum_ty(rcv_ty)
82+
&& let Some(res_ty) = get_enum_ty(res_ty)
83+
// If the only thing the `as_mut`/`as_ref` call is doing is adding references and not
84+
// changing the type, then we can move forward.
85+
&& rcv_ty.peel_refs() == res_ty.peel_refs()
86+
&& let Some(parent) = get_parent_expr(cx, expr)
87+
&& let hir::ExprKind::MethodCall(segment, _, args, _) = parent.kind
88+
&& segment.ident.span != expr.span
89+
// We check that the called method name is `map`.
90+
&& segment.ident.name == sym::map
91+
// And that it only has one argument.
92+
&& let [arg] = args
93+
&& is_calling_clone(cx, arg)
94+
{
95+
lint_as_ref_clone(cx, expr.span.with_hi(parent.span.hi()), recvr, call_name);
96+
}
97+
}
98+
}
99+
100+
fn check_qpath(cx: &LateContext<'_>, qpath: hir::QPath<'_>, hir_id: hir::HirId) -> bool {
101+
// We check it's calling the `clone` method of the `Clone` trait.
102+
if let Some(path_def_id) = cx.qpath_res(&qpath, hir_id).opt_def_id() {
103+
match_def_path(cx, path_def_id, &paths::CLONE_TRAIT_METHOD)
104+
} else {
105+
false
42106
}
43107
}
108+
109+
fn is_calling_clone(cx: &LateContext<'_>, arg: &hir::Expr<'_>) -> bool {
110+
match arg.kind {
111+
hir::ExprKind::Closure(&hir::Closure { body, .. }) => {
112+
// If it's a closure, we need to check what is called.
113+
let closure_body = cx.tcx.hir().body(body);
114+
let closure_expr = peel_blocks(closure_body.value);
115+
match closure_expr.kind {
116+
hir::ExprKind::MethodCall(method, obj, [], _) => {
117+
if method.ident.name == sym::clone
118+
&& let Some(fn_id) = cx.typeck_results().type_dependent_def_id(closure_expr.hir_id)
119+
&& let Some(trait_id) = cx.tcx.trait_of_item(fn_id)
120+
// We check it's the `Clone` trait.
121+
&& cx.tcx.lang_items().clone_trait().map_or(false, |id| id == trait_id)
122+
// no autoderefs
123+
&& !cx.typeck_results().expr_adjustments(obj).iter()
124+
.any(|a| matches!(a.kind, Adjust::Deref(Some(..))))
125+
{
126+
true
127+
} else {
128+
false
129+
}
130+
},
131+
hir::ExprKind::Call(call, [_]) => {
132+
if let hir::ExprKind::Path(qpath) = call.kind {
133+
check_qpath(cx, qpath, call.hir_id)
134+
} else {
135+
false
136+
}
137+
},
138+
_ => false,
139+
}
140+
},
141+
hir::ExprKind::Path(qpath) => check_qpath(cx, qpath, arg.hir_id),
142+
_ => false,
143+
}
144+
}
145+
146+
fn lint_as_ref_clone(cx: &LateContext<'_>, span: Span, recvr: &hir::Expr<'_>, call_name: &str) {
147+
let mut applicability = Applicability::MachineApplicable;
148+
span_lint_and_sugg(
149+
cx,
150+
USELESS_ASREF,
151+
span,
152+
&format!("this call to `{call_name}.map(...)` does nothing"),
153+
"try",
154+
format!(
155+
"{}.clone()",
156+
snippet_with_applicability(cx, recvr.span, "..", &mut applicability)
157+
),
158+
applicability,
159+
);
160+
}

tests/ui/map_clone.fixed

+20-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
clippy::many_single_char_names,
66
clippy::redundant_clone,
77
clippy::redundant_closure,
8+
clippy::useless_asref,
89
clippy::useless_vec
910
)]
1011

@@ -63,6 +64,24 @@ fn main() {
6364
}
6465

6566
let x = Some(String::new());
66-
let y = x.as_ref().cloned();
67+
let x = x.as_ref(); // We do this to prevent triggering the `useless_asref` lint.
68+
let y = x.cloned();
6769
//~^ ERROR: you are explicitly cloning with `.map()`
70+
let y = x.cloned();
71+
//~^ ERROR: you are explicitly cloning with `.map()`
72+
let y = x.cloned();
73+
//~^ ERROR: you are explicitly cloning with `.map()`
74+
75+
// Testing with `Result` now.
76+
let x: Result<String, ()> = Ok(String::new());
77+
let x = x.as_ref(); // We do this to prevent triggering the `useless_asref` lint.
78+
let y = x.cloned();
79+
//~^ ERROR: you are explicitly cloning with `.map()`
80+
let y = x.cloned();
81+
82+
// We ensure that no warning is emitted here because `useless_asref` is taking over.
83+
let x = Some(String::new());
84+
let y = x.as_ref().map(|x| String::clone(x));
85+
let x: Result<String, ()> = Ok(String::new());
86+
let y = x.as_ref().map(|x| String::clone(x));
6887
}

tests/ui/map_clone.rs

+20-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
clippy::many_single_char_names,
66
clippy::redundant_clone,
77
clippy::redundant_closure,
8+
clippy::useless_asref,
89
clippy::useless_vec
910
)]
1011

@@ -63,6 +64,24 @@ fn main() {
6364
}
6465

6566
let x = Some(String::new());
66-
let y = x.as_ref().map(|x| String::clone(x));
67+
let x = x.as_ref(); // We do this to prevent triggering the `useless_asref` lint.
68+
let y = x.map(|x| String::clone(x));
69+
//~^ ERROR: you are explicitly cloning with `.map()`
70+
let y = x.map(Clone::clone);
71+
//~^ ERROR: you are explicitly cloning with `.map()`
72+
let y = x.map(String::clone);
73+
//~^ ERROR: you are explicitly cloning with `.map()`
74+
75+
// Testing with `Result` now.
76+
let x: Result<String, ()> = Ok(String::new());
77+
let x = x.as_ref(); // We do this to prevent triggering the `useless_asref` lint.
78+
let y = x.map(|x| String::clone(x));
6779
//~^ ERROR: you are explicitly cloning with `.map()`
80+
let y = x.map(|x| String::clone(x));
81+
82+
// We ensure that no warning is emitted here because `useless_asref` is taking over.
83+
let x = Some(String::new());
84+
let y = x.as_ref().map(|x| String::clone(x));
85+
let x: Result<String, ()> = Ok(String::new());
86+
let y = x.as_ref().map(|x| String::clone(x));
6887
}

tests/ui/map_clone.stderr

+34-10
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
error: you are using an explicit closure for copying elements
2-
--> $DIR/map_clone.rs:12:22
2+
--> $DIR/map_clone.rs:13:22
33
|
44
LL | let _: Vec<i8> = vec![5_i8; 6].iter().map(|x| *x).collect();
55
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling the dedicated `copied` method: `vec![5_i8; 6].iter().copied()`
@@ -8,40 +8,64 @@ LL | let _: Vec<i8> = vec![5_i8; 6].iter().map(|x| *x).collect();
88
= help: to override `-D warnings` add `#[allow(clippy::map_clone)]`
99

1010
error: you are using an explicit closure for cloning elements
11-
--> $DIR/map_clone.rs:13:26
11+
--> $DIR/map_clone.rs:14:26
1212
|
1313
LL | let _: Vec<String> = vec![String::new()].iter().map(|x| x.clone()).collect();
1414
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling the dedicated `cloned` method: `vec![String::new()].iter().cloned()`
1515

1616
error: you are using an explicit closure for copying elements
17-
--> $DIR/map_clone.rs:14:23
17+
--> $DIR/map_clone.rs:15:23
1818
|
1919
LL | let _: Vec<u32> = vec![42, 43].iter().map(|&x| x).collect();
2020
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling the dedicated `copied` method: `vec![42, 43].iter().copied()`
2121

2222
error: you are using an explicit closure for copying elements
23-
--> $DIR/map_clone.rs:16:26
23+
--> $DIR/map_clone.rs:17:26
2424
|
2525
LL | let _: Option<u64> = Some(&16).map(|b| *b);
2626
| ^^^^^^^^^^^^^^^^^^^^^ help: consider calling the dedicated `copied` method: `Some(&16).copied()`
2727

2828
error: you are using an explicit closure for copying elements
29-
--> $DIR/map_clone.rs:17:25
29+
--> $DIR/map_clone.rs:18:25
3030
|
3131
LL | let _: Option<u8> = Some(&1).map(|x| x.clone());
3232
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling the dedicated `copied` method: `Some(&1).copied()`
3333

3434
error: you are needlessly cloning iterator elements
35-
--> $DIR/map_clone.rs:28:29
35+
--> $DIR/map_clone.rs:29:29
3636
|
3737
LL | let _ = std::env::args().map(|v| v.clone());
3838
| ^^^^^^^^^^^^^^^^^^^ help: remove the `map` call
3939

4040
error: you are explicitly cloning with `.map()`
41-
--> $DIR/map_clone.rs:66:13
41+
--> $DIR/map_clone.rs:68:13
4242
|
43-
LL | let y = x.as_ref().map(|x| String::clone(x));
44-
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling the dedicated `cloned` method: `x.as_ref().cloned()`
43+
LL | let y = x.map(|x| String::clone(x));
44+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling the dedicated `cloned` method: `x.cloned()`
4545

46-
error: aborting due to 7 previous errors
46+
error: you are explicitly cloning with `.map()`
47+
--> $DIR/map_clone.rs:70:13
48+
|
49+
LL | let y = x.map(Clone::clone);
50+
| ^^^^^^^^^^^^^^^^^^^ help: consider calling the dedicated `cloned` method: `x.cloned()`
51+
52+
error: you are explicitly cloning with `.map()`
53+
--> $DIR/map_clone.rs:72:13
54+
|
55+
LL | let y = x.map(String::clone);
56+
| ^^^^^^^^^^^^^^^^^^^^ help: consider calling the dedicated `cloned` method: `x.cloned()`
57+
58+
error: you are explicitly cloning with `.map()`
59+
--> $DIR/map_clone.rs:78:13
60+
|
61+
LL | let y = x.map(|x| String::clone(x));
62+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling the dedicated `cloned` method: `x.cloned()`
63+
64+
error: you are explicitly cloning with `.map()`
65+
--> $DIR/map_clone.rs:80:13
66+
|
67+
LL | let y = x.map(|x| String::clone(x));
68+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider calling the dedicated `cloned` method: `x.cloned()`
69+
70+
error: aborting due to 11 previous errors
4771

tests/ui/useless_asref.fixed

+9-5
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22
#![allow(
33
clippy::explicit_auto_deref,
44
clippy::uninlined_format_args,
5-
clippy::needless_pass_by_ref_mut
5+
clippy::map_clone,
6+
clippy::needless_pass_by_ref_mut,
7+
clippy::redundant_closure
68
)]
79

810
use std::fmt::Debug;
@@ -134,10 +136,12 @@ fn generic_ok<U: AsMut<T> + AsRef<T> + ?Sized, T: Debug + ?Sized>(mru: &mut U) {
134136

135137
fn foo() {
136138
let x = Some(String::new());
137-
let y = x.as_ref().cloned();
138-
//~^ ERROR: you are explicitly cloning with `.map()`
139-
let y = x.as_ref().cloned();
140-
//~^ ERROR: you are explicitly cloning with `.map()`
139+
let z = x.clone();
140+
//~^ ERROR: this call to `as_ref.map(...)` does nothing
141+
let z = x.clone();
142+
//~^ ERROR: this call to `as_ref.map(...)` does nothing
143+
let z = x.clone();
144+
//~^ ERROR: this call to `as_ref.map(...)` does nothing
141145
}
142146

143147
fn main() {

tests/ui/useless_asref.rs

+9-5
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22
#![allow(
33
clippy::explicit_auto_deref,
44
clippy::uninlined_format_args,
5-
clippy::needless_pass_by_ref_mut
5+
clippy::map_clone,
6+
clippy::needless_pass_by_ref_mut,
7+
clippy::redundant_closure
68
)]
79

810
use std::fmt::Debug;
@@ -134,10 +136,12 @@ fn generic_ok<U: AsMut<T> + AsRef<T> + ?Sized, T: Debug + ?Sized>(mru: &mut U) {
134136

135137
fn foo() {
136138
let x = Some(String::new());
137-
let y = x.as_ref().map(Clone::clone);
138-
//~^ ERROR: you are explicitly cloning with `.map()`
139-
let y = x.as_ref().map(String::clone);
140-
//~^ ERROR: you are explicitly cloning with `.map()`
139+
let z = x.as_ref().map(String::clone);
140+
//~^ ERROR: this call to `as_ref.map(...)` does nothing
141+
let z = x.as_ref().map(|z| z.clone());
142+
//~^ ERROR: this call to `as_ref.map(...)` does nothing
143+
let z = x.as_ref().map(|z| String::clone(z));
144+
//~^ ERROR: this call to `as_ref.map(...)` does nothing
141145
}
142146

143147
fn main() {

0 commit comments

Comments
 (0)