rustc_lint/
map_unit_fn.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
use rustc_hir::{Expr, ExprKind, HirId, Stmt, StmtKind};
use rustc_middle::query::Key;
use rustc_middle::ty::{self, Ty};
use rustc_session::{declare_lint, declare_lint_pass};

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<u8>) {
    ///     items.sort();
    /// }
    ///
    /// fn main() {
    ///     let mut x: Vec<Vec<u8>> = 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<'_>) {
        if stmt.span.from_expansion() {
            return;
        }

        if let StmtKind::Semi(expr) = stmt.kind {
            if let ExprKind::MethodCall(path, receiver, args, span) = expr.kind {
                if path.ident.name.as_str() == "map" {
                    if receiver.span.from_expansion()
                        || args.iter().any(|e| e.span.from_expansion())
                        || !is_impl_slice(cx, receiver)
                        || !is_diagnostic_name(cx, expr.hir_id, "IteratorMap")
                    {
                        return;
                    }
                    let arg_ty = cx.typeck_results().expr_ty(&args[0]);
                    let default_span = args[0].span;
                    if let ty::FnDef(id, _) = arg_ty.kind() {
                        let fn_ty = cx.tcx.fn_sig(id).skip_binder();
                        let ret_ty = fn_ty.output().skip_binder();
                        if is_unit_type(ret_ty) {
                            cx.emit_span_lint(MAP_UNIT_FN, span, MappingToUnit {
                                function_label: cx.tcx.span_of_impl(*id).unwrap_or(default_span),
                                argument_label: args[0].span,
                                map_label: arg_ty.default_span(cx.tcx),
                                suggestion: path.ident.span,
                                replace: "for_each".to_string(),
                            })
                        }
                    } else if let ty::Closure(id, subs) = arg_ty.kind() {
                        let cl_ty = subs.as_closure().sig();
                        let ret_ty = cl_ty.output().skip_binder();
                        if is_unit_type(ret_ty) {
                            cx.emit_span_lint(MAP_UNIT_FN, span, MappingToUnit {
                                function_label: cx.tcx.span_of_impl(*id).unwrap_or(default_span),
                                argument_label: args[0].span,
                                map_label: arg_ty.default_span(cx.tcx),
                                suggestion: path.ident.span,
                                replace: "for_each".to_string(),
                            })
                        }
                    }
                }
            }
        }
    }
}

fn is_impl_slice(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
    if let Some(method_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id) {
        if let Some(impl_id) = cx.tcx.impl_of_method(method_id) {
            return cx.tcx.type_of(impl_id).skip_binder().is_slice();
        }
    }
    false
}

fn is_unit_type(ty: Ty<'_>) -> bool {
    ty.is_unit() || ty.is_never()
}

fn is_diagnostic_name(cx: &LateContext<'_>, id: HirId, name: &str) -> bool {
    if let Some(def_id) = cx.typeck_results().type_dependent_def_id(id) {
        if let Some(item) = cx.tcx.get_diagnostic_name(def_id) {
            if item.as_str() == name {
                return true;
            }
        }
    }
    false
}