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

Stabilize let_chains in Rust 1.64 #94927

Merged
merged 1 commit into from
Jul 17, 2022
Merged

Conversation

c410-f3r
Copy link
Contributor

@c410-f3r c410-f3r commented Mar 14, 2022

Stabilization proposal

This PR proposes the stabilization of #![feature(let_chains)] in a future-compatibility way that will allow the possible addition of the EXPR is PAT syntax.

Tracking issue: #53667
Version: 1.64 (beta => 2022-08-11, stable => 2022-10-22).

What is stabilized

The ability to chain let expressions along side local variable declarations or ordinary conditional expressions. For example:

pub enum Color {
    Blue,
    Red,
    Violet,
}

pub enum Flower {
    Rose,
    Tulip,
    Violet,
}

pub fn roses_are_red_violets_are_blue_printer(
    (first_flower, first_flower_color): (Flower, Color),
    (second_flower, second_flower_color): (Flower, Color),
    pick_up_lines: &[&str],
) {
    if let Flower::Rose = first_flower
        && let Color::Red = first_flower_color
        && let Flower::Violet = second_flower
        && let Color::Blue = second_flower_color
        && let &[first_pick_up_line, ..] = pick_up_lines
    {
        println!("Roses are red, violets are blue, {}", first_pick_up_line);
    }
}

fn main() {
    roses_are_red_violets_are_blue_printer(
        (Flower::Rose, Color::Red),
        (Flower::Violet, Color::Blue),
        &["sugar is sweet and so are you"],
    );
}

Motivation

The main motivation for this feature is improving readability, ergonomics and reducing paper cuts.

For more examples, see the RFC.

What isn't stabilized

  • Let chains in match guards (if_let_guard)

  • Resolution of divergent non-terminal matchers

  • The EXPR is PAT syntax

History

From the first RFC (2017-12-24) to the theoretical future stabilization day (2022-10-22), it can be said that this feature took 4 years, 9 months and 28 days of research, development, discussions, agreements and headaches to be settled.

Divergent non-terminal matchers

More specifically, #86730.

macro_rules! mac {
    ($e:expr) => {
        if $e {
            true
        } else {
            false
        }
    };
}

fn main() {
    // OK!
    assert_eq!(mac!(true && let 1 = 1), true);

    // ERROR! Anything starting with `let` is not considered an expression
    assert_eq!(mac!(let 1 = 1 && true), true);
}

To the best of my knowledge, such error or divergence is orthogonal, does not prevent stabilization and can be tackled independently in the near future or effectively in the next Rust 2024 edition. If not, then https://github.com/c410-f3r/rust/tree/let-macro-blah contains a set of changes that will consider let an expression.

It is possible that none of the solutions above satisfies all applicable constraints but I personally don't know of any other plausible answers.

Alternative syntax

Taking into account the usefulness of this feature and the overwhelming desire to use both now and in the past, let PAT = EXPR will be utilized for stabilization but it doesn't or shall create any obstacle for a possible future addition of EXPR is PAT.

The introductory snippet would then be written as the following.

if first_flower is Flower::Rose 
    && first_flower_color is Color::Red
    && second_flower is Flower::Violet
    && second_flower_color is Color::Blue
    && pick_up_lines is &[first_pick_up_line, ..]
{
    println!("Roses are red, violets are blue, {}", first_pick_up_line);
}

Just to reinforce, this PR only unblocks a possible future road for EXPR is PAT and does emphasize what is better or what is worse.

Tests

Most of the infra-structure used by let chains is also used by if expressions in stable compiler versions since #80357 and #88572. As a result, no bugs were found since the integration of #88642.

Possible future work

Thanks @Centril for creating the RFC and huge thanks (again) to @matthewjasper for all the reviews, mentoring and MIR implementations.

Fixes #53667

@rustbot rustbot added the T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. label Mar 14, 2022
@rust-highfive
Copy link
Collaborator

Some changes occurred in src/tools/clippy.

cc @rust-lang/clippy

@rust-highfive
Copy link
Collaborator

r? @estebank

(rust-highfive has picked a reviewer for you, use r? to override)

@rust-highfive rust-highfive added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Mar 14, 2022
@rust-log-analyzer

This comment has been minimized.

@joshtriplett joshtriplett added T-lang Relevant to the language team, which will review and decide on the PR/issue. I-lang-nominated Nominated for discussion during a lang team meeting. labels Mar 14, 2022
@est31
Copy link
Member

est31 commented Mar 14, 2022

I wonder about linting for irrefutable assignments. This code gives you a irrefutable_let_patterns warning:

if let v = 4 + 4 { /*...*/ }
//^ WARNING: irrefutable `if let` pattern

On a similar note, I expect this code to lint:

if let v = 4 + 4 && let w = 8 + 8 { ... }

But there is no warning at all. It's a bit confusing to have an if here despite it never visiting the else clause.

Generally, it makes sense to not lint for irrefutable patterns inside if let chains, because often you might need to create a binding that you then use later. I've done this a few times for my clippy PR rust-lang/rust-clippy#8437 . And even if the binding is never used by later patterns, it might still create some side effects that you want.

However, I think both trailing and leading irrefutable patterns should be linted, as well as the combination of both cases, let chains made up of only irrefutable assignments. Leading assignments can be made part of the body the if is part of, and trailing assignments can be made part of the body, and in the case of chains made up only of irrefutable patterns one doesn't need an if at all.

I'm not sure if this lint needs to be in rust, or it is better present in clippy though. As the irrefutable_let_patterns lint is builtin, one can make the case that this one should be builtin, too.

@joshtriplett
Copy link
Member

@est31 I agree. Technically we can always introduce that lint later, but I think we should have it in place before we ship let chains in stable, since it will affect how people start writing let chains.

@estebank
Copy link
Contributor

@joshtriplett, should a PR like this one should be approved by consensus of t-lang, or should t-compiler take on that responsibility once we receive a go ahead? Either way, I don't currently have the bandwidth to shepherd this myself and would hate to be the cause of it stalling.

@joshtriplett
Copy link
Member

@estebank Historically, I think the way we've handled that is to have two separate aspects of approval: the feature stabilization, and the PR content.

  • T-lang reviews and approves "do we want to stabilize this", which doesn't depend on the exact content of the PR. (That review and approval would be based on input about the state of the available feature; if T-compiler tells us a feature isn't ready to consider stabilization, we're not going to approve a stabilization.)
  • Anyone who can review the actual content of the PR does so. For small PRs that review process might consist of one reviewer and an r+; for larger or more invasive PRs that could potentially include an FCP or MCP.

In this particular case, while the PR is large, it looks like a straightforward stabilization of the feature gate, plus the extensive updating of usages of the feature gate. Seems like any compiler reviewer could reasonably r+ this, once the stabilization is approved?

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

@rust-lang/lang, shall we stabilize let chains?

@rfcbot merge

@rfcbot concern irrefutable-lint

@rfcbot
Copy link

rfcbot commented Mar 15, 2022

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

Concerns:

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!

See this document for info about what commands tagged team members can give me.

@rfcbot 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 Mar 15, 2022
@flip1995
Copy link
Member

I think both trailing and leading irrefutable patterns should be linted

FWIW Clippy has an internal lint for exactly this inside the if_chain! macro.

if is_first_if_chain_expr(cx, block.hir_id, if_chain_span) {
span_lint(
cx,
IF_CHAIN_STYLE,
if_chain_local_span(cx, local, if_chain_span),
"`let` expression should be above the `if_chain!`",
);
} else if local.span.ctxt() == block.span.ctxt() && is_if_chain_then(after, block.expr, if_chain_span) {
span_lint(
cx,
IF_CHAIN_STYLE,
if_chain_local_span(cx, local, if_chain_span),
"`let` expression should be inside `then { .. }`",
);
}

This lint could be used as a basis for implementing the same for let_chains.

@est31
Copy link
Member

est31 commented Mar 15, 2022

I've filed a PR to extend the irrefutable patterns lint to the prefixes/suffixes of let chains: #94951

@flip1995 thanks for the suggestion about the clippy lint. Maybe that lint is even the cause for my suggestion above, as it's possible I got warned by it back when I wrote my clippy PR, I'm not sure any more. I've checked out the implementation to see if I can do something differently in #94951. Mostly I note that it's more compact, probably due to the rich set of utilities that clippy has. Also, maybe the error messages can be improved a little.

@flip1995
Copy link
Member

I've checked out the implementation to see if I can do something differently in #94951.

Yeah the clippy utils really help with writing lints. It might also just be that internal Clippy lints usually don't really care about producing good suggestions/messages, because they're not user facing and we have a lower bar there.

@nikomatsakis
Copy link
Contributor

@rfcbot fcp reviewed

+1 in general

@nikomatsakis
Copy link
Contributor

@rfcbot concern doc-pr

Do we have a pending update to the Rust reference?

@nikomatsakis
Copy link
Contributor

Big thank you to @c410-f3r and others involved in pushing this feature forward!

@estebank
Copy link
Contributor

@rfcbot concern let_else-interaction

Do we have existing tests verifying that this feature interacts nicely with let_else, both in parsing and semantics? It might be enough to extend https://github.com/rust-lang/rust/blob/master/src/test/ui/rfc-2497-if-let-chains/irrefutable-lets.rs with a single let/else expression.

@joshtriplett
Copy link
Member

joshtriplett commented Mar 15, 2022

@estebank When that has come up previously, our general conclusion of the interaction between let chains and let else was "don't": they don't mix at all.

Adding a failing test to ensure that the compiler does not allow attaching an else to a let that's part of an if or while condition would help.

@bors bors added the S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. label Jul 16, 2022
@c410-f3r
Copy link
Contributor Author

Rebased due to recent code conflict.

@joshtriplett
Copy link
Member

@bors r+

@bors
Copy link
Contributor

bors commented Jul 17, 2022

📌 Commit 3266460 has been approved by joshtriplett

It is now in the queue for this repository.

bors added a commit to rust-lang-ci/rust that referenced this pull request Jul 17, 2022
Rollup of 7 pull requests

Successful merges:

 - rust-lang#94927 (Stabilize `let_chains` in Rust 1.64)
 - rust-lang#97915 (Implement `fmt::Write` for `OsString`)
 - rust-lang#99036 (Add `#[test]` to functions in test modules)
 - rust-lang#99088 (Document and stabilize process_set_process_group)
 - rust-lang#99302 (add tracking issue to generic member access APIs)
 - rust-lang#99306 (Stabilize `future_poll_fn`)
 - rust-lang#99354 (Add regression test for rust-lang#95829)

Failed merges:

r? `@ghost`
`@rustbot` modify labels: rollup
@bors bors merged commit 353d018 into rust-lang:master Jul 17, 2022
@rustbot rustbot added this to the 1.64.0 milestone Jul 17, 2022
@WiSaGaN
Copy link
Contributor

WiSaGaN commented Aug 19, 2022

Just for reference, this was reverted in #100538, thus will likely not be in 1.64 release.

@jhpratt
Copy link
Member

jhpratt commented Aug 19, 2022

Because of this ^^

@rustbot label -relnotes

@rustbot rustbot removed the relnotes Marks issues that should be documented in the release notes of the next release. label Aug 19, 2022
workingjubilee added a commit to workingjubilee/rustc that referenced this pull request Nov 10, 2024
…compiler-errors

Additional tests to ensure let is rejected during parsing

In the original stabilization PR, @ `compiler-errors` has [pointed out](rust-lang#94927 (comment)) that rust-lang#97295 wasn't enough to address the concerns about having `let` in expressions being rejected at parsing time, instead of later.

Thankfully, since then the situation has been greatly improved by rust-lang#115677. This PR adds some additional tests to `disallowed-positions.rs`, and adds two additional revisions to the "normal" case which is now given the `feature` name:

* `no_feature`: Added to incorporate `disallowed-positions-without-feature-gate.rs` into the file, reducing duplication.
* `nothing`: like feature, but all functions are cfg'd out. Ensures that the errors are really emitted during parsing.

cc tracking issue rust-lang#53667
rust-timer added a commit to rust-lang-ci/rust that referenced this pull request Nov 10, 2024
Rollup merge of rust-lang#132828 - est31:let_chains_parsing_tests, r=compiler-errors

Additional tests to ensure let is rejected during parsing

In the original stabilization PR, @ `compiler-errors` has [pointed out](rust-lang#94927 (comment)) that rust-lang#97295 wasn't enough to address the concerns about having `let` in expressions being rejected at parsing time, instead of later.

Thankfully, since then the situation has been greatly improved by rust-lang#115677. This PR adds some additional tests to `disallowed-positions.rs`, and adds two additional revisions to the "normal" case which is now given the `feature` name:

* `no_feature`: Added to incorporate `disallowed-positions-without-feature-gate.rs` into the file, reducing duplication.
* `nothing`: like feature, but all functions are cfg'd out. Ensures that the errors are really emitted during parsing.

cc tracking issue rust-lang#53667
RalfJung pushed a commit to RalfJung/miri that referenced this pull request Nov 10, 2024
…errors

Additional tests to ensure let is rejected during parsing

In the original stabilization PR, @ `compiler-errors` has [pointed out](rust-lang/rust#94927 (comment)) that #97295 wasn't enough to address the concerns about having `let` in expressions being rejected at parsing time, instead of later.

Thankfully, since then the situation has been greatly improved by #115677. This PR adds some additional tests to `disallowed-positions.rs`, and adds two additional revisions to the "normal" case which is now given the `feature` name:

* `no_feature`: Added to incorporate `disallowed-positions-without-feature-gate.rs` into the file, reducing duplication.
* `nothing`: like feature, but all functions are cfg'd out. Ensures that the errors are really emitted during parsing.

cc tracking issue #53667
mati865 pushed a commit to mati865/rust that referenced this pull request Nov 12, 2024
…compiler-errors

Additional tests to ensure let is rejected during parsing

In the original stabilization PR, @ `compiler-errors` has [pointed out](rust-lang#94927 (comment)) that rust-lang#97295 wasn't enough to address the concerns about having `let` in expressions being rejected at parsing time, instead of later.

Thankfully, since then the situation has been greatly improved by rust-lang#115677. This PR adds some additional tests to `disallowed-positions.rs`, and adds two additional revisions to the "normal" case which is now given the `feature` name:

* `no_feature`: Added to incorporate `disallowed-positions-without-feature-gate.rs` into the file, reducing duplication.
* `nothing`: like feature, but all functions are cfg'd out. Ensures that the errors are really emitted during parsing.

cc tracking issue rust-lang#53667
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. final-comment-period In the final comment period and will be merged soon unless new substantive objections are raised. finished-final-comment-period The final comment period is finished for this PR / Issue. S-waiting-on-bors Status: Waiting on bors to run and complete tests. Bors will change the label on completion. T-lang Relevant to the language team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Tracking issue for eRFC 2497, "if- and while-let-chains, take 2"