Skip to content

Commit 5753614

Browse files
committed
Draft skeleton for new lint
1 parent 949b125 commit 5753614

File tree

3 files changed

+68
-0
lines changed

3 files changed

+68
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1969,6 +1969,7 @@ 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`]: https://rust-lang.github.io/rust-clippy/master/index.html#for_loops_over_options
19721973
[`forget_copy`]: https://rust-lang.github.io/rust-clippy/master/index.html#forget_copy
19731974
[`forget_ref`]: https://rust-lang.github.io/rust-clippy/master/index.html#forget_ref
19741975
[`from_iter_instead_of_collect`]: https://rust-lang.github.io/rust-clippy/master/index.html#from_iter_instead_of_collect

clippy_lints/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -685,6 +685,7 @@ 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,
688689
&loops::ITER_NEXT_LOOP,
689690
&loops::MANUAL_MEMCPY,
690691
&loops::MUT_RANGE_BOUND,
@@ -1488,6 +1489,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
14881489
LintId::of(&loops::EXPLICIT_COUNTER_LOOP),
14891490
LintId::of(&loops::FOR_KV_MAP),
14901491
LintId::of(&loops::FOR_LOOPS_OVER_FALLIBLES),
1492+
LintId::of(&loops::FOR_LOOPS_OVER_OPTIONS),
14911493
LintId::of(&loops::ITER_NEXT_LOOP),
14921494
LintId::of(&loops::MANUAL_MEMCPY),
14931495
LintId::of(&loops::MUT_RANGE_BOUND),
@@ -1820,6 +1822,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
18201822
LintId::of(&lifetimes::EXTRA_UNUSED_LIFETIMES),
18211823
LintId::of(&lifetimes::NEEDLESS_LIFETIMES),
18221824
LintId::of(&loops::EXPLICIT_COUNTER_LOOP),
1825+
LintId::of(&loops::FOR_LOOPS_OVER_OPTIONS),
18231826
LintId::of(&loops::MUT_RANGE_BOUND),
18241827
LintId::of(&loops::SINGLE_ELEMENT_LOOP),
18251828
LintId::of(&loops::WHILE_LET_LOOP),

clippy_lints/src/loops.rs

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -494,13 +494,45 @@ declare_clippy_lint! {
494494
"there is no reason to have a single element loop"
495495
}
496496

497+
declare_clippy_lint! {
498+
/// **What it does:** Checks for iteration of `Option`s with
499+
/// a single `if let Some()` expression inside.
500+
///
501+
/// **Why is this bad?** It is verbose and can be simplified
502+
/// by first calling the `flatten` method on the `Iterator`.
503+
///
504+
/// **Known problems:** None.
505+
///
506+
/// **Example:**
507+
///
508+
/// ```rust
509+
/// let x = vec![Some(1), Some(2), Some(3)];
510+
/// for n in x {
511+
/// if let Some(n) = n {
512+
/// println!("{}", n);
513+
/// }
514+
/// }
515+
/// ```
516+
/// Use instead:
517+
/// ```rust
518+
/// let x = vec![Some(1), Some(2), Some(3)];
519+
/// for n in x.iter().flatten() {
520+
/// println!("{}", n);
521+
/// }
522+
/// ```
523+
pub FOR_LOOPS_OVER_OPTIONS,
524+
complexity,
525+
"for loops over `Option`s or `Result`s with a single expression can be simplified"
526+
}
527+
497528
declare_lint_pass!(Loops => [
498529
MANUAL_MEMCPY,
499530
NEEDLESS_RANGE_LOOP,
500531
EXPLICIT_ITER_LOOP,
501532
EXPLICIT_INTO_ITER_LOOP,
502533
ITER_NEXT_LOOP,
503534
FOR_LOOPS_OVER_FALLIBLES,
535+
FOR_LOOPS_OVER_OPTIONS,
504536
WHILE_LET_LOOP,
505537
NEEDLESS_COLLECT,
506538
EXPLICIT_COUNTER_LOOP,
@@ -830,6 +862,7 @@ fn check_for_loop<'tcx>(
830862
check_for_mut_range_bound(cx, arg, body);
831863
check_for_single_element_loop(cx, pat, arg, body, expr);
832864
detect_same_item_push(cx, pat, arg, body, expr);
865+
check_for_loop_over_options_or_results(cx, pat, arg, body, expr);
833866
}
834867

835868
// this function assumes the given expression is a `for` loop.
@@ -1953,6 +1986,37 @@ fn check_for_single_element_loop<'tcx>(
19531986
}
19541987
}
19551988

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>(
1992+
cx: &LateContext<'tcx>,
1993+
pat: &'tcx Pat<'_>,
1994+
arg: &'tcx Expr<'_>,
1995+
body: &'tcx Expr<'_>,
1996+
expr: &'tcx Expr<'_>,
1997+
) {
1998+
if_chain! {
1999+
if let ExprKind::Block(ref block, _) = body.kind;
2000+
if block.stmts.is_empty();
2001+
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+
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!();
2012+
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, expr.span, msg, None, &hint);
2016+
}
2017+
}
2018+
}
2019+
19562020
struct MutatePairDelegate<'a, 'tcx> {
19572021
cx: &'a LateContext<'tcx>,
19582022
hir_id_low: Option<HirId>,

0 commit comments

Comments
 (0)