]> git.proxmox.com Git - rustc.git/blame - compiler/rustc_lint/src/map_unit_fn.rs
New upstream version 1.70.0+dfsg1
[rustc.git] / compiler / rustc_lint / src / map_unit_fn.rs
CommitLineData
9ffffee4
FG
1use crate::lints::MappingToUnit;
2use crate::{LateContext, LateLintPass, LintContext};
3
4use rustc_hir::{Expr, ExprKind, HirId, Stmt, StmtKind};
5use rustc_middle::{
6 query::Key,
7 ty::{self, Ty},
8};
9
10declare_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
40declare_lint_pass!(MapUnitFn => [MAP_UNIT_FN]);
41
42impl<'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
105fn 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
114fn is_unit_type(ty: Ty<'_>) -> bool {
115 ty.is_unit() || ty.is_never()
116}
117
118fn 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}