Skip to content

Commit 76eee82

Browse files
committed
Auto merge of #12823 - schvv31n:fix-iter-on-empty-collections, r=y21
Suppress `iter_on_empty_collections` if the iterator's concrete type is relied upon changelog: fixed #12807
2 parents 7e4c1ae + 7439ecb commit 76eee82

File tree

4 files changed

+97
-2
lines changed

4 files changed

+97
-2
lines changed

clippy_lints/src/methods/iter_on_single_or_empty_collections.rs

+46-1
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
1+
use std::iter::once;
2+
13
use clippy_utils::diagnostics::span_lint_and_sugg;
24
use clippy_utils::source::snippet;
35
use clippy_utils::{get_expr_use_or_unification_node, is_res_lang_ctor, path_res, std_or_core};
46

57
use rustc_errors::Applicability;
8+
use rustc_hir::def_id::DefId;
9+
use rustc_hir::hir_id::HirId;
610
use rustc_hir::LangItem::{OptionNone, OptionSome};
711
use rustc_hir::{Expr, ExprKind, Node};
812
use rustc_lint::LateContext;
@@ -25,7 +29,29 @@ impl IterType {
2529
}
2630
}
2731

28-
pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, method_name: &str, recv: &Expr<'_>) {
32+
fn is_arg_ty_unified_in_fn<'tcx>(
33+
cx: &LateContext<'tcx>,
34+
fn_id: DefId,
35+
arg_id: HirId,
36+
args: impl IntoIterator<Item = &'tcx Expr<'tcx>>,
37+
) -> bool {
38+
let fn_sig = cx.tcx.fn_sig(fn_id).instantiate_identity();
39+
let arg_id_in_args = args.into_iter().position(|e| e.hir_id == arg_id).unwrap();
40+
let arg_ty_in_args = fn_sig.input(arg_id_in_args).skip_binder();
41+
42+
cx.tcx.predicates_of(fn_id).predicates.iter().any(|(clause, _)| {
43+
clause
44+
.as_projection_clause()
45+
.and_then(|p| p.map_bound(|p| p.term.ty()).transpose())
46+
.is_some_and(|ty| ty.skip_binder() == arg_ty_in_args)
47+
}) || fn_sig
48+
.inputs()
49+
.iter()
50+
.enumerate()
51+
.any(|(i, ty)| i != arg_id_in_args && ty.skip_binder().walk().any(|arg| arg.as_type() == Some(arg_ty_in_args)))
52+
}
53+
54+
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, method_name: &str, recv: &'tcx Expr<'tcx>) {
2955
let item = match recv.kind {
3056
ExprKind::Array([]) => None,
3157
ExprKind::Array([e]) => Some(e),
@@ -43,6 +69,25 @@ pub(super) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, method_name: &str, re
4369
let is_unified = match get_expr_use_or_unification_node(cx.tcx, expr) {
4470
Some((Node::Expr(parent), child_id)) => match parent.kind {
4571
ExprKind::If(e, _, _) | ExprKind::Match(e, _, _) if e.hir_id == child_id => false,
72+
ExprKind::Call(
73+
Expr {
74+
kind: ExprKind::Path(path),
75+
hir_id,
76+
..
77+
},
78+
args,
79+
) => cx
80+
.typeck_results()
81+
.qpath_res(path, *hir_id)
82+
.opt_def_id()
83+
.filter(|fn_id| cx.tcx.def_kind(fn_id).is_fn_like())
84+
.is_some_and(|fn_id| is_arg_ty_unified_in_fn(cx, fn_id, child_id, args)),
85+
ExprKind::MethodCall(_name, recv, args, _span) => is_arg_ty_unified_in_fn(
86+
cx,
87+
cx.typeck_results().type_dependent_def_id(parent.hir_id).unwrap(),
88+
child_id,
89+
once(recv).chain(args.iter()),
90+
),
4691
ExprKind::If(_, _, _)
4792
| ExprKind::Match(_, _, _)
4893
| ExprKind::Closure(_)

tests/ui/iter_on_empty_collections.fixed

+22
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,28 @@ fn array() {
2020
};
2121

2222
let _ = if false { ["test"].iter() } else { [].iter() };
23+
24+
let smth = Some(vec![1, 2, 3]);
25+
26+
// Don't trigger when the empty collection iter is relied upon for its concrete type
27+
// But do trigger if it is just an iterator, despite being an argument to a method
28+
for i in smth.as_ref().map_or([].iter(), |s| s.iter()).chain(std::iter::empty()) {
29+
println!("{i}");
30+
}
31+
32+
// Same as above, but for empty collection iters with extra layers
33+
for i in smth.as_ref().map_or({ [].iter() }, |s| s.iter()) {
34+
println!("{y}", y = i + 1);
35+
}
36+
37+
// Same as above, but for regular function calls
38+
for i in Option::map_or(smth.as_ref(), [].iter(), |s| s.iter()) {
39+
println!("{i}");
40+
}
41+
42+
// Same as above, but when there are no predicates that mention the collection iter type.
43+
let mut iter = [34, 228, 35].iter();
44+
let _ = std::mem::replace(&mut iter, [].iter());
2345
}
2446

2547
macro_rules! in_macros {

tests/ui/iter_on_empty_collections.rs

+22
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,28 @@ fn array() {
2020
};
2121

2222
let _ = if false { ["test"].iter() } else { [].iter() };
23+
24+
let smth = Some(vec![1, 2, 3]);
25+
26+
// Don't trigger when the empty collection iter is relied upon for its concrete type
27+
// But do trigger if it is just an iterator, despite being an argument to a method
28+
for i in smth.as_ref().map_or([].iter(), |s| s.iter()).chain([].iter()) {
29+
println!("{i}");
30+
}
31+
32+
// Same as above, but for empty collection iters with extra layers
33+
for i in smth.as_ref().map_or({ [].iter() }, |s| s.iter()) {
34+
println!("{y}", y = i + 1);
35+
}
36+
37+
// Same as above, but for regular function calls
38+
for i in Option::map_or(smth.as_ref(), [].iter(), |s| s.iter()) {
39+
println!("{i}");
40+
}
41+
42+
// Same as above, but when there are no predicates that mention the collection iter type.
43+
let mut iter = [34, 228, 35].iter();
44+
let _ = std::mem::replace(&mut iter, [].iter());
2345
}
2446

2547
macro_rules! in_macros {

tests/ui/iter_on_empty_collections.stderr

+7-1
Original file line numberDiff line numberDiff line change
@@ -37,5 +37,11 @@ error: `iter` call on an empty collection
3737
LL | assert_eq!(None.iter().next(), Option::<&i32>::None);
3838
| ^^^^^^^^^^^ help: try: `std::iter::empty()`
3939

40-
error: aborting due to 6 previous errors
40+
error: `iter` call on an empty collection
41+
--> tests/ui/iter_on_empty_collections.rs:28:66
42+
|
43+
LL | for i in smth.as_ref().map_or([].iter(), |s| s.iter()).chain([].iter()) {
44+
| ^^^^^^^^^ help: try: `std::iter::empty()`
45+
46+
error: aborting due to 7 previous errors
4147

0 commit comments

Comments
 (0)