use clippy_utils::source::snippet_with_applicability; use clippy_utils::ty::is_type_diagnostic_item; use clippy_utils::{get_parent_expr, path_to_local_id, usage}; use if_chain::if_chain; use rustc_ast::ast; use rustc_errors::Applicability; use rustc_hir as hir; use rustc_hir::intravisit::{walk_expr, Visitor}; use rustc_hir::{BorrowKind, Expr, ExprKind, HirId, Mutability, Pat}; use rustc_lint::LateContext; use rustc_middle::hir::nested_filter; use rustc_middle::ty::{self, Ty}; use rustc_span::symbol::sym; pub(super) fn derefs_to_slice<'tcx>( cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'tcx>, ty: Ty<'tcx>, ) -> Option<&'tcx hir::Expr<'tcx>> { fn may_slice<'a>(cx: &LateContext<'a>, ty: Ty<'a>) -> bool { match ty.kind() { ty::Slice(_) => true, ty::Adt(def, _) if def.is_box() => may_slice(cx, ty.boxed_ty()), ty::Adt(..) => is_type_diagnostic_item(cx, ty, sym::Vec), ty::Array(_, size) => size.try_eval_usize(cx.tcx, cx.param_env).is_some(), ty::Ref(_, inner, _) => may_slice(cx, *inner), _ => false, } } if let hir::ExprKind::MethodCall(path, [self_arg, ..], _) = &expr.kind { if path.ident.name == sym::iter && may_slice(cx, cx.typeck_results().expr_ty(self_arg)) { Some(self_arg) } else { None } } else { match ty.kind() { ty::Slice(_) => Some(expr), ty::Adt(def, _) if def.is_box() && may_slice(cx, ty.boxed_ty()) => Some(expr), ty::Ref(_, inner, _) => { if may_slice(cx, *inner) { Some(expr) } else { None } }, _ => None, } } } pub(super) fn get_hint_if_single_char_arg( cx: &LateContext<'_>, arg: &hir::Expr<'_>, applicability: &mut Applicability, ) -> Option { if_chain! { if let hir::ExprKind::Lit(lit) = &arg.kind; if let ast::LitKind::Str(r, style) = lit.node; let string = r.as_str(); if string.chars().count() == 1; then { let snip = snippet_with_applicability(cx, arg.span, string, applicability); let ch = if let ast::StrStyle::Raw(nhash) = style { let nhash = nhash as usize; // for raw string: r##"a"## &snip[(nhash + 2)..(snip.len() - 1 - nhash)] } else { // for regular string: "a" &snip[1..(snip.len() - 1)] }; let hint = format!("'{}'", match ch { "'" => "\\'" , r"\" => "\\\\", _ => ch, }); Some(hint) } else { None } } } /// The core logic of `check_for_loop_iter` in `unnecessary_iter_cloned.rs`, this function wraps a /// use of `CloneOrCopyVisitor`. pub(super) fn clone_or_copy_needed<'tcx>( cx: &LateContext<'tcx>, pat: &Pat<'tcx>, body: &'tcx Expr<'tcx>, ) -> (bool, Vec<&'tcx Expr<'tcx>>) { let mut visitor = CloneOrCopyVisitor { cx, binding_hir_ids: pat_bindings(pat), clone_or_copy_needed: false, addr_of_exprs: Vec::new(), }; visitor.visit_expr(body); (visitor.clone_or_copy_needed, visitor.addr_of_exprs) } /// Returns a vector of all `HirId`s bound by the pattern. fn pat_bindings(pat: &Pat<'_>) -> Vec { let mut collector = usage::ParamBindingIdCollector { binding_hir_ids: Vec::new(), }; collector.visit_pat(pat); collector.binding_hir_ids } /// `clone_or_copy_needed` will be false when `CloneOrCopyVisitor` is done visiting if the only /// operations performed on `binding_hir_ids` are: /// * to take non-mutable references to them /// * to use them as non-mutable `&self` in method calls /// If any of `binding_hir_ids` is used in any other way, then `clone_or_copy_needed` will be true /// when `CloneOrCopyVisitor` is done visiting. struct CloneOrCopyVisitor<'cx, 'tcx> { cx: &'cx LateContext<'tcx>, binding_hir_ids: Vec, clone_or_copy_needed: bool, addr_of_exprs: Vec<&'tcx Expr<'tcx>>, } impl<'cx, 'tcx> Visitor<'tcx> for CloneOrCopyVisitor<'cx, 'tcx> { type NestedFilter = nested_filter::OnlyBodies; fn nested_visit_map(&mut self) -> Self::Map { self.cx.tcx.hir() } fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) { walk_expr(self, expr); if self.is_binding(expr) { if let Some(parent) = get_parent_expr(self.cx, expr) { match parent.kind { ExprKind::AddrOf(BorrowKind::Ref, Mutability::Not, _) => { self.addr_of_exprs.push(parent); return; }, ExprKind::MethodCall(_, args, _) => { if_chain! { if args.iter().skip(1).all(|arg| !self.is_binding(arg)); if let Some(method_def_id) = self.cx.typeck_results().type_dependent_def_id(parent.hir_id); let method_ty = self.cx.tcx.type_of(method_def_id); let self_ty = method_ty.fn_sig(self.cx.tcx).input(0).skip_binder(); if matches!(self_ty.kind(), ty::Ref(_, _, Mutability::Not)); then { return; } } }, _ => {}, } } self.clone_or_copy_needed = true; } } } impl<'cx, 'tcx> CloneOrCopyVisitor<'cx, 'tcx> { fn is_binding(&self, expr: &Expr<'tcx>) -> bool { self.binding_hir_ids .iter() .any(|hir_id| path_to_local_id(expr, *hir_id)) } }