rustc_lint/
map_unit_fn.rs1use rustc_hir::{Expr, ExprKind, Stmt, StmtKind};
2use rustc_middle::ty::{self};
3use rustc_session::{declare_lint, declare_lint_pass};
4use rustc_span::sym;
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 let StmtKind::Semi(expr) = stmt.kind else {
44 return;
45 };
46 let ExprKind::MethodCall(path, receiver, [arg], span) = expr.kind else {
47 return;
48 };
49 if path.ident.name != sym::map
50 || stmt.span.from_expansion()
51 || receiver.span.from_expansion()
52 || arg.span.from_expansion()
53 || !is_impl_slice(cx, receiver)
54 || !cx
55 .typeck_results()
56 .type_dependent_def_id(expr.hir_id)
57 .is_some_and(|id| cx.tcx.is_diagnostic_item(sym::IteratorMap, id))
58 {
59 return;
60 }
61 let (id, sig) = match *cx.typeck_results().expr_ty(arg).kind() {
62 ty::Closure(id, subs) => (id, subs.as_closure().sig()),
63 ty::FnDef(id, _) => (id, cx.tcx.fn_sig(id).skip_binder()),
64 _ => return,
65 };
66 let ret_ty = sig.output().skip_binder();
67 if !(ret_ty.is_unit() || ret_ty.is_never()) {
68 return;
69 }
70 cx.emit_span_lint(
71 MAP_UNIT_FN,
72 span,
73 MappingToUnit {
74 function_label: cx.tcx.span_of_impl(id).unwrap_or(arg.span),
75 argument_label: arg.span,
76 map_label: span,
77 suggestion: path.ident.span,
78 },
79 );
80 }
81}
82
83fn is_impl_slice(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
84 if let Some(method_id) = cx.typeck_results().type_dependent_def_id(expr.hir_id)
85 && let Some(impl_id) = cx.tcx.impl_of_assoc(method_id)
86 {
87 return cx.tcx.type_of(impl_id).skip_binder().is_slice();
88 }
89 false
90}