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