-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
50 changed files
with
785 additions
and
342 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
92 changes: 0 additions & 92 deletions
92
clippy_lints/src/methods/iter_on_single_or_empty_collections.rs
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
209 changes: 209 additions & 0 deletions
209
clippy_lints/src/methods/single_or_empty_collections_iter.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,209 @@ | ||
use clippy_utils::{ | ||
diagnostics::span_lint_and_then, | ||
get_parent_expr, | ||
higher::VecArgs, | ||
is_diagnostic_item_or_ctor, is_lang_item_or_ctor, last_path_segment, | ||
macros::root_macro_call_first_node, | ||
msrvs::{Msrv, ITER_ONCE_AND_EMPTY}, | ||
path_res, | ||
source::snippet_opt, | ||
std_or_core, | ||
}; | ||
use rustc_errors::{Applicability, Diagnostic}; | ||
use rustc_hir::{Expr, ExprKind, GenericArg, LangItem}; | ||
use rustc_lint::{LateContext, Lint, LintContext}; | ||
use rustc_middle::{ | ||
lint::in_external_macro, | ||
ty::{Clause, PredicateKind}, | ||
}; | ||
use rustc_span::{sym, Span}; | ||
|
||
use super::{ITER_ON_EMPTY_COLLECTIONS, ITER_ON_SINGLE_ITEMS}; | ||
|
||
#[derive(Clone, Copy, Debug)] | ||
enum Variant<'tcx> { | ||
SomeToOnce, | ||
NoneToEmpty, | ||
OneLenToOnce(&'tcx Expr<'tcx>), | ||
ZeroLenToEmpty, | ||
} | ||
|
||
impl<'tcx> Variant<'tcx> { | ||
fn as_lint(self) -> &'static Lint { | ||
match self { | ||
Self::SomeToOnce | Self::OneLenToOnce(_) => ITER_ON_SINGLE_ITEMS, | ||
Self::NoneToEmpty | Self::ZeroLenToEmpty => ITER_ON_EMPTY_COLLECTIONS, | ||
} | ||
} | ||
|
||
fn as_str(self) -> &'static str { | ||
match self { | ||
Self::SomeToOnce => "Some", | ||
Self::NoneToEmpty => "None", | ||
Self::OneLenToOnce(_) => "[T; 1]", | ||
Self::ZeroLenToEmpty => "[T; 0]", | ||
} | ||
} | ||
|
||
fn desc(self) -> &'static str { | ||
match self { | ||
Self::SomeToOnce | Self::OneLenToOnce(_) => "iterator with only one element", | ||
Self::NoneToEmpty | Self::ZeroLenToEmpty => "empty iterator", | ||
} | ||
} | ||
|
||
fn sugg_fn(self, cx: &LateContext<'_>) -> Option<String> { | ||
Some(format!( | ||
"{}::iter::{}", | ||
std_or_core(cx)?, | ||
match self { | ||
Self::SomeToOnce | Self::OneLenToOnce(_) => "once", | ||
Self::NoneToEmpty | Self::ZeroLenToEmpty => "empty", | ||
}, | ||
)) | ||
} | ||
|
||
fn turbofish_or_args_snippet( | ||
self, | ||
cx: &LateContext<'_>, | ||
expr: &Expr<'_>, | ||
method_name: Option<&str>, | ||
) -> Option<String> { | ||
let ref_prefix = ref_prefix(method_name.unwrap_or("")); | ||
|
||
match self { | ||
Self::SomeToOnce if let ExprKind::Call(_, [arg]) = expr.kind => { | ||
snippet_opt(cx, arg.span).map(|s| format!("({ref_prefix}{s})")) | ||
}, | ||
Self::NoneToEmpty if let ExprKind::Path(qpath) = expr.kind | ||
&& let Some(args) = last_path_segment(&qpath).args | ||
&& let [GenericArg::Type(ty)] = args.args => | ||
{ | ||
snippet_opt(cx, ty.span).map(|s| format!("::<{s}>()")) | ||
}, | ||
Self::OneLenToOnce(one) => { | ||
snippet_opt(cx, one.span).map(|s| format!("({ref_prefix}{s})")) | ||
} | ||
Self::NoneToEmpty | Self::ZeroLenToEmpty => Some("()".to_owned()), | ||
Self::SomeToOnce => None, | ||
} | ||
} | ||
} | ||
|
||
pub(super) fn check<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>, msrv: &Msrv) { | ||
if !msrv.meets(ITER_ONCE_AND_EMPTY) { | ||
return; | ||
} | ||
|
||
let (variant, is_vec, sugg_span) = match expr.kind { | ||
// `[T; 1]` | ||
ExprKind::Array([one]) => (Variant::OneLenToOnce(one), false, expr.span), | ||
// `[T; 0]` | ||
ExprKind::Array([]) => (Variant::ZeroLenToEmpty, false, expr.span), | ||
// `Some` | ||
ExprKind::Call(path, _) if let Some(def_id) = path_res(cx, path).opt_def_id() | ||
&& is_lang_item_or_ctor(cx, def_id, LangItem::OptionSome) => | ||
{ | ||
(Variant::SomeToOnce, false, expr.span) | ||
}, | ||
// `None` | ||
ExprKind::Path(qpath) if let Some(def_id) = cx.qpath_res(&qpath, expr.hir_id).opt_def_id() | ||
&& is_lang_item_or_ctor(cx, def_id, LangItem::OptionNone) => | ||
{ | ||
(Variant::NoneToEmpty, false, expr.span) | ||
} | ||
// `vec![]` | ||
_ if let Some(mac_call) = root_macro_call_first_node(cx, expr) | ||
&& let Some(VecArgs::Vec(elems)) = VecArgs::hir(cx, expr) => | ||
{ | ||
if let [one] = elems { | ||
(Variant::OneLenToOnce(one), true, mac_call.span.source_callsite()) | ||
} else if elems.is_empty() { | ||
(Variant::ZeroLenToEmpty, true, mac_call.span.source_callsite()) | ||
} else { | ||
return; | ||
} | ||
}, | ||
_ => return, | ||
}; | ||
|
||
// `vec![]` must be external | ||
if !is_vec && in_external_macro(cx.sess(), expr.span) { | ||
return; | ||
} | ||
|
||
// FIXME: `get_expr_use_or_unification_node` doesn't work here. Even with the exact same check | ||
// that the old code used. Please help | ||
|
||
if let Some(parent) = get_parent_expr(cx, expr) | ||
&& let ExprKind::MethodCall(path, recv, args, _) = parent | ||
.peel_blocks() | ||
.peel_borrows() | ||
.kind | ||
{ | ||
if recv.hir_id == expr.hir_id | ||
&& let method_name = path.ident.as_str() | ||
&& matches!(method_name, "iter" | "iter_mut" | "into_iter") | ||
{ | ||
emit_lint(cx, expr, Some(path.ident.as_str()), parent.span, variant, |_| {}); | ||
} else if args.iter().any(|arg| arg.hir_id == expr.hir_id) | ||
&& let Some(def_id) = cx.typeck_results().type_dependent_def_id(parent.hir_id) | ||
{ | ||
for predicate in cx.tcx.param_env(def_id).caller_bounds() { | ||
match predicate.kind().skip_binder() { | ||
// FIXME: This doesn't take into account whether this `Clause` is related | ||
// to `arg`, afaik | ||
PredicateKind::Clause(Clause::Trait(trait_predicate)) | ||
if is_diagnostic_item_or_ctor(cx, trait_predicate.def_id(), sym::Iterator) => | ||
{ | ||
emit_lint(cx, expr, None, sugg_span, variant, |diag| { | ||
diag.note("this method is generic over `Iterator`"); | ||
}); | ||
}, | ||
_ => {}, | ||
} | ||
} | ||
} | ||
} | ||
{} | ||
} | ||
|
||
fn emit_lint<'tcx>( | ||
cx: &LateContext<'tcx>, | ||
expr: &Expr<'tcx>, | ||
method_name: Option<&str>, | ||
sugg_span: Span, | ||
variant: Variant<'_>, | ||
f: impl FnOnce(&mut Diagnostic), | ||
) { | ||
let Some(sugg_fn) = variant.sugg_fn(cx) else { | ||
return; | ||
}; | ||
let Some(turbofish_or_args) = variant.turbofish_or_args_snippet(cx, expr, method_name) else { | ||
return; | ||
}; | ||
|
||
span_lint_and_then( | ||
cx, | ||
variant.as_lint(), | ||
expr.span.source_callsite(), | ||
&format!("usage of `{}` to create an {}", variant.as_str(), variant.desc()), | ||
|diag| { | ||
diag.span_suggestion( | ||
sugg_span, | ||
format!("use `{sugg_fn}` instead"), | ||
format!("{sugg_fn}{turbofish_or_args}"), | ||
Applicability::MachineApplicable, | ||
); | ||
f(diag); | ||
}, | ||
); | ||
} | ||
|
||
fn ref_prefix(method_name: &str) -> &str { | ||
match method_name { | ||
"iter" => "&", | ||
"iter_mut" => "&mut ", | ||
_ => "", | ||
} | ||
} |
Oops, something went wrong.