Skip to content

Commit 9d5ea8f

Browse files
committed
Fix Clippy with changed for loop desugar
1 parent 9c83f8c commit 9d5ea8f

File tree

10 files changed

+90
-140
lines changed

10 files changed

+90
-140
lines changed

src/tools/clippy/clippy_lints/src/loops/explicit_counter_loop.rs

+2-6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
use super::{
2-
get_span_of_entire_for_loop, make_iterator_snippet, IncrementVisitor, InitializeVisitor, EXPLICIT_COUNTER_LOOP,
3-
};
1+
use super::{make_iterator_snippet, IncrementVisitor, InitializeVisitor, EXPLICIT_COUNTER_LOOP};
42
use clippy_utils::diagnostics::span_lint_and_sugg;
53
use clippy_utils::source::snippet_with_applicability;
64
use clippy_utils::{get_enclosing_block, is_integer_const};
@@ -37,12 +35,10 @@ pub(super) fn check<'tcx>(
3735
then {
3836
let mut applicability = Applicability::MachineApplicable;
3937

40-
let for_span = get_span_of_entire_for_loop(expr);
41-
4238
span_lint_and_sugg(
4339
cx,
4440
EXPLICIT_COUNTER_LOOP,
45-
for_span.with_hi(arg.span.hi()),
41+
expr.span.with_hi(arg.span.hi()),
4642
&format!("the variable `{}` is used as a loop counter", name),
4743
"consider using",
4844
format!(

src/tools/clippy/clippy_lints/src/loops/manual_memcpy.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use super::{get_span_of_entire_for_loop, IncrementVisitor, InitializeVisitor, MANUAL_MEMCPY};
1+
use super::{IncrementVisitor, InitializeVisitor, MANUAL_MEMCPY};
22
use clippy_utils::diagnostics::span_lint_and_sugg;
33
use clippy_utils::source::snippet;
44
use clippy_utils::sugg::Sugg;
@@ -86,7 +86,7 @@ pub(super) fn check<'tcx>(
8686
span_lint_and_sugg(
8787
cx,
8888
MANUAL_MEMCPY,
89-
get_span_of_entire_for_loop(expr),
89+
expr.span,
9090
"it looks like you're manually copying between slices",
9191
"try replacing the loop by",
9292
big_sugg,

src/tools/clippy/clippy_lints/src/loops/mod.rs

+16-3
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ use rustc_hir::{Expr, ExprKind, LoopSource, Pat};
2323
use rustc_lint::{LateContext, LateLintPass};
2424
use rustc_session::{declare_lint_pass, declare_tool_lint};
2525
use rustc_span::source_map::Span;
26-
use utils::{get_span_of_entire_for_loop, make_iterator_snippet, IncrementVisitor, InitializeVisitor};
26+
use utils::{make_iterator_snippet, IncrementVisitor, InitializeVisitor};
2727

2828
declare_clippy_lint! {
2929
/// ### What it does
@@ -566,14 +566,25 @@ declare_lint_pass!(Loops => [
566566
impl<'tcx> LateLintPass<'tcx> for Loops {
567567
#[allow(clippy::too_many_lines)]
568568
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
569-
if let Some(higher::ForLoop { pat, arg, body, span }) = higher::ForLoop::hir(expr) {
569+
let for_loop = higher::ForLoop::hir(expr);
570+
if let Some(higher::ForLoop {
571+
pat,
572+
arg,
573+
body,
574+
loop_id,
575+
span,
576+
}) = for_loop
577+
{
570578
// we don't want to check expanded macros
571579
// this check is not at the top of the function
572580
// since higher::for_loop expressions are marked as expansions
573581
if body.span.from_expansion() {
574582
return;
575583
}
576584
check_for_loop(cx, pat, arg, body, expr, span);
585+
if let ExprKind::Block(block, _) = body.kind {
586+
never_loop::check(cx, block, loop_id, span, for_loop.as_ref());
587+
}
577588
}
578589

579590
// we don't want to check expanded macros
@@ -582,7 +593,9 @@ impl<'tcx> LateLintPass<'tcx> for Loops {
582593
}
583594

584595
// check for never_loop
585-
never_loop::check(cx, expr);
596+
if let ExprKind::Loop(block, ..) = expr.kind {
597+
never_loop::check(cx, block, expr.hir_id, expr.span, None);
598+
}
586599

587600
// check for `loop { if let {} else break }` that could be `while let`
588601
// (also matches an explicit "match" instead of "if let")

src/tools/clippy/clippy_lints/src/loops/never_loop.rs

+32-26
Original file line numberDiff line numberDiff line change
@@ -4,35 +4,41 @@ use clippy_utils::diagnostics::span_lint_and_then;
44
use clippy_utils::higher::ForLoop;
55
use clippy_utils::source::snippet;
66
use rustc_errors::Applicability;
7-
use rustc_hir::{Block, Expr, ExprKind, HirId, InlineAsmOperand, LoopSource, Node, Pat, Stmt, StmtKind};
7+
use rustc_hir::{Block, Expr, ExprKind, HirId, InlineAsmOperand, Pat, Stmt, StmtKind};
88
use rustc_lint::LateContext;
9+
use rustc_span::Span;
910
use std::iter::{once, Iterator};
1011

11-
pub(super) fn check(cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
12-
if let ExprKind::Loop(block, _, source, _) = expr.kind {
13-
match never_loop_block(block, expr.hir_id) {
14-
NeverLoopResult::AlwaysBreak => {
15-
span_lint_and_then(cx, NEVER_LOOP, expr.span, "this loop never actually loops", |diag| {
16-
if_chain! {
17-
if source == LoopSource::ForLoop;
18-
if let Some((_, Node::Expr(parent_match))) = cx.tcx.hir().parent_iter(expr.hir_id).nth(1);
19-
if let Some(ForLoop { arg: iterator, pat, span: for_span, .. }) = ForLoop::hir(parent_match);
20-
then {
21-
// Suggests using an `if let` instead. This is `Unspecified` because the
22-
// loop may (probably) contain `break` statements which would be invalid
23-
// in an `if let`.
24-
diag.span_suggestion_verbose(
25-
for_span.with_hi(iterator.span.hi()),
26-
"if you need the first element of the iterator, try writing",
27-
for_to_if_let_sugg(cx, iterator, pat),
28-
Applicability::Unspecified,
29-
);
30-
}
31-
};
32-
});
33-
},
34-
NeverLoopResult::MayContinueMainLoop | NeverLoopResult::Otherwise => (),
35-
}
12+
pub(super) fn check(
13+
cx: &LateContext<'tcx>,
14+
block: &'tcx Block<'_>,
15+
loop_id: HirId,
16+
span: Span,
17+
for_loop: Option<&ForLoop<'_>>,
18+
) {
19+
match never_loop_block(block, loop_id) {
20+
NeverLoopResult::AlwaysBreak => {
21+
span_lint_and_then(cx, NEVER_LOOP, span, "this loop never actually loops", |diag| {
22+
if let Some(ForLoop {
23+
arg: iterator,
24+
pat,
25+
span: for_span,
26+
..
27+
}) = for_loop
28+
{
29+
// Suggests using an `if let` instead. This is `Unspecified` because the
30+
// loop may (probably) contain `break` statements which would be invalid
31+
// in an `if let`.
32+
diag.span_suggestion_verbose(
33+
for_span.with_hi(iterator.span.hi()),
34+
"if you need the first element of the iterator, try writing",
35+
for_to_if_let_sugg(cx, iterator, pat),
36+
Applicability::Unspecified,
37+
);
38+
}
39+
});
40+
},
41+
NeverLoopResult::MayContinueMainLoop | NeverLoopResult::Otherwise => (),
3642
}
3743
}
3844

src/tools/clippy/clippy_lints/src/loops/single_element_loop.rs

+2-3
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use super::{get_span_of_entire_for_loop, SINGLE_ELEMENT_LOOP};
1+
use super::SINGLE_ELEMENT_LOOP;
22
use clippy_utils::diagnostics::span_lint_and_sugg;
33
use clippy_utils::single_segment_path;
44
use clippy_utils::source::{indent_of, snippet};
@@ -30,7 +30,6 @@ pub(super) fn check<'tcx>(
3030
if !block.stmts.is_empty();
3131

3232
then {
33-
let for_span = get_span_of_entire_for_loop(expr);
3433
let mut block_str = snippet(cx, block.span, "..").into_owned();
3534
block_str.remove(0);
3635
block_str.pop();
@@ -39,7 +38,7 @@ pub(super) fn check<'tcx>(
3938
span_lint_and_sugg(
4039
cx,
4140
SINGLE_ELEMENT_LOOP,
42-
for_span,
41+
expr.span,
4342
"for loop over a single element",
4443
"try",
4544
format!("{{\n{}let {} = &{};{}}}", " ".repeat(indent_of(cx, block.stmts[0].span).unwrap_or(0)), target.name, list_item_name, block_str),

src/tools/clippy/clippy_lints/src/loops/utils.rs

-12
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ use rustc_hir::HirIdMap;
77
use rustc_hir::{BinOpKind, BorrowKind, Expr, ExprKind, HirId, Mutability, Pat, PatKind, Stmt, StmtKind};
88
use rustc_lint::LateContext;
99
use rustc_middle::hir::map::Map;
10-
use rustc_span::source_map::Span;
1110
use rustc_span::symbol::{sym, Symbol};
1211
use std::iter::Iterator;
1312

@@ -300,17 +299,6 @@ impl<'tcx> Visitor<'tcx> for LoopNestVisitor {
300299
}
301300
}
302301

303-
// this function assumes the given expression is a `for` loop.
304-
pub(super) fn get_span_of_entire_for_loop(expr: &Expr<'_>) -> Span {
305-
// for some reason this is the only way to get the `Span`
306-
// of the entire `for` loop
307-
if let ExprKind::Match(_, arms, _) = &expr.kind {
308-
arms[0].body.span
309-
} else {
310-
unreachable!()
311-
}
312-
}
313-
314302
/// If `arg` was the argument to a `for` loop, return the "cleanest" way of writing the
315303
/// actual `Iterator` that the loop uses.
316304
pub(super) fn make_iterator_snippet(cx: &LateContext<'_>, arg: &Expr<'_>, applic_ref: &mut Applicability) -> String {

src/tools/clippy/clippy_lints/src/misc.rs

+2-3
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ use rustc_span::symbol::sym;
2020
use clippy_utils::consts::{constant, Constant};
2121
use clippy_utils::sugg::Sugg;
2222
use clippy_utils::{
23-
expr_path_res, get_item_name, get_parent_expr, higher, in_constant, is_diag_trait_item, is_integer_const,
24-
iter_input_pats, last_path_segment, match_any_def_paths, paths, unsext, SpanlessEq,
23+
expr_path_res, get_item_name, get_parent_expr, in_constant, is_diag_trait_item, is_integer_const, iter_input_pats,
24+
last_path_segment, match_any_def_paths, paths, unsext, SpanlessEq,
2525
};
2626

2727
declare_clippy_lint! {
@@ -312,7 +312,6 @@ impl<'tcx> LateLintPass<'tcx> for MiscLints {
312312
if let StmtKind::Local(local) = stmt.kind;
313313
if let PatKind::Binding(an, .., name, None) = local.pat.kind;
314314
if let Some(init) = local.init;
315-
if !higher::is_from_for_desugar(local);
316315
if an == BindingAnnotation::Ref || an == BindingAnnotation::RefMut;
317316
then {
318317
// use the macro callsite when the init span (but not the whole local span)

src/tools/clippy/clippy_lints/src/unit_types/let_unit_value.rs

-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
use clippy_utils::diagnostics::span_lint_and_then;
2-
use clippy_utils::higher;
32
use clippy_utils::source::snippet_with_macro_callsite;
43
use rustc_errors::Applicability;
54
use rustc_hir::{Stmt, StmtKind};
@@ -14,9 +13,6 @@ pub(super) fn check(cx: &LateContext<'_>, stmt: &Stmt<'_>) {
1413
if in_external_macro(cx.sess(), stmt.span) || local.pat.span.from_expansion() {
1514
return;
1615
}
17-
if higher::is_from_for_desugar(local) {
18-
return;
19-
}
2016
span_lint_and_then(
2117
cx,
2218
LET_UNIT_VALUE,

src/tools/clippy/clippy_utils/src/higher.rs

+15-47
Original file line numberDiff line numberDiff line change
@@ -22,31 +22,31 @@ pub struct ForLoop<'tcx> {
2222
pub arg: &'tcx hir::Expr<'tcx>,
2323
/// `for` loop body
2424
pub body: &'tcx hir::Expr<'tcx>,
25+
/// Compare this against `hir::Destination.target`
26+
pub loop_id: HirId,
2527
/// entire `for` loop span
2628
pub span: Span,
2729
}
2830

2931
impl<'tcx> ForLoop<'tcx> {
30-
#[inline]
3132
/// Parses a desugared `for` loop
3233
pub fn hir(expr: &Expr<'tcx>) -> Option<Self> {
3334
if_chain! {
34-
if let hir::ExprKind::Match(iterexpr, arms, hir::MatchSource::ForLoopDesugar) = expr.kind;
35-
if let Some(first_arm) = arms.get(0);
36-
if let hir::ExprKind::Call(_, iterargs) = iterexpr.kind;
37-
if let Some(first_arg) = iterargs.get(0);
38-
if iterargs.len() == 1 && arms.len() == 1 && first_arm.guard.is_none();
39-
if let hir::ExprKind::Loop(block, ..) = first_arm.body.kind;
40-
if block.expr.is_none();
41-
if let [ _, _, ref let_stmt, ref body ] = *block.stmts;
42-
if let hir::StmtKind::Local(local) = let_stmt.kind;
43-
if let hir::StmtKind::Expr(body_expr) = body.kind;
35+
if let hir::ExprKind::DropTemps(e) = expr.kind;
36+
if let hir::ExprKind::Match(iterexpr, [arm], hir::MatchSource::ForLoopDesugar) = e.kind;
37+
if let hir::ExprKind::Call(_, [arg]) = iterexpr.kind;
38+
if let hir::ExprKind::Loop(block, ..) = arm.body.kind;
39+
if let [stmt] = &*block.stmts;
40+
if let hir::StmtKind::Expr(e) = stmt.kind;
41+
if let hir::ExprKind::Match(_, [_, some_arm], _) = e.kind;
42+
if let hir::PatKind::Struct(_, [field], _) = some_arm.pat.kind;
4443
then {
4544
return Some(Self {
46-
pat: &*local.pat,
47-
arg: first_arg,
48-
body: body_expr,
49-
span: first_arm.span
45+
pat: field.pat,
46+
arg,
47+
body: some_arm.body,
48+
loop_id: arm.body.hir_id,
49+
span: expr.span.ctxt().outer_expn_data().call_site,
5050
});
5151
}
5252
}
@@ -678,38 +678,6 @@ impl<'tcx> FormatArgsArg<'tcx> {
678678
}
679679
}
680680

681-
/// Checks if a `let` statement is from a `for` loop desugaring.
682-
pub fn is_from_for_desugar(local: &hir::Local<'_>) -> bool {
683-
// This will detect plain for-loops without an actual variable binding:
684-
//
685-
// ```
686-
// for x in some_vec {
687-
// // do stuff
688-
// }
689-
// ```
690-
if_chain! {
691-
if let Some(expr) = local.init;
692-
if let hir::ExprKind::Match(_, _, hir::MatchSource::ForLoopDesugar) = expr.kind;
693-
then {
694-
return true;
695-
}
696-
}
697-
698-
// This detects a variable binding in for loop to avoid `let_unit_value`
699-
// lint (see issue #1964).
700-
//
701-
// ```
702-
// for _ in vec![()] {
703-
// // anything
704-
// }
705-
// ```
706-
if let hir::LocalSource::ForLoopDesugar = local.source {
707-
return true;
708-
}
709-
710-
false
711-
}
712-
713681
/// A parsed `panic!` expansion
714682
pub struct PanicExpn<'tcx> {
715683
/// Span of `panic!(..)`

src/tools/clippy/tests/ui/author/for_loop.stdout

+19-34
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,8 @@ if_chain! {
1111
// unimplemented: field checks
1212
if arms.len() == 1;
1313
if let ExprKind::Loop(ref body, ref label, LoopSource::ForLoop) = arms[0].body.kind;
14-
if body.stmts.len() == 4;
15-
if let StmtKind::Local(ref local) = body.stmts[0].kind;
16-
if let PatKind::Binding(BindingAnnotation::Mutable, _, name, None) = local.pat.kind;
17-
if name.as_str() == "__next";
18-
if let StmtKind::Expr(ref e, _) = body.stmts[1].kind
14+
if body.stmts.len() == 1;
15+
if let StmtKind::Expr(ref e, _) = body.stmts[0].kind
1916
if let ExprKind::Match(ref expr2, ref arms1, MatchSource::ForLoopDesugar) = e.kind;
2017
if let ExprKind::Call(ref func1, ref args1) = expr2.kind;
2118
if let ExprKind::Path(ref path2) = func1.kind;
@@ -25,39 +22,27 @@ if_chain! {
2522
if let ExprKind::Path(ref path3) = inner.kind;
2623
if match_qpath(path3, &["iter"]);
2724
if arms1.len() == 2;
28-
if let ExprKind::Assign(ref target, ref value, ref _span) = arms1[0].body.kind;
29-
if let ExprKind::Path(ref path4) = target.kind;
30-
if match_qpath(path4, &["__next"]);
31-
if let ExprKind::Path(ref path5) = value.kind;
32-
if match_qpath(path5, &["val"]);
33-
if let PatKind::Struct(ref path6, ref fields1, false) = arms1[0].pat.kind;
34-
if matches!(path6, QPath::LangItem(LangItem::OptionSome, _));
35-
if fields1.len() == 1;
36-
// unimplemented: field checks
37-
if let ExprKind::Break(ref destination, None) = arms1[1].body.kind;
38-
if let PatKind::Struct(ref path7, ref fields2, false) = arms1[1].pat.kind;
39-
if matches!(path7, QPath::LangItem(LangItem::OptionNone, _));
40-
if fields2.len() == 0;
25+
if let ExprKind::Break(ref destination, None) = arms1[0].body.kind;
26+
if let PatKind::Struct(ref path4, ref fields1, false) = arms1[0].pat.kind;
27+
if matches!(path4, QPath::LangItem(LangItem::OptionNone, _));
28+
if fields1.len() == 0;
4129
// unimplemented: field checks
42-
if let StmtKind::Local(ref local1) = body.stmts[2].kind;
43-
if let Some(ref init) = local1.init;
44-
if let ExprKind::Path(ref path8) = init.kind;
45-
if match_qpath(path8, &["__next"]);
46-
if let PatKind::Binding(BindingAnnotation::Unannotated, _, name1, None) = local1.pat.kind;
47-
if name1.as_str() == "y";
48-
if let StmtKind::Expr(ref e1, _) = body.stmts[3].kind
49-
if let ExprKind::Block(ref block) = e1.kind;
30+
if let ExprKind::Block(ref block) = arms1[1].body.kind;
5031
if block.stmts.len() == 1;
51-
if let StmtKind::Local(ref local2) = block.stmts[0].kind;
52-
if let Some(ref init1) = local2.init;
53-
if let ExprKind::Path(ref path9) = init1.kind;
54-
if match_qpath(path9, &["y"]);
55-
if let PatKind::Binding(BindingAnnotation::Unannotated, _, name2, None) = local2.pat.kind;
56-
if name2.as_str() == "z";
32+
if let StmtKind::Local(ref local) = block.stmts[0].kind;
33+
if let Some(ref init) = local.init;
34+
if let ExprKind::Path(ref path5) = init.kind;
35+
if match_qpath(path5, &["y"]);
36+
if let PatKind::Binding(BindingAnnotation::Unannotated, _, name, None) = local.pat.kind;
37+
if name.as_str() == "z";
5738
if block.expr.is_none();
39+
if let PatKind::Struct(ref path6, ref fields2, false) = arms1[1].pat.kind;
40+
if matches!(path6, QPath::LangItem(LangItem::OptionSome, _));
41+
if fields2.len() == 1;
42+
// unimplemented: field checks
5843
if body.expr.is_none();
59-
if let PatKind::Binding(BindingAnnotation::Mutable, _, name3, None) = arms[0].pat.kind;
60-
if name3.as_str() == "iter";
44+
if let PatKind::Binding(BindingAnnotation::Mutable, _, name1, None) = arms[0].pat.kind;
45+
if name1.as_str() == "iter";
6146
then {
6247
// report your lint here
6348
}

0 commit comments

Comments
 (0)