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 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}