use rustc_hir::{Expr, ExprKind, Stmt, StmtKind}; use rustc_middle::ty::{self}; use rustc_session::{declare_lint, declare_lint_pass}; use rustc_span::sym; use crate::lints::MappingToUnit; use crate::{LateContext, LateLintPass, LintContext}; declare_lint! { /// The `map_unit_fn` lint checks for `Iterator::map` receive /// a callable that returns `()`. /// /// ### Example /// /// ```rust /// fn foo(items: &mut Vec) { /// items.sort(); /// } /// /// fn main() { /// let mut x: Vec> = vec![ /// vec![0, 2, 1], /// vec![5, 4, 3], /// ]; /// x.iter_mut().map(foo); /// } /// ``` /// /// {{produces}} /// /// ### Explanation /// /// Mapping to `()` is almost always a mistake. pub MAP_UNIT_FN, Warn, "`Iterator::map` call that discard the iterator's values" } declare_lint_pass!(MapUnitFn => [MAP_UNIT_FN]); impl<'tcx> LateLintPass<'tcx> for MapUnitFn { fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &Stmt<'_>) { let StmtKind::Semi(expr) = stmt.kind else { return; }; let ExprKind::MethodCall(path, receiver, [arg], span) = expr.kind else { return; }; if path.ident.name != sym::map || stmt.span.from_expansion() || receiver.span.from_expansion() || arg.span.from_expansion() || !is_impl_slice(cx, receiver) || !cx .typeck_results() .type_dependent_def_id(expr.hir_id) .is_some_and(|id| cx.tcx.is_diagnostic_item(sym::IteratorMap, id)) { return; } let (id, sig) = match *cx.typeck_results().expr_ty(arg).kind() { ty::Closure(id, subs) => (id, subs.as_closure().sig()), ty::FnDef(id, _) => (id, cx.tcx.fn_sig(id).skip_binder()), _ => return, }; let ret_ty = sig.output().skip_binder(); if !(ret_ty.is_unit() || ret_ty.is_never()) { return; } cx.emit_span_lint( MAP_UNIT_FN, span, MappingToUnit { function_label: cx.tcx.span_of_impl(id).unwrap_or(arg.span), argument_label: arg.span, map_label: span, suggestion: path.ident.span, }, ); } } fn is_impl_slice(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool { if let Some(method_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) && let Some(impl_id) = cx.tcx.impl_of_assoc(method_id) { return cx.tcx.type_of(impl_id).skip_binder().is_slice(); } false }