Skip to content

Commit 02b3d3d

Browse files
Extend useless_asref lint on map(clone)
1 parent af35d37 commit 02b3d3d

File tree

1 file changed

+132
-3
lines changed

1 file changed

+132
-3
lines changed

clippy_lints/src/methods/useless_asref.rs

Lines changed: 132 additions & 3 deletions
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,101 @@ 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+
{
94+
match arg.kind {
95+
hir::ExprKind::Closure(&hir::Closure { body, .. }) => {
96+
// If it's a closure, we need to check what is called.
97+
let closure_body = cx.tcx.hir().body(body);
98+
let closure_expr = peel_blocks(closure_body.value);
99+
match closure_expr.kind {
100+
hir::ExprKind::MethodCall(method, obj, [], _) => {
101+
if method.ident.name == sym::clone
102+
&& let Some(fn_id) = cx.typeck_results().type_dependent_def_id(closure_expr.hir_id)
103+
&& let Some(trait_id) = cx.tcx.trait_of_item(fn_id)
104+
// We check it's the `Clone` trait.
105+
&& cx.tcx.lang_items().clone_trait().map_or(false, |id| id == trait_id)
106+
// no autoderefs
107+
&& !cx.typeck_results().expr_adjustments(obj).iter()
108+
.any(|a| matches!(a.kind, Adjust::Deref(Some(..))))
109+
{
110+
lint_as_ref_clone(cx, expr.span.with_hi(parent.span.hi()), recvr, call_name);
111+
}
112+
},
113+
hir::ExprKind::Call(call, [_]) => {
114+
if let hir::ExprKind::Path(qpath) = call.kind {
115+
check_qpath(
116+
cx,
117+
expr.span.with_hi(parent.span.hi()),
118+
recvr,
119+
call_name,
120+
qpath,
121+
call.hir_id,
122+
);
123+
}
124+
},
125+
_ => {},
126+
}
127+
},
128+
hir::ExprKind::Path(qpath) => check_qpath(
129+
cx,
130+
expr.span.with_hi(parent.span.hi()),
131+
recvr,
132+
call_name,
133+
qpath,
134+
arg.hir_id,
135+
),
136+
_ => {},
137+
}
138+
}
42139
}
43140
}
141+
142+
fn check_qpath(
143+
cx: &LateContext<'_>,
144+
span: Span,
145+
recvr: &hir::Expr<'_>,
146+
call_name: &str,
147+
qpath: hir::QPath<'_>,
148+
hir_id: hir::HirId,
149+
) {
150+
// We check it's calling the `clone` method of the `Clone` trait.
151+
if let Some(path_def_id) = cx.qpath_res(&qpath, hir_id).opt_def_id()
152+
&& match_def_path(cx, path_def_id, &paths::CLONE_TRAIT_METHOD)
153+
{
154+
lint_as_ref_clone(cx, span, recvr, call_name);
155+
}
156+
}
157+
158+
fn lint_as_ref_clone(cx: &LateContext<'_>, span: Span, recvr: &hir::Expr<'_>, call_name: &str) {
159+
let mut applicability = Applicability::MachineApplicable;
160+
span_lint_and_sugg(
161+
cx,
162+
USELESS_ASREF,
163+
span,
164+
&format!("this call to `{call_name}.map(...)` does nothing"),
165+
"try",
166+
format!(
167+
"{}.clone()",
168+
snippet_with_applicability(cx, recvr.span, "..", &mut applicability)
169+
),
170+
applicability,
171+
);
172+
}

0 commit comments

Comments
 (0)