169 lines
5.7 KiB
Rust
169 lines
5.7 KiB
Rust
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<String> {
|
|
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<HirId> {
|
|
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<HirId>,
|
|
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))
|
|
}
|
|
}
|