blob: 6d841853fbe5f1dd570dd4a4fa10c094ec04fc68 [file] [log] [blame]
use clippy_utils::diagnostics::span_lint_and_then;
use clippy_utils::ty::{has_non_owning_mutable_access, implements_trait};
use clippy_utils::{is_mutable, is_trait_method, path_to_local, sym};
use rustc_errors::Applicability;
use rustc_hir::{Expr, Node, PatKind};
use rustc_lint::LateContext;
use rustc_middle::ty::Instance;
use rustc_span::Span;
use super::DOUBLE_ENDED_ITERATOR_LAST;
pub(super) fn check(cx: &LateContext<'_>, expr: &'_ Expr<'_>, self_expr: &'_ Expr<'_>, call_span: Span) {
let typeck = cx.typeck_results();
// if the "last" method is that of Iterator
if is_trait_method(cx, expr, sym::Iterator)
// if self implements DoubleEndedIterator
&& let Some(deiter_id) = cx.tcx.get_diagnostic_item(sym::DoubleEndedIterator)
&& let self_type = cx.typeck_results().expr_ty(self_expr)
&& implements_trait(cx, self_type.peel_refs(), deiter_id, &[])
// resolve the method definition
&& let id = typeck.type_dependent_def_id(expr.hir_id).unwrap()
&& let args = typeck.node_args(expr.hir_id)
&& let Ok(Some(fn_def)) = Instance::try_resolve(cx.tcx, cx.typing_env(), id, args)
// find the provided definition of Iterator::last
&& let Some(item) = cx.tcx.get_diagnostic_item(sym::Iterator)
&& let Some(last_def) = cx.tcx.provided_trait_methods(item).find(|m| m.name() == sym::last)
// if the resolved method is the same as the provided definition
&& fn_def.def_id() == last_def.def_id
&& let self_ty = cx.typeck_results().expr_ty(self_expr)
&& !has_non_owning_mutable_access(cx, self_ty)
{
let mut sugg = vec![(call_span, String::from("next_back()"))];
let mut dont_apply = false;
// if `self_expr` is a reference, it is mutable because it is used for `.last()`
// TODO: Change this to lint only when the referred iterator is not used later. If it is used later,
// changing to `next_back()` may change its behavior.
if !(is_mutable(cx, self_expr) || self_type.is_ref()) {
if let Some(hir_id) = path_to_local(self_expr)
&& let Node::Pat(pat) = cx.tcx.hir_node(hir_id)
&& let PatKind::Binding(_, _, ident, _) = pat.kind
{
sugg.push((ident.span.shrink_to_lo(), String::from("mut ")));
} else {
// If we can't make the binding mutable, make the suggestion `Unspecified` to prevent it from being
// automatically applied, and add a complementary help message.
dont_apply = true;
}
}
span_lint_and_then(
cx,
DOUBLE_ENDED_ITERATOR_LAST,
expr.span,
"called `Iterator::last` on a `DoubleEndedIterator`; this will needlessly iterate the entire iterator",
|diag| {
let expr_ty = cx.typeck_results().expr_ty(expr);
let droppable_elements = expr_ty.has_significant_drop(cx.tcx, cx.typing_env());
diag.multipart_suggestion(
"try",
sugg,
if dont_apply {
Applicability::Unspecified
} else if droppable_elements {
Applicability::MaybeIncorrect
} else {
Applicability::MachineApplicable
},
);
if droppable_elements {
diag.note("this change will alter drop order which may be undesirable");
}
if dont_apply {
diag.span_note(self_expr.span, "this must be made mutable to use `.next_back()`");
}
},
);
}
}