| use clippy_utils::diagnostics::span_lint_and_sugg; |
| use clippy_utils::source::snippet_with_applicability; |
| use clippy_utils::ty::walk_ptrs_ty_depth; |
| use clippy_utils::{ |
| get_parent_expr, is_diag_trait_item, match_def_path, path_to_local_id, paths, peel_blocks, strip_pat_refs, |
| }; |
| use rustc_errors::Applicability; |
| use rustc_hir as hir; |
| use rustc_lint::LateContext; |
| use rustc_middle::ty::adjustment::Adjust; |
| use rustc_middle::ty::{Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, TypeVisitor}; |
| use rustc_span::{sym, Span}; |
| |
| use core::ops::ControlFlow; |
| |
| use super::USELESS_ASREF; |
| |
| /// Returns the first type inside the `Option`/`Result` type passed as argument. |
| fn get_enum_ty(enum_ty: Ty<'_>) -> Option<Ty<'_>> { |
| struct ContainsTyVisitor { |
| level: usize, |
| } |
| |
| impl<'tcx> TypeVisitor<TyCtxt<'tcx>> for ContainsTyVisitor { |
| type Result = ControlFlow<Ty<'tcx>>; |
| |
| fn visit_ty(&mut self, t: Ty<'tcx>) -> Self::Result { |
| self.level += 1; |
| if self.level == 1 { |
| t.super_visit_with(self) |
| } else { |
| ControlFlow::Break(t) |
| } |
| } |
| } |
| |
| match enum_ty.visit_with(&mut ContainsTyVisitor { level: 0 }) { |
| ControlFlow::Break(ty) => Some(ty), |
| ControlFlow::Continue(()) => None, |
| } |
| } |
| |
| /// Checks for the `USELESS_ASREF` lint. |
| pub(super) fn check(cx: &LateContext<'_>, expr: &hir::Expr<'_>, call_name: &str, recvr: &hir::Expr<'_>) { |
| // when we get here, we've already checked that the call name is "as_ref" or "as_mut" |
| // check if the call is to the actual `AsRef` or `AsMut` trait |
| let Some(def_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) else { |
| return; |
| }; |
| |
| if is_diag_trait_item(cx, def_id, sym::AsRef) || is_diag_trait_item(cx, def_id, sym::AsMut) { |
| // check if the type after `as_ref` or `as_mut` is the same as before |
| let rcv_ty = cx.typeck_results().expr_ty(recvr); |
| let res_ty = cx.typeck_results().expr_ty(expr); |
| let (base_res_ty, res_depth) = walk_ptrs_ty_depth(res_ty); |
| let (base_rcv_ty, rcv_depth) = walk_ptrs_ty_depth(rcv_ty); |
| if base_rcv_ty == base_res_ty && rcv_depth >= res_depth { |
| // allow the `as_ref` or `as_mut` if it is followed by another method call |
| if let Some(parent) = get_parent_expr(cx, expr) |
| && let hir::ExprKind::MethodCall(segment, ..) = parent.kind |
| && segment.ident.span != expr.span |
| { |
| return; |
| } |
| |
| let mut applicability = Applicability::MachineApplicable; |
| span_lint_and_sugg( |
| cx, |
| USELESS_ASREF, |
| expr.span, |
| &format!("this call to `{call_name}` does nothing"), |
| "try", |
| snippet_with_applicability(cx, recvr.span, "..", &mut applicability).to_string(), |
| applicability, |
| ); |
| } |
| } else if match_def_path(cx, def_id, &["core", "option", "Option", call_name]) |
| || match_def_path(cx, def_id, &["core", "result", "Result", call_name]) |
| { |
| let rcv_ty = cx.typeck_results().expr_ty(recvr).peel_refs(); |
| let res_ty = cx.typeck_results().expr_ty(expr).peel_refs(); |
| |
| if let Some(rcv_ty) = get_enum_ty(rcv_ty) |
| && let Some(res_ty) = get_enum_ty(res_ty) |
| // If the only thing the `as_mut`/`as_ref` call is doing is adding references and not |
| // changing the type, then we can move forward. |
| && rcv_ty.peel_refs() == res_ty.peel_refs() |
| && let Some(parent) = get_parent_expr(cx, expr) |
| && let hir::ExprKind::MethodCall(segment, _, args, _) = parent.kind |
| && segment.ident.span != expr.span |
| // We check that the called method name is `map`. |
| && segment.ident.name == sym::map |
| // And that it only has one argument. |
| && let [arg] = args |
| && is_calling_clone(cx, arg) |
| { |
| lint_as_ref_clone(cx, expr.span.with_hi(parent.span.hi()), recvr, call_name); |
| } |
| } |
| } |
| |
| fn check_qpath(cx: &LateContext<'_>, qpath: hir::QPath<'_>, hir_id: hir::HirId) -> bool { |
| // We check it's calling the `clone` method of the `Clone` trait. |
| if let Some(path_def_id) = cx.qpath_res(&qpath, hir_id).opt_def_id() { |
| match_def_path(cx, path_def_id, &paths::CLONE_TRAIT_METHOD) |
| } else { |
| false |
| } |
| } |
| |
| fn is_calling_clone(cx: &LateContext<'_>, arg: &hir::Expr<'_>) -> bool { |
| match arg.kind { |
| hir::ExprKind::Closure(&hir::Closure { body, .. }) |
| // If it's a closure, we need to check what is called. |
| if let closure_body = cx.tcx.hir().body(body) |
| && let [param] = closure_body.params |
| && let hir::PatKind::Binding(_, local_id, ..) = strip_pat_refs(param.pat).kind => |
| { |
| let closure_expr = peel_blocks(closure_body.value); |
| match closure_expr.kind { |
| hir::ExprKind::MethodCall(method, obj, [], _) => { |
| if method.ident.name == sym::clone |
| && let Some(fn_id) = cx.typeck_results().type_dependent_def_id(closure_expr.hir_id) |
| && let Some(trait_id) = cx.tcx.trait_of_item(fn_id) |
| // We check it's the `Clone` trait. |
| && cx.tcx.lang_items().clone_trait().map_or(false, |id| id == trait_id) |
| // no autoderefs |
| && !cx.typeck_results().expr_adjustments(obj).iter() |
| .any(|a| matches!(a.kind, Adjust::Deref(Some(..)))) |
| && path_to_local_id(obj, local_id) |
| { |
| true |
| } else { |
| false |
| } |
| }, |
| hir::ExprKind::Call(call, [recv]) => { |
| if let hir::ExprKind::Path(qpath) = call.kind |
| && path_to_local_id(recv, local_id) |
| { |
| check_qpath(cx, qpath, call.hir_id) |
| } else { |
| false |
| } |
| }, |
| _ => false, |
| } |
| }, |
| hir::ExprKind::Path(qpath) => check_qpath(cx, qpath, arg.hir_id), |
| _ => false, |
| } |
| } |
| |
| fn lint_as_ref_clone(cx: &LateContext<'_>, span: Span, recvr: &hir::Expr<'_>, call_name: &str) { |
| let mut applicability = Applicability::MachineApplicable; |
| span_lint_and_sugg( |
| cx, |
| USELESS_ASREF, |
| span, |
| &format!("this call to `{call_name}.map(...)` does nothing"), |
| "try", |
| format!( |
| "{}.clone()", |
| snippet_with_applicability(cx, recvr.span, "..", &mut applicability) |
| ), |
| applicability, |
| ); |
| } |