Skip to content

Conversation

@Natural-selection1
Copy link
Contributor

@Natural-selection1 Natural-selection1 commented Sep 21, 2025

Description

this PR makes the lint irrefutable_let_patterns not check for let chains,
only check for single if let, while let, and if let guard.

Motivation

Since let chains were stabilized, the following code has become common:

fn max() -> usize { 42 }

fn main() {
    if let mx = max() && mx < usize::MAX { /* */ }
}

This code naturally expresses "please call that function and then do something if the return value satisfies a condition".
Putting the let binding outside the if would be bad as then it remains in scope after the if, which is not the intent.

Current Output:

warning: leading irrefutable pattern in let chain
 --> src/main.rs:7:8
  |
7 |     if let mx = max() && mx < usize::MAX {
  |        ^^^^^^^^^^^^^^
  |
  = note: this pattern will always match
  = help: consider moving it outside of the construct
  = note: `#[warn(irrefutable_let_patterns)]` on by default

Another common case is progressively destructuring a struct with enum fields, or an enum with struct variants:

struct NameOfOuterStruct {
    middle: NameOfMiddleEnum,
    other: (),
}
enum NameOfMiddleEnum {
    Inner(NameOfInnerStruct),
    Other(()),
}
struct NameOfInnerStruct {
    id: u32,
}

fn test(outer: NameOfOuterStruct) {
    if let NameOfOuterStruct { middle, .. } = outer
        && let NameOfMiddleEnum::Inner(inner) = middle
        && let NameOfInnerStruct { id } = inner
    {
        /* */
    }
}

Current Output:

warning: leading irrefutable pattern in let chain
  --> src\main.rs:17:8
   |
17 |     if let NameOfOuterStruct { middle, .. } = outer
   |        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
   = note: this pattern will always match
   = help: consider moving it outside of the construct
   = note: `#[warn(irrefutable_let_patterns)]` on by default


warning: trailing irrefutable pattern in let chain
  --> src\main.rs:19:12
   |
19 |         && let NameOfInnerStruct { id } = inner
   |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
   = note: this pattern will always match
   = help: consider moving it into the body

To avoid the warning, the readability would be much worse:

fn test(outer: NameOfOuterStruct) {
    if let NameOfOuterStruct {
        middle: NameOfMiddleEnum::Inner(NameOfInnerStruct { id }),
        ..
    } = outer
    {
        /* */
    }
}

related issue

possible questions

  1. Moving the irrefutable pattern at the head of the chain out of it would cause a variable that was intended to be temporary to remain in scope, so we remove it.
    However, should we keep the check for moving the irrefutable pattern at the tail into the body?

  2. Should we still lint entire chain is made up of irrefutable let?


This is my first time contributing non-documentation code to Rust. If there are any irregularities, please feel free to point them out.
: )

@rustbot rustbot added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. labels Sep 21, 2025
@rustbot

This comment was marked as off-topic.

@rust-log-analyzer

This comment has been minimized.

@rust-log-analyzer

This comment has been minimized.

@traviscross traviscross added T-lang Relevant to the language team needs-fcp This change is insta-stable, or significant enough to need a team FCP to proceed. I-lang-radar Items that are on lang's radar and will need eventual work or consideration. labels Sep 21, 2025
@rust-log-analyzer

This comment has been minimized.

@Natural-selection1 Natural-selection1 force-pushed the not-in-chains branch 2 times, most recently from ce66065 to e77a3c4 Compare September 21, 2025 11:02
@Natural-selection1 Natural-selection1 marked this pull request as ready for review September 21, 2025 11:07
@rustbot
Copy link
Collaborator

rustbot commented Sep 21, 2025

Some changes occurred in match checking

cc @Nadrieril

The Miri subtree was changed

cc @rust-lang/miri

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. and removed S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. labels Sep 21, 2025
@Natural-selection1
Copy link
Contributor Author

It seems that only modifying tests/ui/rfcs/rfc-2497-if-let-chains/irrefutable-lets.rs is needed to cover this change. Do I still need to add an additional new test?

@traviscross
Copy link
Contributor

It seems that only modifying tests/ui/rfcs/rfc-2497-if-let-chains/irrefutable-lets.rs is needed to cover this change. Do I still need to add an additional new test?

All the lints seem to be removed in that test, but you'll want to ensure there are tests that cover the edges of what is still linted here.

@rustbot author

@rustbot rustbot removed the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Sep 21, 2025
@rustbot
Copy link
Collaborator

rustbot commented Sep 21, 2025

Reminder, once the PR becomes ready for a review, use @rustbot ready.

@rustbot rustbot added the S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. label Sep 21, 2025
Comment on lines 171 to 173
// For let chains, don't lint.
if let [Some((_, refutability))] = chain_refutabilities[..] {
self.check_single_let(refutability, ex.span);
Copy link
Contributor Author

@Natural-selection1 Natural-selection1 Sep 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

but you'll want to ensure there are tests that cover the edges of what is still linted here.

match chain_refutabilities.len() {
    0   => "`assert!(self.let_source != LetSource::None)` ensure this case doesn't exist"
    2.. => "`if let [Some((_, refutability))] = chain_refutabilities[..]` ensure not checked"  // The purpose of this PR: don't check let chains
    1 if chain_refutabilities[0].is_none() 
        => "A boolean expression at the start of `while let` | `if let` | `else if let`, this case cannot pass syntax analysis"
    1 if chain_refutabilities[0].is_some() 
        => "There is exactly one let binding in `while let` | `if let` | `else if let` statement, proceed to check"
}

@Nadrieril
Copy link
Member

If so, what are the next steps to get it started?

No one answered, but the answer is "add the I-lang-nominated label and wait for the lang team to look at it", which @traviscross did. So there's nothing more to do for you until then :)

@Nadrieril Nadrieril added S-waiting-on-t-lang Status: Awaiting decision from T-lang and removed S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. labels Oct 8, 2025
@Natural-selection1
Copy link
Contributor Author

Hi, I really hope this PR can be merged before the 1.91 release. Is there any progress now
: )

@fmease
Copy link
Member

fmease commented Oct 23, 2025

I really hope this PR can be merged before the 1.91 release.

That's not possible, master is 1.92 already and 1.91 is beta meaning the latter only accepts patches for stable-to-beta and stable-to-stable regressions (also, 1.91 will be promoted to stable next week).

@joshtriplett joshtriplett removed the T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. label Nov 1, 2025
@joshtriplett
Copy link
Member

@rfcbot merge

@rust-rfcbot
Copy link
Collaborator

rust-rfcbot commented Nov 1, 2025

Team member @joshtriplett has proposed to merge this. The next step is review by the rest of the tagged team members:

No concerns currently listed.

Once a majority of reviewers approve (and at most 2 approvals are outstanding), this will enter its final comment period. If you spot a major issue that hasn't been raised at any point in this process, please speak up!

cc @rust-lang/lang-advisors: FCP proposed for lang, please feel free to register concerns.
See this document for info about what commands tagged team members can give me.

@rust-rfcbot rust-rfcbot added proposed-final-comment-period Proposed to merge/close by relevant subteam, see T-<team> label. Will enter FCP once signed off. disposition-merge This issue / PR is in PFCP or FCP with a disposition to merge it. labels Nov 1, 2025
@joshtriplett joshtriplett added T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. and removed proposed-final-comment-period Proposed to merge/close by relevant subteam, see T-<team> label. Will enter FCP once signed off. disposition-merge This issue / PR is in PFCP or FCP with a disposition to merge it. labels Nov 1, 2025
if chain_refutabilities.iter().any(|x| x.is_some()) {
self.check_let_chain(chain_refutabilities, ex.span);
// Check only single let binding
if let [Some((_, refutability))] = chain_refutabilities[..] {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if let [Some((_, refutability))] = chain_refutabilities[..] {
if let [Some((_, Irrefutable))] = chain_refutabilities[..] {

Could go either way, I suppose, but it almost seems worth just doing the rest of the check inline here. It's already checking the "only one" part; maybe it should just check the "irrefutable" part also. Then either check_single_let could be renamed to lint_single_let (since the check has been moved out of it) or perhaps be fully inlined.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've pushed a new commit addressing this suggestion. Not sure if it meets your expectations

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, looks right. With it factored this way, might as well use lint_single_let in check_let as well.

@traviscross
Copy link
Contributor

@rfcbot reviewed

@rust-rfcbot rust-rfcbot added proposed-final-comment-period Proposed to merge/close by relevant subteam, see T-<team> label. Will enter FCP once signed off. disposition-merge This issue / PR is in PFCP or FCP with a disposition to merge it. labels Nov 2, 2025
@rustbot

This comment has been minimized.

@rust-log-analyzer

This comment has been minimized.

if chain_refutabilities.iter().any(|x| x.is_some()) {
self.check_let_chain(chain_refutabilities, ex.span);
// Lint only single irrefutable let binding.
if let [Some((_, Irrefutable))] = chain_refutabilities[..] {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if there are multiple bindings, all irrefutable?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not linting as long as it is chains. In #139369 , people have nearly reached a consensus on this point.

@rust-log-analyzer

This comment has been minimized.

@rust-log-analyzer

This comment has been minimized.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

disposition-merge This issue / PR is in PFCP or FCP with a disposition to merge it. I-lang-nominated Nominated for discussion during a lang team meeting. I-lang-radar Items that are on lang's radar and will need eventual work or consideration. needs-fcp This change is insta-stable, or significant enough to need a team FCP to proceed. P-lang-drag-2 Lang team prioritization drag level 2.https://rust-lang.zulipchat.com/#narrow/channel/410516-t-lang. proposed-final-comment-period Proposed to merge/close by relevant subteam, see T-<team> label. Will enter FCP once signed off. S-waiting-on-t-lang Status: Awaiting decision from T-lang T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-lang Relevant to the language team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

10 participants