rust-lang / rust-lang/rust-clippy

Lint nested and independent `if let` clauses

Open
#5,218 4 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

A-lint E-medium L-complexity S-needs-discussion
Dominant language
Rust
Stars
13.5k
Forks
2.2k
Avg merge
2d 10h
Merged PRs (30d)
32

Description

This code

if let Some(start) = (1_usize).checked_add(5) {
    if let Some(end) = (2_usize).checked_add(5) {
        // ...
    }
}

could be written like this

if let (Some(start), Some(end)) = ((1_usize).checked_add(5), (2_usize).checked_add(5)) {
    // ...
}

which would remove a layer of nesting.


The lint should only lint nested if let clauses, that are independent of each other.

For example this must be ignored (or handled like in #2521):

if let Some(start) = (1_usize).checked_add(5) {
    if let Some(end) = call_fn(start) {
        // ...
    }
}

but it should not matter, if there is code in between the if let clauses, as long as it does not influence the other if let clause(s):

if let Some(start) = (1_usize).checked_add(5) {
    let x = 13_usize;
    if let Some(end) = (2_usize).checked_add(5) {
        // ...
    }
    let y = 15_usize;
}

I think that there should not be a limit on how many nested if let clauses should be linted, because it would improve the readability either way.

Another thing to consider would be else clauses. I think this lint should only trigger, if the nested else clauses have the same body:

if let Some(start) = (1_usize).checked_add(5) {
    if let Some(end) = (2_usize).checked_add(5) {
        // ...
    } else {
        call_x();
    }
} else {
    call_x();
}

would become

if let (Some(start), Some(end)) = ((1_usize).checked_add(5), (2_usize).checked_add(5)) {
    // ...
} else {
    call_x();
}

It might be too difficult (for clippy), but it would be possible to refactor nested if let-clauses with different else-clauses too

if let Some(start) = (1_usize).checked_add(5) {
    if let Some(end) = (2_usize).checked_add(5) {
        // ...
    } else {
        call_z(start);
    }
} else {
    call_y();
}

->

match ((1_usize).checked_add(5), (2_usize).checked_add(5)) {
    (Some(start), Some(end)) => {
        // ...
    }

    (Some(start), None) => {
        call_z(start);
    }

    (None, None) => {
        call_y();
    }

    _ => {
        // this was not reachable in the original code (end would only be matched if Some(start))
    }
}

Should I move this issue in to several smaller ones?

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reviewing the independent and dependent nested if let examples in the issue, along with the existing handling referenced in #2521. Define the lint's scope, including intervening code, multiple clauses, and matching or differing else bodies; done requires a decided behavior for these cases and corresponding coverage.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
devtools, tooling
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.