Skip to content

Commit b87e189

Browse files
committed
Implement manual flatten lint
1 parent 8973f2c commit b87e189

File tree

13 files changed

+208
-106
lines changed

13 files changed

+208
-106
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1969,7 +1969,6 @@ Released 2018-09-13
19691969
[`fn_to_numeric_cast_with_truncation`]: https://rust-lang.github.io/rust-clippy/master/index.html#fn_to_numeric_cast_with_truncation
19701970
[`for_kv_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#for_kv_map
19711971
[`for_loops_over_fallibles`]: https://rust-lang.github.io/rust-clippy/master/index.html#for_loops_over_fallibles
1972-
[`for_loops_over_options_or_results`]: https://rust-lang.github.io/rust-clippy/master/index.html#for_loops_over_options_or_results
19731972
[`forget_copy`]: https://rust-lang.github.io/rust-clippy/master/index.html#forget_copy
19741973
[`forget_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#forget_ref
19751974
[`from_iter_instead_of_collect`]: https://rust-lang.github.io/rust-clippy/master/index.html#from_iter_instead_of_collect
@@ -2040,6 +2039,7 @@ Released 2018-09-13
20402039
[`manual_async_fn`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_async_fn
20412040
[`manual_filter_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_filter_map
20422041
[`manual_find_map`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_find_map
2042+
[`manual_flatten`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_flatten
20432043
[`manual_memcpy`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_memcpy
20442044
[`manual_non_exhaustive`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_non_exhaustive
20452045
[`manual_ok_or`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_ok_or

clippy_lints/src/lib.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -685,8 +685,8 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
685685
&loops::EXPLICIT_ITER_LOOP,
686686
&loops::FOR_KV_MAP,
687687
&loops::FOR_LOOPS_OVER_FALLIBLES,
688-
&loops::FOR_LOOPS_OVER_OPTIONS_OR_RESULTS,
689688
&loops::ITER_NEXT_LOOP,
689+
&loops::MANUAL_FLATTEN,
690690
&loops::MANUAL_MEMCPY,
691691
&loops::MUT_RANGE_BOUND,
692692
&loops::NEEDLESS_COLLECT,
@@ -1489,8 +1489,8 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
14891489
LintId::of(&loops::EXPLICIT_COUNTER_LOOP),
14901490
LintId::of(&loops::FOR_KV_MAP),
14911491
LintId::of(&loops::FOR_LOOPS_OVER_FALLIBLES),
1492-
LintId::of(&loops::FOR_LOOPS_OVER_OPTIONS_OR_RESULTS),
14931492
LintId::of(&loops::ITER_NEXT_LOOP),
1493+
LintId::of(&loops::MANUAL_FLATTEN),
14941494
LintId::of(&loops::MANUAL_MEMCPY),
14951495
LintId::of(&loops::MUT_RANGE_BOUND),
14961496
LintId::of(&loops::NEEDLESS_COLLECT),
@@ -1822,7 +1822,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
18221822
LintId::of(&lifetimes::EXTRA_UNUSED_LIFETIMES),
18231823
LintId::of(&lifetimes::NEEDLESS_LIFETIMES),
18241824
LintId::of(&loops::EXPLICIT_COUNTER_LOOP),
1825-
LintId::of(&loops::FOR_LOOPS_OVER_OPTIONS_OR_RESULTS),
1825+
LintId::of(&loops::MANUAL_FLATTEN),
18261826
LintId::of(&loops::MUT_RANGE_BOUND),
18271827
LintId::of(&loops::SINGLE_ELEMENT_LOOP),
18281828
LintId::of(&loops::WHILE_LET_LOOP),

clippy_lints/src/loops.rs

Lines changed: 57 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,10 @@ use crate::utils::usage::{is_unused, mutated_variables};
55
use crate::utils::visitors::LocalUsedVisitor;
66
use crate::utils::{
77
contains_name, get_enclosing_block, get_parent_expr, get_trait_def_id, has_iter_method, higher, implements_trait,
8-
indent_of, is_in_panic_handler, is_integer_const, is_no_std_crate, is_refutable, is_type_diagnostic_item,
9-
last_path_segment, match_trait_method, match_type, match_var, multispan_sugg, single_segment_path, snippet,
10-
snippet_with_applicability, snippet_with_macro_callsite, span_lint, span_lint_and_help, span_lint_and_sugg,
11-
span_lint_and_then, sugg, SpanlessEq,
8+
indent_of, is_in_panic_handler, is_integer_const, is_no_std_crate, is_ok_ctor, is_refutable, is_some_ctor,
9+
is_type_diagnostic_item, last_path_segment, match_trait_method, match_type, match_var, multispan_sugg,
10+
single_segment_path, snippet, snippet_with_applicability, snippet_with_macro_callsite, span_lint,
11+
span_lint_and_help, span_lint_and_sugg, span_lint_and_then, sugg, SpanlessEq,
1212
};
1313
use if_chain::if_chain;
1414
use rustc_ast::ast;
@@ -495,8 +495,8 @@ declare_clippy_lint! {
495495
}
496496

497497
declare_clippy_lint! {
498-
/// **What it does:** Checks for iteration of `Option`s with
499-
/// a single `if let Some()` expression inside.
498+
/// **What it does:** Check for unnecessary `if let` usage in a for loop
499+
/// where only the `Some` or `Ok` variant of the iterator element is used.
500500
///
501501
/// **Why is this bad?** It is verbose and can be simplified
502502
/// by first calling the `flatten` method on the `Iterator`.
@@ -516,23 +516,23 @@ declare_clippy_lint! {
516516
/// Use instead:
517517
/// ```rust
518518
/// let x = vec![Some(1), Some(2), Some(3)];
519-
/// for n in x.iter().flatten() {
519+
/// for n in x.into_iter().flatten() {
520520
/// println!("{}", n);
521521
/// }
522522
/// ```
523-
pub FOR_LOOPS_OVER_OPTIONS_OR_RESULTS,
523+
pub MANUAL_FLATTEN,
524524
complexity,
525525
"for loops over `Option`s or `Result`s with a single expression can be simplified"
526526
}
527527

528528
declare_lint_pass!(Loops => [
529529
MANUAL_MEMCPY,
530+
MANUAL_FLATTEN,
530531
NEEDLESS_RANGE_LOOP,
531532
EXPLICIT_ITER_LOOP,
532533
EXPLICIT_INTO_ITER_LOOP,
533534
ITER_NEXT_LOOP,
534535
FOR_LOOPS_OVER_FALLIBLES,
535-
FOR_LOOPS_OVER_OPTIONS_OR_RESULTS,
536536
WHILE_LET_LOOP,
537537
NEEDLESS_COLLECT,
538538
EXPLICIT_COUNTER_LOOP,
@@ -549,14 +549,14 @@ declare_lint_pass!(Loops => [
549549
impl<'tcx> LateLintPass<'tcx> for Loops {
550550
#[allow(clippy::too_many_lines)]
551551
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
552-
if let Some((pat, arg, body)) = higher::for_loop(expr) {
552+
if let Some((pat, arg, body, span)) = higher::for_loop(expr) {
553553
// we don't want to check expanded macros
554554
// this check is not at the top of the function
555555
// since higher::for_loop expressions are marked as expansions
556556
if body.span.from_expansion() {
557557
return;
558558
}
559-
check_for_loop(cx, pat, arg, body, expr);
559+
check_for_loop(cx, pat, arg, body, expr, span);
560560
}
561561

562562
// we don't want to check expanded macros
@@ -851,6 +851,7 @@ fn check_for_loop<'tcx>(
851851
arg: &'tcx Expr<'_>,
852852
body: &'tcx Expr<'_>,
853853
expr: &'tcx Expr<'_>,
854+
span: Span,
854855
) {
855856
let is_manual_memcpy_triggered = detect_manual_memcpy(cx, pat, arg, body, expr);
856857
if !is_manual_memcpy_triggered {
@@ -862,7 +863,7 @@ fn check_for_loop<'tcx>(
862863
check_for_mut_range_bound(cx, arg, body);
863864
check_for_single_element_loop(cx, pat, arg, body, expr);
864865
detect_same_item_push(cx, pat, arg, body, expr);
865-
check_for_loop_over_options_or_results(cx, pat, arg, body, expr);
866+
check_manual_flatten(cx, pat, arg, body, span);
866867
}
867868

868869
// this function assumes the given expression is a `for` loop.
@@ -1986,33 +1987,61 @@ fn check_for_single_element_loop<'tcx>(
19861987
}
19871988
}
19881989

1989-
/// Check if a for loop loops over `Option`s or `Result`s and contains only
1990-
/// a `if let Some` or `if let Ok` expression.
1991-
fn check_for_loop_over_options_or_results<'tcx>(
1990+
/// Check for unnecessary `if let` usage in a for loop where only the `Some` or `Ok` variant of the
1991+
/// iterator element is used.
1992+
fn check_manual_flatten<'tcx>(
19921993
cx: &LateContext<'tcx>,
19931994
pat: &'tcx Pat<'_>,
19941995
arg: &'tcx Expr<'_>,
19951996
body: &'tcx Expr<'_>,
1996-
expr: &'tcx Expr<'_>,
1997+
span: Span,
19971998
) {
19981999
if_chain! {
2000+
// Ensure the `if let` statement is the only expression in the for-loop
19992001
if let ExprKind::Block(ref block, _) = body.kind;
20002002
if block.stmts.is_empty();
20012003
if let Some(inner_expr) = block.expr;
2002-
if let ExprKind::Match(ref _match_expr, ref _match_arms, MatchSource::IfLetDesugar{ contains_else_clause }) = inner_expr.kind;
2003-
if !contains_else_clause;
2004+
if let ExprKind::Match(
2005+
ref match_expr, ref match_arms, MatchSource::IfLetDesugar{ contains_else_clause: false }
2006+
) = inner_expr.kind;
2007+
// Ensure match_expr in `if let` statement is the same as the pat from the for-loop
2008+
if let PatKind::Binding(_, pat_hir_id, _, _) = pat.kind;
2009+
if let ExprKind::Path(QPath::Resolved(None, match_expr_path)) = match_expr.kind;
2010+
if let Res::Local(match_expr_path_id) = match_expr_path.res;
2011+
if pat_hir_id == match_expr_path_id;
2012+
// Ensure the `if let` statement is for the `Some` variant of `Option` or the `Ok` variant of `Result`
2013+
if let PatKind::TupleStruct(QPath::Resolved(None, path), _, _) = match_arms[0].pat.kind;
2014+
if is_some_ctor(cx, path.res) || is_ok_ctor(cx, path.res);
2015+
let if_let_type = if is_some_ctor(cx, path.res) {
2016+
"Some"
2017+
} else {
2018+
"Ok"
2019+
};
2020+
// Determine if `arg` is `Iterator` or implicitly calls `into_iter`
2021+
let arg_ty = cx.typeck_results().expr_ty(arg);
2022+
if let Some(id) = get_trait_def_id(cx, &paths::ITERATOR);
2023+
if let is_iterator = implements_trait(cx, arg_ty, id, &[]);
2024+
20042025
then {
2005-
// println!("if_let_expr:\n{:?}", snippet(cx, if_let_expr.span, ".."));
2006-
// println!("pat is:\n {:?}", snippet(cx, pat.span, ".."));
2007-
// println!("arg is:\n {:?}", snippet(cx, arg.span, ".."));
2008-
// println!("body is:\n {:?}", snippet(cx, body.span, ".."));
2009-
// println!("arg kind is: {:?}", arg.kind);
2010-
// println!("expr is:\n {:?}", snippet(cx, expr.span, ".."));
2011-
// todo!();
2026+
// Prepare the error message
2027+
let msg = format!("Unnecessary `if let` since only the `{}` variant of the iterator element is used.", if_let_type);
2028+
2029+
// Prepare the help message
20122030
let arg_snippet = snippet(cx, arg.span, "..");
2013-
let msg = "looping over `Option`s or `Result`s with an `if let` expression.";
2014-
let hint = format!("try turn {} into an `Iterator` and use `flatten`: `{}.iter().flatten()`", arg_snippet, arg_snippet);
2015-
span_lint_and_help(cx, FOR_LOOPS_OVER_OPTIONS_OR_RESULTS, expr.span, msg, None, &hint);
2031+
let hint = if is_iterator {
2032+
format!("try: `{}.flatten()`", arg_snippet)
2033+
} else {
2034+
format!("try: `{}.into_iter().flatten()`", arg_snippet)
2035+
};
2036+
2037+
span_lint_and_help(
2038+
cx,
2039+
MANUAL_FLATTEN,
2040+
span,
2041+
&msg,
2042+
Some(arg.span),
2043+
&hint,
2044+
);
20162045
}
20172046
}
20182047
}

clippy_lints/src/mut_mut.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ impl<'a, 'tcx> intravisit::Visitor<'tcx> for MutVisitor<'a, 'tcx> {
5252
return;
5353
}
5454

55-
if let Some((_, arg, body)) = higher::for_loop(expr) {
55+
if let Some((_, arg, body, _)) = higher::for_loop(expr) {
5656
// A `for` loop lowers to:
5757
// ```rust
5858
// match ::std::iter::Iterator::next(&mut iter) {

clippy_lints/src/needless_question_mark.rs

Lines changed: 1 addition & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
use rustc_errors::Applicability;
2-
use rustc_hir::def::{CtorKind, CtorOf, DefKind, Res};
32
use rustc_hir::{Body, Expr, ExprKind, LangItem, MatchSource, QPath};
43
use rustc_lint::{LateContext, LateLintPass, LintContext};
5-
use rustc_middle::ty::DefIdTree;
64
use rustc_semver::RustcVersion;
75
use rustc_session::{declare_tool_lint, impl_lint_pass};
86
use rustc_span::sym;
@@ -160,7 +158,7 @@ fn is_some_or_ok_call<'a>(
160158
// Check outer expression matches CALL_IDENT(ARGUMENT) format
161159
if let ExprKind::Call(path, args) = &expr.kind;
162160
if let ExprKind::Path(QPath::Resolved(None, path)) = &path.kind;
163-
if is_some_ctor(cx, path.res) || is_ok_ctor(cx, path.res);
161+
if utils::is_some_ctor(cx, path.res) || utils::is_ok_ctor(cx, path.res);
164162

165163
// Extract inner expression from ARGUMENT
166164
if let ExprKind::Match(inner_expr_with_q, _, MatchSource::TryDesugar) = &args[0].kind;
@@ -208,25 +206,3 @@ fn is_some_or_ok_call<'a>(
208206
fn has_implicit_error_from(cx: &LateContext<'_>, entire_expr: &Expr<'_>, inner_result_expr: &Expr<'_>) -> bool {
209207
return cx.typeck_results().expr_ty(entire_expr) != cx.typeck_results().expr_ty(inner_result_expr);
210208
}
211-
212-
fn is_ok_ctor(cx: &LateContext<'_>, res: Res) -> bool {
213-
if let Some(ok_id) = cx.tcx.lang_items().result_ok_variant() {
214-
if let Res::Def(DefKind::Ctor(CtorOf::Variant, CtorKind::Fn), id) = res {
215-
if let Some(variant_id) = cx.tcx.parent(id) {
216-
return variant_id == ok_id;
217-
}
218-
}
219-
}
220-
false
221-
}
222-
223-
fn is_some_ctor(cx: &LateContext<'_>, res: Res) -> bool {
224-
if let Some(some_id) = cx.tcx.lang_items().option_some_variant() {
225-
if let Res::Def(DefKind::Ctor(CtorOf::Variant, CtorKind::Fn), id) = res {
226-
if let Some(variant_id) = cx.tcx.parent(id) {
227-
return variant_id == some_id;
228-
}
229-
}
230-
}
231-
false
232-
}

clippy_lints/src/ranges.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -442,7 +442,7 @@ fn check_reversed_empty_range(cx: &LateContext<'_>, expr: &Expr<'_>) {
442442
let mut cur_expr = expr;
443443
while let Some(parent_expr) = get_parent_expr(cx, cur_expr) {
444444
match higher::for_loop(parent_expr) {
445-
Some((_, args, _)) if args.hir_id == expr.hir_id => return true,
445+
Some((_, args, _, _)) if args.hir_id == expr.hir_id => return true,
446446
_ => cur_expr = parent_expr,
447447
}
448448
}

clippy_lints/src/utils/higher.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use rustc_ast::ast;
99
use rustc_hir as hir;
1010
use rustc_hir::{BorrowKind, Expr, ExprKind, StmtKind, UnOp};
1111
use rustc_lint::LateContext;
12+
use rustc_span::source_map::Span;
1213

1314
/// Converts a hir binary operator to the corresponding `ast` type.
1415
#[must_use]
@@ -133,11 +134,11 @@ pub fn is_from_for_desugar(local: &hir::Local<'_>) -> bool {
133134
false
134135
}
135136

136-
/// Recover the essential nodes of a desugared for loop:
137-
/// `for pat in arg { body }` becomes `(pat, arg, body)`.
137+
/// Recover the essential nodes of a desugared for loop as well as the entire span:
138+
/// `for pat in arg { body }` becomes `(pat, arg, body)`. Return `(pat, arg, body, span)`.
138139
pub fn for_loop<'tcx>(
139140
expr: &'tcx hir::Expr<'tcx>,
140-
) -> Option<(&hir::Pat<'_>, &'tcx hir::Expr<'tcx>, &'tcx hir::Expr<'tcx>)> {
141+
) -> Option<(&hir::Pat<'_>, &'tcx hir::Expr<'tcx>, &'tcx hir::Expr<'tcx>, Span)> {
141142
if_chain! {
142143
if let hir::ExprKind::Match(ref iterexpr, ref arms, hir::MatchSource::ForLoopDesugar) = expr.kind;
143144
if let hir::ExprKind::Call(_, ref iterargs) = iterexpr.kind;
@@ -148,7 +149,7 @@ pub fn for_loop<'tcx>(
148149
if let hir::StmtKind::Local(ref local) = let_stmt.kind;
149150
if let hir::StmtKind::Expr(ref expr) = body.kind;
150151
then {
151-
return Some((&*local.pat, &iterargs[0], expr));
152+
return Some((&*local.pat, &iterargs[0], expr, arms[0].span));
152153
}
153154
}
154155
None

clippy_lints/src/utils/internal_lints.rs

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -841,15 +841,13 @@ pub fn check_path(cx: &LateContext<'_>, path: &[&str]) -> bool {
841841
// implementations of native types. Check lang items.
842842
let path_syms: Vec<_> = path.iter().map(|p| Symbol::intern(p)).collect();
843843
let lang_items = cx.tcx.lang_items();
844-
for lang_item in lang_items.items() {
845-
if let Some(def_id) = lang_item {
846-
let lang_item_path = cx.get_def_path(*def_id);
847-
if path_syms.starts_with(&lang_item_path) {
848-
if let [item] = &path_syms[lang_item_path.len()..] {
849-
for child in cx.tcx.item_children(*def_id) {
850-
if child.ident.name == *item {
851-
return true;
852-
}
844+
for item_def_id in lang_items.items().iter().flatten() {
845+
let lang_item_path = cx.get_def_path(*item_def_id);
846+
if path_syms.starts_with(&lang_item_path) {
847+
if let [item] = &path_syms[lang_item_path.len()..] {
848+
for child in cx.tcx.item_children(*item_def_id) {
849+
if child.ident.name == *item {
850+
return true;
853851
}
854852
}
855853
}

clippy_lints/src/utils/mod.rs

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ use rustc_ast::ast::{self, Attribute, LitKind};
3737
use rustc_data_structures::fx::FxHashMap;
3838
use rustc_errors::Applicability;
3939
use rustc_hir as hir;
40-
use rustc_hir::def::{DefKind, Res};
40+
use rustc_hir::def::{CtorKind, CtorOf, DefKind, Res};
4141
use rustc_hir::def_id::{DefId, LOCAL_CRATE};
4242
use rustc_hir::intravisit::{self, NestedVisitorMap, Visitor};
4343
use rustc_hir::Node;
@@ -50,7 +50,7 @@ use rustc_lint::{LateContext, Level, Lint, LintContext};
5050
use rustc_middle::hir::exports::Export;
5151
use rustc_middle::hir::map::Map;
5252
use rustc_middle::ty::subst::{GenericArg, GenericArgKind};
53-
use rustc_middle::ty::{self, layout::IntegerExt, Ty, TyCtxt, TypeFoldable};
53+
use rustc_middle::ty::{self, layout::IntegerExt, DefIdTree, Ty, TyCtxt, TypeFoldable};
5454
use rustc_semver::RustcVersion;
5555
use rustc_session::Session;
5656
use rustc_span::hygiene::{ExpnKind, MacroKind};
@@ -1700,6 +1700,30 @@ pub fn is_hir_ty_cfg_dependant(cx: &LateContext<'_>, ty: &hir::Ty<'_>) -> bool {
17001700
}
17011701
}
17021702

1703+
/// Check if the resolution of a given path is an `Ok` variant of `Result`.
1704+
pub fn is_ok_ctor(cx: &LateContext<'_>, res: Res) -> bool {
1705+
if let Some(ok_id) = cx.tcx.lang_items().result_ok_variant() {
1706+
if let Res::Def(DefKind::Ctor(CtorOf::Variant, CtorKind::Fn), id) = res {
1707+
if let Some(variant_id) = cx.tcx.parent(id) {
1708+
return variant_id == ok_id;
1709+
}
1710+
}
1711+
}
1712+
false
1713+
}
1714+
1715+
/// Check if the resolution of a given path is a `Some` variant of `Option`.
1716+
pub fn is_some_ctor(cx: &LateContext<'_>, res: Res) -> bool {
1717+
if let Some(some_id) = cx.tcx.lang_items().option_some_variant() {
1718+
if let Res::Def(DefKind::Ctor(CtorOf::Variant, CtorKind::Fn), id) = res {
1719+
if let Some(variant_id) = cx.tcx.parent(id) {
1720+
return variant_id == some_id;
1721+
}
1722+
}
1723+
}
1724+
false
1725+
}
1726+
17031727
#[cfg(test)]
17041728
mod test {
17051729
use super::{reindent_multiline, without_block_comments};

clippy_lints/src/vec.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ impl<'tcx> LateLintPass<'tcx> for UselessVec {
5555

5656
// search for `for _ in vec![…]`
5757
if_chain! {
58-
if let Some((_, arg, _)) = higher::for_loop(expr);
58+
if let Some((_, arg, _, _)) = higher::for_loop(expr);
5959
if let Some(vec_args) = higher::vec_macro(cx, arg);
6060
if is_copy(cx, vec_type(cx.typeck_results().expr_ty_adjusted(arg)));
6161
then {

0 commit comments

Comments
 (0)