blob: e378cbd6ae0adba3150c0e183c02a86c5d4e9725 [file] [log] [blame]
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::sugg::Sugg;
use rustc_ast::BorrowKind;
use rustc_errors::{Applicability, Diag};
use rustc_hir::{Expr, ExprKind, Node, QPath};
use rustc_lint::LateContext;
use rustc_middle::ty::adjustment::Adjust;
use rustc_span::sym;
use super::SWAP_WITH_TEMPORARY;
const MSG_TEMPORARY: &str = "this expression returns a temporary value";
const MSG_TEMPORARY_REFMUT: &str = "this is a mutable reference to a temporary value";
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'_>, func: &Expr<'_>, args: &'tcx [Expr<'_>]) {
if let ExprKind::Path(QPath::Resolved(_, func_path)) = func.kind
&& let Some(func_def_id) = func_path.res.opt_def_id()
&& cx.tcx.is_diagnostic_item(sym::mem_swap, func_def_id)
{
match (ArgKind::new(cx, &args[0]), ArgKind::new(cx, &args[1])) {
(ArgKind::RefMutToTemp(left_temp), ArgKind::RefMutToTemp(right_temp)) => {
emit_lint_useless(cx, expr, &args[0], &args[1], left_temp, right_temp);
},
(ArgKind::RefMutToTemp(left_temp), right) => emit_lint_assign(cx, expr, &right, &args[0], left_temp),
(left, ArgKind::RefMutToTemp(right_temp)) => emit_lint_assign(cx, expr, &left, &args[1], right_temp),
_ => {},
}
}
}
enum ArgKind<'tcx> {
// Mutable reference to a place, coming from a macro, and number of dereferences to use
RefMutToPlaceAsMacro(&'tcx Expr<'tcx>, usize),
// Place behind a mutable reference, and number of dereferences to use
RefMutToPlace(&'tcx Expr<'tcx>, usize),
// Temporary value behind a mutable reference
RefMutToTemp(&'tcx Expr<'tcx>),
// Any other case
Expr(&'tcx Expr<'tcx>),
}
impl<'tcx> ArgKind<'tcx> {
/// Build a new `ArgKind` from `arg`. There must be no false positive when returning a
/// `ArgKind::RefMutToTemp` variant, as this may cause a spurious lint to be emitted.
fn new(cx: &LateContext<'tcx>, arg: &'tcx Expr<'tcx>) -> Self {
if let ExprKind::AddrOf(BorrowKind::Ref, _, target) = arg.kind
&& let adjustments = cx.typeck_results().expr_adjustments(arg)
&& adjustments
.first()
.is_some_and(|adj| matches!(adj.kind, Adjust::Deref(None)))
&& adjustments
.last()
.is_some_and(|adj| matches!(adj.kind, Adjust::Borrow(_)))
{
let extra_derefs = adjustments[1..adjustments.len() - 1]
.iter()
.filter(|adj| matches!(adj.kind, Adjust::Deref(_)))
.count();
// If a deref is used, `arg` might be a place expression. For example, a mutex guard
// would dereference into the mutex content which is probably not temporary.
if target.is_syntactic_place_expr() || extra_derefs > 0 {
if arg.span.from_expansion() {
ArgKind::RefMutToPlaceAsMacro(arg, extra_derefs)
} else {
ArgKind::RefMutToPlace(target, extra_derefs)
}
} else {
ArgKind::RefMutToTemp(target)
}
} else {
ArgKind::Expr(arg)
}
}
}
// Emits a note either on the temporary expression if it can be found in the same context as the
// base and returns `true`, or on the mutable reference to the temporary expression otherwise and
// returns `false`.
fn emit_note(diag: &mut Diag<'_, ()>, base: &Expr<'_>, expr: &Expr<'_>, expr_temp: &Expr<'_>) -> bool {
if base.span.eq_ctxt(expr.span) {
diag.span_note(expr_temp.span.source_callsite(), MSG_TEMPORARY);
true
} else {
diag.span_note(expr.span.source_callsite(), MSG_TEMPORARY_REFMUT);
false
}
}
fn emit_lint_useless(
cx: &LateContext<'_>,
expr: &Expr<'_>,
left: &Expr<'_>,
right: &Expr<'_>,
left_temp: &Expr<'_>,
right_temp: &Expr<'_>,
) {
span_lint_and_then(
cx,
SWAP_WITH_TEMPORARY,
expr.span,
"swapping temporary values has no effect",
|diag| {
emit_note(diag, expr, left, left_temp);
emit_note(diag, expr, right, right_temp);
},
);
}
fn emit_lint_assign(cx: &LateContext<'_>, expr: &Expr<'_>, target: &ArgKind<'_>, reftemp: &Expr<'_>, temp: &Expr<'_>) {
span_lint_and_then(
cx,
SWAP_WITH_TEMPORARY,
expr.span,
"swapping with a temporary value is inefficient",
|diag| {
if !emit_note(diag, expr, reftemp, temp) {
return;
}
// Make the suggestion only when the original `swap()` call is a statement
// or the last expression in a block.
if matches!(cx.tcx.parent_hir_node(expr.hir_id), Node::Stmt(..) | Node::Block(..)) {
let mut applicability = Applicability::MachineApplicable;
let ctxt = expr.span.ctxt();
let assign_target = match target {
ArgKind::Expr(target) => Sugg::hir_with_context(cx, target, ctxt, "_", &mut applicability).deref(),
ArgKind::RefMutToPlaceAsMacro(arg, derefs) => (0..*derefs).fold(
Sugg::hir_with_context(cx, arg, ctxt, "_", &mut applicability).deref(),
|sugg, _| sugg.deref(),
),
ArgKind::RefMutToPlace(target, derefs) => (0..*derefs).fold(
Sugg::hir_with_context(cx, target, ctxt, "_", &mut applicability),
|sugg, _| sugg.deref(),
),
ArgKind::RefMutToTemp(_) => unreachable!(),
};
let assign_source = Sugg::hir_with_context(cx, temp, ctxt, "_", &mut applicability);
diag.span_suggestion(
expr.span,
"use assignment instead",
format!("{assign_target} = {assign_source}"),
applicability,
);
}
},
);
}