rustc_lint/
map_unit_fn.rs

1use rustc_hir::{Expr, ExprKind, HirId, Stmt, StmtKind};
2use rustc_middle::query::Key;
3use rustc_middle::ty::{self, Ty};
4use rustc_session::{declare_lint, declare_lint_pass};
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        if stmt.span.from_expansion() {
44            return;
45        }
46
47        if let StmtKind::Semi(expr) = stmt.kind {
48            if let ExprKind::MethodCall(path, receiver, args, span) = expr.kind {
49                if path.ident.name.as_str() == "map" {
50                    if receiver.span.from_expansion()
51                        || args.iter().any(|e| e.span.from_expansion())
52                        || !is_impl_slice(cx, receiver)
53                        || !is_diagnostic_name(cx, expr.hir_id, "IteratorMap")
54                    {
55                        return;
56                    }
57                    let arg_ty = cx.typeck_results().expr_ty(&args[0]);
58                    let default_span = args[0].span;
59                    if let ty::FnDef(id, _) = arg_ty.kind() {
60                        let fn_ty = cx.tcx.fn_sig(id).skip_binder();
61                        let ret_ty = fn_ty.output().skip_binder();
62                        if is_unit_type(ret_ty) {
63                            cx.emit_span_lint(
64                                MAP_UNIT_FN,
65                                span,
66                                MappingToUnit {
67                                    function_label: cx
68                                        .tcx
69                                        .span_of_impl(*id)
70                                        .unwrap_or(default_span),
71                                    argument_label: args[0].span,
72                                    map_label: arg_ty.default_span(cx.tcx),
73                                    suggestion: path.ident.span,
74                                    replace: "for_each".to_string(),
75                                },
76                            )
77                        }
78                    } else if let ty::Closure(id, subs) = arg_ty.kind() {
79                        let cl_ty = subs.as_closure().sig();
80                        let ret_ty = cl_ty.output().skip_binder();
81                        if is_unit_type(ret_ty) {
82                            cx.emit_span_lint(
83                                MAP_UNIT_FN,
84                                span,
85                                MappingToUnit {
86                                    function_label: cx
87                                        .tcx
88                                        .span_of_impl(*id)
89                                        .unwrap_or(default_span),
90                                    argument_label: args[0].span,
91                                    map_label: arg_ty.default_span(cx.tcx),
92                                    suggestion: path.ident.span,
93                                    replace: "for_each".to_string(),
94                                },
95                            )
96                        }
97                    }
98                }
99            }
100        }
101    }
102}
103
104fn is_impl_slice(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
105    if let Some(method_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) {
106        if let Some(impl_id) = cx.tcx.impl_of_method(method_id) {
107            return cx.tcx.type_of(impl_id).skip_binder().is_slice();
108        }
109    }
110    false
111}
112
113fn is_unit_type(ty: Ty<'_>) -> bool {
114    ty.is_unit() || ty.is_never()
115}
116
117fn is_diagnostic_name(cx: &LateContext<'_>, id: HirId, name: &str) -> bool {
118    if let Some(def_id) = cx.typeck_results().type_dependent_def_id(id) {
119        if let Some(item) = cx.tcx.get_diagnostic_name(def_id) {
120            if item.as_str() == name {
121                return true;
122            }
123        }
124    }
125    false
126}