rustc_lint/
map_unit_fn.rs

1use rustc_hir::{Expr, ExprKind, HirId, Stmt, StmtKind};
2use rustc_middle::ty::{self, Ty};
3use rustc_session::{declare_lint, declare_lint_pass};
4
5use crate::lints::MappingToUnit;
6use crate::{LateContext, LateLintPass, LintContext};
7
8declare_lint! {
9    /// The `map_unit_fn` lint checks for `Iterator::map` receive
10    /// a callable that returns `()`.
11    ///
12    /// ### Example
13    ///
14    /// ```rust
15    /// fn foo(items: &mut Vec<u8>) {
16    ///     items.sort();
17    /// }
18    ///
19    /// fn main() {
20    ///     let mut x: Vec<Vec<u8>> = vec![
21    ///         vec![0, 2, 1],
22    ///         vec![5, 4, 3],
23    ///     ];
24    ///     x.iter_mut().map(foo);
25    /// }
26    /// ```
27    ///
28    /// {{produces}}
29    ///
30    /// ### Explanation
31    ///
32    /// Mapping to `()` is almost always a mistake.
33    pub MAP_UNIT_FN,
34    Warn,
35    "`Iterator::map` call that discard the iterator's values"
36}
37
38declare_lint_pass!(MapUnitFn => [MAP_UNIT_FN]);
39
40impl<'tcx> LateLintPass<'tcx> for MapUnitFn {
41    fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &Stmt<'_>) {
42        if stmt.span.from_expansion() {
43            return;
44        }
45
46        if let StmtKind::Semi(expr) = stmt.kind {
47            if let ExprKind::MethodCall(path, receiver, args, span) = expr.kind {
48                if path.ident.name.as_str() == "map" {
49                    if receiver.span.from_expansion()
50                        || args.iter().any(|e| e.span.from_expansion())
51                        || !is_impl_slice(cx, receiver)
52                        || !is_diagnostic_name(cx, expr.hir_id, "IteratorMap")
53                    {
54                        return;
55                    }
56                    let arg_ty = cx.typeck_results().expr_ty(&args[0]);
57                    let default_span = args[0].span;
58                    if let ty::FnDef(id, _) = arg_ty.kind() {
59                        let fn_ty = cx.tcx.fn_sig(id).skip_binder();
60                        let ret_ty = fn_ty.output().skip_binder();
61                        if is_unit_type(ret_ty) {
62                            cx.emit_span_lint(
63                                MAP_UNIT_FN,
64                                span,
65                                MappingToUnit {
66                                    function_label: cx
67                                        .tcx
68                                        .span_of_impl(*id)
69                                        .unwrap_or(default_span),
70                                    argument_label: args[0].span,
71                                    map_label: span,
72                                    suggestion: path.ident.span,
73                                    replace: "for_each".to_string(),
74                                },
75                            )
76                        }
77                    } else if let ty::Closure(id, subs) = arg_ty.kind() {
78                        let cl_ty = subs.as_closure().sig();
79                        let ret_ty = cl_ty.output().skip_binder();
80                        if is_unit_type(ret_ty) {
81                            cx.emit_span_lint(
82                                MAP_UNIT_FN,
83                                span,
84                                MappingToUnit {
85                                    function_label: cx
86                                        .tcx
87                                        .span_of_impl(*id)
88                                        .unwrap_or(default_span),
89                                    argument_label: args[0].span,
90                                    map_label: span,
91                                    suggestion: path.ident.span,
92                                    replace: "for_each".to_string(),
93                                },
94                            )
95                        }
96                    }
97                }
98            }
99        }
100    }
101}
102
103fn is_impl_slice(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
104    if let Some(method_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) {
105        if let Some(impl_id) = cx.tcx.impl_of_method(method_id) {
106            return cx.tcx.type_of(impl_id).skip_binder().is_slice();
107        }
108    }
109    false
110}
111
112fn is_unit_type(ty: Ty<'_>) -> bool {
113    ty.is_unit() || ty.is_never()
114}
115
116fn is_diagnostic_name(cx: &LateContext<'_>, id: HirId, name: &str) -> bool {
117    if let Some(def_id) = cx.typeck_results().type_dependent_def_id(id) {
118        if let Some(item) = cx.tcx.get_diagnostic_name(def_id) {
119            if item.as_str() == name {
120                return true;
121            }
122        }
123    }
124    false
125}