Skip to content

Commit 5385139

Browse files
committed
Extend iter_on to catch more cases
1 parent ecdea8c commit 5385139

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+785
-342
lines changed

clippy_lints/src/dereference.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ use rustc_span::{symbol::sym, Span, Symbol};
3434
use rustc_trait_selection::infer::InferCtxtExt as _;
3535
use rustc_trait_selection::traits::{query::evaluate_obligation::InferCtxtExt as _, Obligation, ObligationCause};
3636
use std::collections::VecDeque;
37+
use std::iter::once;
3738

3839
declare_clippy_lint! {
3940
/// ### What it does
@@ -893,7 +894,7 @@ fn walk_parents<'tcx>(
893894
&& infcx
894895
.type_implements_trait(
895896
trait_id,
896-
[impl_ty.into()].into_iter().chain(subs.iter().copied()),
897+
once(impl_ty.into()).chain(subs.iter().copied()),
897898
cx.param_env,
898899
)
899900
.must_apply_modulo_regions()

clippy_lints/src/manual_strip.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use std::iter::once;
2+
13
use clippy_utils::consts::{constant, Constant};
24
use clippy_utils::diagnostics::{multispan_sugg, span_lint_and_then};
35
use clippy_utils::msrvs::{self, Msrv};
@@ -113,11 +115,11 @@ impl<'tcx> LateLintPass<'tcx> for ManualStrip {
113115
multispan_sugg(
114116
diag,
115117
&format!("try using the `strip_{kind_word}` method"),
116-
vec![(test_span,
118+
once((test_span,
117119
format!("if let Some(<stripped>) = {}.strip_{kind_word}({}) ",
118120
snippet(cx, target_arg.span, ".."),
119-
snippet(cx, pattern.span, "..")))]
120-
.into_iter().chain(strippings.into_iter().map(|span| (span, "<stripped>".into()))),
121+
snippet(cx, pattern.span, ".."))))
122+
.chain(strippings.into_iter().map(|span| (span, "<stripped>".into()))),
121123
);
122124
});
123125
}

clippy_lints/src/methods/iter_on_single_or_empty_collections.rs

Lines changed: 0 additions & 92 deletions
This file was deleted.

clippy_lints/src/methods/mod.rs

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@ mod iter_kv_map;
4141
mod iter_next_slice;
4242
mod iter_nth;
4343
mod iter_nth_zero;
44-
mod iter_on_single_or_empty_collections;
4544
mod iter_overeager_cloned;
4645
mod iter_skip_next;
4746
mod iter_with_drain;
@@ -79,6 +78,7 @@ mod single_char_add_str;
7978
mod single_char_insert_string;
8079
mod single_char_pattern;
8180
mod single_char_push_string;
81+
mod single_or_empty_collections_iter;
8282
mod skip_while_next;
8383
mod stable_sort_primitive;
8484
mod str_splitn;
@@ -2437,7 +2437,7 @@ declare_clippy_lint! {
24372437
/// The type of the resulting iterator might become incompatible with its usage
24382438
#[clippy::version = "1.65.0"]
24392439
pub ITER_ON_SINGLE_ITEMS,
2440-
nursery,
2440+
pedantic,
24412441
"Iterator for array of length 1"
24422442
}
24432443

@@ -2469,7 +2469,7 @@ declare_clippy_lint! {
24692469
/// The type of the resulting iterator might become incompatible with its usage
24702470
#[clippy::version = "1.65.0"]
24712471
pub ITER_ON_EMPTY_COLLECTIONS,
2472-
nursery,
2472+
pedantic,
24732473
"Iterator for empty array"
24742474
}
24752475

@@ -3432,6 +3432,8 @@ fn method_call<'tcx>(
34323432

34333433
impl<'tcx> LateLintPass<'tcx> for Methods {
34343434
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx hir::Expr<'_>) {
3435+
single_or_empty_collections_iter::check(cx, expr, &self.msrv);
3436+
34353437
if expr.span.from_expansion() {
34363438
return;
34373439
}
@@ -3725,9 +3727,6 @@ impl Methods {
37253727
("is_digit", [radix]) => is_digit_ascii_radix::check(cx, expr, recv, radix, &self.msrv),
37263728
("is_none", []) => check_is_some_is_none(cx, expr, recv, false),
37273729
("is_some", []) => check_is_some_is_none(cx, expr, recv, true),
3728-
("iter" | "iter_mut" | "into_iter", []) => {
3729-
iter_on_single_or_empty_collections::check(cx, expr, name, recv);
3730-
},
37313730
("join", [join_arg]) => {
37323731
if let Some(("collect", _, _, span, _)) = method_call(recv) {
37333732
unnecessary_join::check(cx, expr, recv, join_arg, span);
Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
use clippy_utils::{
2+
diagnostics::span_lint_and_then,
3+
get_parent_expr,
4+
higher::VecArgs,
5+
is_diagnostic_item_or_ctor, is_lang_item_or_ctor, last_path_segment,
6+
macros::root_macro_call_first_node,
7+
msrvs::{Msrv, ITER_ONCE_AND_EMPTY},
8+
path_res,
9+
source::snippet_opt,
10+
std_or_core,
11+
};
12+
use rustc_errors::{Applicability, Diagnostic};
13+
use rustc_hir::{Expr, ExprKind, GenericArg, LangItem};
14+
use rustc_lint::{LateContext, Lint, LintContext};
15+
use rustc_middle::{
16+
lint::in_external_macro,
17+
ty::{Clause, PredicateKind},
18+
};
19+
use rustc_span::{sym, Span};
20+
21+
use super::{ITER_ON_EMPTY_COLLECTIONS, ITER_ON_SINGLE_ITEMS};
22+
23+
#[derive(Clone, Copy, Debug)]
24+
enum Variant<'tcx> {
25+
SomeToOnce,
26+
NoneToEmpty,
27+
OneLenToOnce(&'tcx Expr<'tcx>),
28+
ZeroLenToEmpty,
29+
}
30+
31+
impl<'tcx> Variant<'tcx> {
32+
fn as_lint(self) -> &'static Lint {
33+
match self {
34+
Self::SomeToOnce | Self::OneLenToOnce(_) => ITER_ON_SINGLE_ITEMS,
35+
Self::NoneToEmpty | Self::ZeroLenToEmpty => ITER_ON_EMPTY_COLLECTIONS,
36+
}
37+
}
38+
39+
fn as_str(self) -> &'static str {
40+
match self {
41+
Self::SomeToOnce => "Some",
42+
Self::NoneToEmpty => "None",
43+
Self::OneLenToOnce(_) => "[T; 1]",
44+
Self::ZeroLenToEmpty => "[T; 0]",
45+
}
46+
}
47+
48+
fn desc(self) -> &'static str {
49+
match self {
50+
Self::SomeToOnce | Self::OneLenToOnce(_) => "iterator with only one element",
51+
Self::NoneToEmpty | Self::ZeroLenToEmpty => "empty iterator",
52+
}
53+
}
54+
55+
fn sugg_fn(self, cx: &LateContext<'_>) -> Option<String> {
56+
Some(format!(
57+
"{}::iter::{}",
58+
std_or_core(cx)?,
59+
match self {
60+
Self::SomeToOnce | Self::OneLenToOnce(_) => "once",
61+
Self::NoneToEmpty | Self::ZeroLenToEmpty => "empty",
62+
},
63+
))
64+
}
65+
66+
fn turbofish_or_args_snippet(
67+
self,
68+
cx: &LateContext<'_>,
69+
expr: &Expr<'_>,
70+
method_name: Option<&str>,
71+
) -> Option<String> {
72+
let ref_prefix = ref_prefix(method_name.unwrap_or(""));
73+
74+
match self {
75+
Self::SomeToOnce if let ExprKind::Call(_, [arg]) = expr.kind => {
76+
snippet_opt(cx, arg.span).map(|s| format!("({ref_prefix}{s})"))
77+
},
78+
Self::NoneToEmpty if let ExprKind::Path(qpath) = expr.kind
79+
&& let Some(args) = last_path_segment(&qpath).args
80+
&& let [GenericArg::Type(ty)] = args.args =>
81+
{
82+
snippet_opt(cx, ty.span).map(|s| format!("::<{s}>()"))
83+
},
84+
Self::OneLenToOnce(one) => {
85+
snippet_opt(cx, one.span).map(|s| format!("({ref_prefix}{s})"))
86+
}
87+
Self::NoneToEmpty | Self::ZeroLenToEmpty => Some("()".to_owned()),
88+
Self::SomeToOnce => None,
89+
}
90+
}
91+
}
92+
93+
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, msrv: &Msrv) {
94+
if !msrv.meets(ITER_ONCE_AND_EMPTY) {
95+
return;
96+
}
97+
98+
let (variant, is_vec, sugg_span) = match expr.kind {
99+
// `[T; 1]`
100+
ExprKind::Array([one]) => (Variant::OneLenToOnce(one), false, expr.span),
101+
// `[T; 0]`
102+
ExprKind::Array([]) => (Variant::ZeroLenToEmpty, false, expr.span),
103+
// `Some`
104+
ExprKind::Call(path, _) if let Some(def_id) = path_res(cx, path).opt_def_id()
105+
&& is_lang_item_or_ctor(cx, def_id, LangItem::OptionSome) =>
106+
{
107+
(Variant::SomeToOnce, false, expr.span)
108+
},
109+
// `None`
110+
ExprKind::Path(qpath) if let Some(def_id) = cx.qpath_res(&qpath, expr.hir_id).opt_def_id()
111+
&& is_lang_item_or_ctor(cx, def_id, LangItem::OptionNone) =>
112+
{
113+
(Variant::NoneToEmpty, false, expr.span)
114+
}
115+
// `vec![]`
116+
_ if let Some(mac_call) = root_macro_call_first_node(cx, expr)
117+
&& let Some(VecArgs::Vec(elems)) = VecArgs::hir(cx, expr) =>
118+
{
119+
if let [one] = elems {
120+
(Variant::OneLenToOnce(one), true, mac_call.span.source_callsite())
121+
} else if elems.is_empty() {
122+
(Variant::ZeroLenToEmpty, true, mac_call.span.source_callsite())
123+
} else {
124+
return;
125+
}
126+
},
127+
_ => return,
128+
};
129+
130+
// `vec![]` must be external
131+
if !is_vec && in_external_macro(cx.sess(), expr.span) {
132+
return;
133+
}
134+
135+
// FIXME: `get_expr_use_or_unification_node` doesn't work here. Even with the exact same check
136+
// that the old code used. Please help
137+
138+
if let Some(parent) = get_parent_expr(cx, expr)
139+
&& let ExprKind::MethodCall(path, recv, args, _) = parent
140+
.peel_blocks()
141+
.peel_borrows()
142+
.kind
143+
{
144+
if recv.hir_id == expr.hir_id
145+
&& let method_name = path.ident.as_str()
146+
&& matches!(method_name, "iter" | "iter_mut" | "into_iter")
147+
{
148+
emit_lint(cx, expr, Some(path.ident.as_str()), parent.span, variant, |_| {});
149+
} else if args.iter().any(|arg| arg.hir_id == expr.hir_id)
150+
&& let Some(def_id) = cx.typeck_results().type_dependent_def_id(parent.hir_id)
151+
{
152+
for predicate in cx.tcx.param_env(def_id).caller_bounds() {
153+
match predicate.kind().skip_binder() {
154+
// FIXME: This doesn't take into account whether this `Clause` is related
155+
// to `arg`, afaik
156+
PredicateKind::Clause(Clause::Trait(trait_predicate))
157+
if is_diagnostic_item_or_ctor(cx, trait_predicate.def_id(), sym::Iterator) =>
158+
{
159+
emit_lint(cx, expr, None, sugg_span, variant, |diag| {
160+
diag.note("this method is generic over `Iterator`");
161+
});
162+
},
163+
_ => {},
164+
}
165+
}
166+
}
167+
}
168+
{}
169+
}
170+
171+
fn emit_lint<'tcx>(
172+
cx: &LateContext<'tcx>,
173+
expr: &Expr<'tcx>,
174+
method_name: Option<&str>,
175+
sugg_span: Span,
176+
variant: Variant<'_>,
177+
f: impl FnOnce(&mut Diagnostic),
178+
) {
179+
let Some(sugg_fn) = variant.sugg_fn(cx) else {
180+
return;
181+
};
182+
let Some(turbofish_or_args) = variant.turbofish_or_args_snippet(cx, expr, method_name) else {
183+
return;
184+
};
185+
186+
span_lint_and_then(
187+
cx,
188+
variant.as_lint(),
189+
expr.span.source_callsite(),
190+
&format!("usage of `{}` to create an {}", variant.as_str(), variant.desc()),
191+
|diag| {
192+
diag.span_suggestion(
193+
sugg_span,
194+
format!("use `{sugg_fn}` instead"),
195+
format!("{sugg_fn}{turbofish_or_args}"),
196+
Applicability::MachineApplicable,
197+
);
198+
f(diag);
199+
},
200+
);
201+
}
202+
203+
fn ref_prefix(method_name: &str) -> &str {
204+
match method_name {
205+
"iter" => "&",
206+
"iter_mut" => "&mut ",
207+
_ => "",
208+
}
209+
}

0 commit comments

Comments
 (0)