blob: fa394526bdf1ed1cc96ed7e97fcbdd0aa38b5b55 [file] [log] [blame] [edit]
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::res::{MaybeDef, MaybeTypeckRes};
use clippy_utils::source::snippet_with_applicability;
use clippy_utils::ty::is_copy;
use clippy_utils::{is_expr_untyped_identity_function, is_mutable, path_to_local_with_projections};
use rustc_errors::Applicability;
use rustc_hir::{self as hir, ExprKind, Node, PatKind};
use rustc_lint::{LateContext, LintContext};
use rustc_span::{Span, Symbol, sym};
use super::MAP_IDENTITY;
const MSG: &str = "unnecessary map of the identity function";
pub(super) fn check(
cx: &LateContext<'_>,
expr: &hir::Expr<'_>,
caller: &hir::Expr<'_>,
map_arg: &hir::Expr<'_>,
name: Symbol,
_map_span: Span,
) {
let caller_ty = cx.typeck_results().expr_ty(caller);
if (cx.ty_based_def(expr).opt_parent(cx).is_diag_item(cx, sym::Iterator)
|| caller_ty.is_diag_item(cx, sym::Result)
|| caller_ty.is_diag_item(cx, sym::Option))
&& is_expr_untyped_identity_function(cx, map_arg)
&& let Some(call_span) = expr.span.trim_start(caller.span)
{
span_lint_and_then(cx, MAP_IDENTITY, call_span, MSG, |diag| {
let main_sugg = (call_span, String::new());
let mut app = if is_copy(cx, caller_ty) {
// there is technically a behavioral change here for `Copy` iterators, where
// `iter.map(|x| x).next()` would mutate a temporary copy of the iterator and
// changing it to `iter.next()` mutates iter directly
Applicability::Unspecified
} else {
Applicability::MachineApplicable
};
let needs_to_be_mutable = cx.typeck_results().expr_ty_adjusted(expr).is_mutable_ptr();
if needs_to_be_mutable && !is_mutable(cx, caller) {
if let Some(hir_id) = path_to_local_with_projections(caller)
&& let Node::Pat(pat) = cx.tcx.hir_node(hir_id)
&& let PatKind::Binding(_, _, ident, _) = pat.kind
{
// We can reach the binding -- suggest making it mutable
let suggs = vec![main_sugg, (ident.span.shrink_to_lo(), String::from("mut "))];
let ident = snippet_with_applicability(cx.sess(), ident.span, "_", &mut app);
diag.multipart_suggestion(
format!("remove the call to `{name}`, and make `{ident}` mutable"),
suggs,
app,
);
} else {
// If we can't make the binding mutable, prevent the suggestion from being automatically applied,
// and add a complementary help message.
app = Applicability::Unspecified;
let method_requiring_mut = if let Node::Expr(expr) = cx.tcx.parent_hir_node(expr.hir_id)
&& let ExprKind::MethodCall(method, ..) = expr.kind
{
Some(method.ident)
} else {
None
};
diag.span_suggestion(main_sugg.0, format!("remove the call to `{name}`"), main_sugg.1, app);
let note = if let Some(method_requiring_mut) = method_requiring_mut {
format!("this must be made mutable to use `{method_requiring_mut}`")
} else {
"this must be made mutable".to_string()
};
diag.span_note(caller.span, note);
}
} else {
diag.span_suggestion(main_sugg.0, format!("remove the call to `{name}`"), main_sugg.1, app);
}
});
}
}