Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New lint: while let Some(&x) = slice.iter().next() is infinite loop #4554

Open
tesuji opened this issue Sep 19, 2019 · 11 comments
Open

New lint: while let Some(&x) = slice.iter().next() is infinite loop #4554

tesuji opened this issue Sep 19, 2019 · 11 comments
Labels
A-lint Area: New lints good-first-issue These issues are a good way to get started with Clippy L-correctness Lint: Belongs in the correctness lint group L-suggestion Lint: Improving, adding or fixing lint suggestions

Comments

@tesuji
Copy link
Contributor

tesuji commented Sep 19, 2019

This code is an infinite loop: (Do not run this in playpen)

fn main() {
    let a = [1, 2, 3];
    while let Some(&x) = a.iter().next() {
        println!("{:?}", x);
    }
}
@flip1995 flip1995 added L-correctness Lint: Belongs in the correctness lint group L-suggestion Lint: Improving, adding or fixing lint suggestions good-first-issue These issues are a good way to get started with Clippy A-lint Area: New lints labels Sep 19, 2019
@flip1995
Copy link
Member

Basically calling _.iter() in the while let expression will lead to an infinite loop (except the collection is empty).

@pop
Copy link

pop commented Sep 26, 2019

I'd love to contribute to Clippy. Since this is a good first issue I was hoping I could start working on it. Could I get some pointers on where in the code to get started?

@llogiq
Copy link
Contributor

llogiq commented Sep 26, 2019

Cool! I usually start by writing a test case (create a new .rs file in tests/ui, see the other files there for examples). Then I look into where the lint fits in, for example clippy_lints/src/loops.rs might be a good fit. There you first need to add the lint declaration and add the lint to the LintPass. Finally you need some code to check for iter calls whose expr_ty implements Iterator.

@flip1995
Copy link
Member

I'm a little late, but this doc gives you step by step instructions on how to write a new lint. For this lint the Author lint chapter is probably interesting for you.

@chibby0ne
Copy link

Hi all, if @pop is not going to tackle this issue, I'd like to take it. I've already taken a look at the resources, thanks @llogiq and @flip1995.
I think a LateLintPass fits here because we need the type information of the match arm.
From what I've seen this is the place where we should detect this condition and emit the lint message:

if let ExprKind::Match(ref match_expr, ref arms, MatchSource::WhileLetDesugar) = expr.kind {

This "desugars" the while let into a loop match, which in our case would be:

while let Some(&x) = a.iter().next() {
    println!("{:?}", x);
}

into

loop {
    match a.iter().next() {
        Some(&x) => { println!("{:?}", x); },
        _ => (),
        }
    }
}

Afterwards it decomposes the first arm using the pattern kind and the match_expre using the expression kind as well,

let pat = &arms[0].pat.kind;
if let (
&PatKind::TupleStruct(ref qpath, ref pat_args, _),
&ExprKind::MethodCall(ref method_path, _, ref method_args),
) = (pat, &match_expr.kind)
{

So that, roughly speaking:

pat is Some(&x) => { println!("{:?}", x); },
match_expr is a Method call => a.iter().next()

Where I get lost is how to get the type information from the qpath, and method_path.
I've looked into match_type in utils, but it seems it expects a different type, than qpath, which is a hir::QPath or in method_path case it is hir::PathSegment.

I'll keep looking into it, but if you have any useful pointers into what I should look into (HIR, MIR, Ty) or examples I would greatly appreciate it, specially since I'm new to clippy and rustc internals.

@pop
Copy link

pop commented Oct 5, 2019

You seem primed and ready to take this one, so go for it @chibby0ne.

@flip1995
Copy link
Member

flip1995 commented Oct 6, 2019

From what I've seen this is the place where we should detect this condition and emit the lint message:

Seems good to me

So that, roughly speaking:
pat is Some(&x) => { println!("{:?}", x); },
match_expr is a Method call => a.iter().next()

Exactly, you should only have to look at the match_expr though.

how to get the type information from the qpath

You should be able to use the function

/// Matches a `QPath` against a slice of segment string literals.
///
/// There is also `match_path` if you are dealing with a `rustc::hir::Path` instead of a
/// `rustc::hir::QPath`.
///
/// # Examples
/// ```rust,ignore
/// match_qpath(path, &["std", "rt", "begin_unwind"])
/// ```
pub fn match_qpath(path: &QPath, segments: &[&str]) -> bool {

You can grep the repo for match_qpath on how to use it.

@chibby0ne
Copy link

You seem primed and ready to take this one, so go for it @chibby0ne.

Thanks @pop I appreciate! 😄

@felix91gr
Copy link
Contributor

How did you fare with this one, @chibby0ne? :)

@boxdot
Copy link
Contributor

boxdot commented Apr 16, 2021

I started to look into this issue. But it seems that it contradicts #1654. There is also an example where recreating the iterator is actually useful:

use std::collections::HashSet;
let mut values = HashSet::new();
values.insert(1);

while let Some(..) = values.iter().next() {
    values.remove(&1);
}

Also see

// Don't lint when the iterator is recreated on every iteration
if_chain! {
if let ExprKind::MethodCall(..) | ExprKind::Call(..) = iter_expr.kind;
if let Some(iter_def_id) = cx.tcx.get_diagnostic_item(sym::Iterator);
if implements_trait(cx, cx.typeck_results().expr_ty(iter_expr), iter_def_id, &[]);
then {
return;
}
}

@bbb651
Copy link

bbb651 commented Oct 7, 2024

This is also useful for streams, it's very easy to miss in streams that wait for events because they behave almost identically and manifest in subtle ways, I recently came across this:

 #[tokio::main]
 async fn main() -> anyhow::Result<()> {
-    while let Some(mode) = dark_light::subscribe().await?.next().await {
+    let mut stream = dark_light::subscribe().await?;
+    while let Some(mode) = stream.next().await {
         println!("System theme changed: {:?}", mode);
     }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-lint Area: New lints good-first-issue These issues are a good way to get started with Clippy L-correctness Lint: Belongs in the correctness lint group L-suggestion Lint: Improving, adding or fixing lint suggestions
Projects
None yet
Development

No branches or pull requests

9 participants