rustc_lint/
map_unit_fn.rs

1use rustc_hir::{Expr, ExprKind, Stmt, StmtKind};
2use rustc_middle::ty::{self};
3use rustc_session::{declare_lint, declare_lint_pass};
4use rustc_span::sym;
5
6use crate::lints::MappingToUnit;
7use crate::{LateContext, LateLintPass, LintContext};
8
9declare_lint! {
10    /// The `map_unit_fn` lint checks for `Iterator::map` receive
11    /// a callable that returns `()`.
12    ///
13    /// ### Example
14    ///
15    /// ```rust
16    /// fn foo(items: &mut Vec<u8>) {
17    ///     items.sort();
18    /// }
19    ///
20    /// fn main() {
21    ///     let mut x: Vec<Vec<u8>> = vec![
22    ///         vec![0, 2, 1],
23    ///         vec![5, 4, 3],
24    ///     ];
25    ///     x.iter_mut().map(foo);
26    /// }
27    /// ```
28    ///
29    /// {{produces}}
30    ///
31    /// ### Explanation
32    ///
33    /// Mapping to `()` is almost always a mistake.
34    pub MAP_UNIT_FN,
35    Warn,
36    "`Iterator::map` call that discard the iterator's values"
37}
38
39declare_lint_pass!(MapUnitFn => [MAP_UNIT_FN]);
40
41impl<'tcx> LateLintPass<'tcx> for MapUnitFn {
42    fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &Stmt<'_>) {
43        let StmtKind::Semi(expr) = stmt.kind else {
44            return;
45        };
46        let ExprKind::MethodCall(path, receiver, [arg], span) = expr.kind else {
47            return;
48        };
49        if path.ident.name != sym::map
50            || stmt.span.from_expansion()
51            || receiver.span.from_expansion()
52            || arg.span.from_expansion()
53            || !is_impl_slice(cx, receiver)
54            || !cx
55                .typeck_results()
56                .type_dependent_def_id(expr.hir_id)
57                .is_some_and(|id| cx.tcx.is_diagnostic_item(sym::IteratorMap, id))
58        {
59            return;
60        }
61        let (id, sig) = match *cx.typeck_results().expr_ty(arg).kind() {
62            ty::Closure(id, subs) => (id, subs.as_closure().sig()),
63            ty::FnDef(id, _) => (id, cx.tcx.fn_sig(id).skip_binder()),
64            _ => return,
65        };
66        let ret_ty = sig.output().skip_binder();
67        if !(ret_ty.is_unit() || ret_ty.is_never()) {
68            return;
69        }
70        cx.emit_span_lint(
71            MAP_UNIT_FN,
72            span,
73            MappingToUnit {
74                function_label: cx.tcx.span_of_impl(id).unwrap_or(arg.span),
75                argument_label: arg.span,
76                map_label: span,
77                suggestion: path.ident.span,
78            },
79        );
80    }
81}
82
83fn is_impl_slice(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
84    if let Some(method_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id)
85        && let Some(impl_id) = cx.tcx.impl_of_assoc(method_id)
86    {
87        return cx.tcx.type_of(impl_id).skip_binder().is_slice();
88    }
89    false
90}