]>
Commit | Line | Data |
---|---|---|
9ffffee4 FG |
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 { | |
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]); | |
353b0b11 | 59 | let default_span = args[0].span; |
9ffffee4 FG |
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 { | |
353b0b11 FG |
68 | function_label: cx |
69 | .tcx | |
70 | .span_of_impl(*id) | |
71 | .unwrap_or(default_span), | |
9ffffee4 FG |
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 { | |
353b0b11 FG |
87 | function_label: cx |
88 | .tcx | |
89 | .span_of_impl(*id) | |
90 | .unwrap_or(default_span), | |
9ffffee4 FG |
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 | ||
105 | 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 | ||
114 | fn is_unit_type(ty: Ty<'_>) -> bool { | |
115 | ty.is_unit() || ty.is_never() | |
116 | } | |
117 | ||
118 | 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 | } |