Skip to content

Remove let_chains unstable feature #143214

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

Merged
merged 1 commit into from
Jul 2, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion compiler/rustc_ast_passes/src/feature_gate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -469,7 +469,6 @@ pub fn check_crate(krate: &ast::Crate, sess: &Session, features: &Features) {
"`if let` guards are experimental",
"you can write `if matches!(<expr>, <pattern>)` instead of `if let <pattern> = <expr>`"
);
gate_all!(let_chains, "`let` expressions in this position are unstable");
gate_all!(
async_trait_bounds,
"`async` trait bounds are unstable",
Expand Down
2 changes: 2 additions & 0 deletions compiler/rustc_feature/src/accepted.rs
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,8 @@ declare_features! (
(accepted, keylocker_x86, "CURRENT_RUSTC_VERSION", Some(134813)),
/// Allows `'a: { break 'a; }`.
(accepted, label_break_value, "1.65.0", Some(48594)),
/// Allows `if/while p && let q = r && ...` chains.
(accepted, let_chains, "1.88.0", Some(53667)),
/// Allows `let...else` statements.
(accepted, let_else, "1.65.0", Some(87335)),
/// Allows using `reason` in lint attributes and the `#[expect(lint)]` lint check.
Expand Down
2 changes: 0 additions & 2 deletions compiler/rustc_feature/src/unstable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -552,8 +552,6 @@ declare_features! (
(unstable, large_assignments, "1.52.0", Some(83518)),
/// Allow to have type alias types for inter-crate use.
(incomplete, lazy_type_alias, "1.72.0", Some(112792)),
/// Allows `if/while p && let q = r && ...` chains.
(unstable, let_chains, "1.37.0", Some(53667)),
/// Allows using `#[link(kind = "link-arg", name = "...")]`
/// to pass custom arguments to the linker.
(unstable, link_arg_attribute, "1.76.0", Some(99427)),
Expand Down
2 changes: 2 additions & 0 deletions compiler/rustc_parse/messages.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -512,6 +512,8 @@ parse_leading_underscore_unicode_escape_label = invalid start of unicode escape
parse_left_arrow_operator = unexpected token: `<-`
.suggestion = if you meant to write a comparison against a negative value, add a space in between `<` and `-`
parse_let_chain_pre_2024 = let chains are only allowed in Rust 2024 or later
parse_lifetime_after_mut = lifetime must precede `mut`
.suggestion = place the lifetime before `mut`
Expand Down
7 changes: 7 additions & 0 deletions compiler/rustc_parse/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1778,6 +1778,13 @@ pub(crate) struct AsyncBoundModifierIn2015 {
pub help: HelpUseLatestEdition,
}

#[derive(Diagnostic)]
#[diag(parse_let_chain_pre_2024)]
pub(crate) struct LetChainPre2024 {
#[primary_span]
pub span: Span,
}

#[derive(Diagnostic)]
#[diag(parse_self_argument_pointer)]
pub(crate) struct SelfArgumentPointer {
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_parse/src/parser/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4117,7 +4117,7 @@ impl MutVisitor for CondChecker<'_> {
LetChainsPolicy::AlwaysAllowed => (),
LetChainsPolicy::EditionDependent { current_edition } => {
if !current_edition.at_least_rust_2024() || !span.at_least_rust_2024() {
self.parser.psess.gated_spans.gate(sym::let_chains, span);
self.parser.dcx().emit_err(errors::LetChainPre2024 { span });
}
}
}
Expand Down
2 changes: 2 additions & 0 deletions src/tools/rustfmt/tests/source/let_chains.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// rustfmt-edition: 2024

fn main() {
if let x = x && x {}

Expand Down
2 changes: 2 additions & 0 deletions src/tools/rustfmt/tests/target/let_chains.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
// rustfmt-edition: 2024

fn main() {
if let x = x
&& x
Expand Down
4 changes: 2 additions & 2 deletions tests/coverage/branch/if-let.coverage
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
LL| |#![feature(coverage_attribute, let_chains)]
LL| |//@ edition: 2021
LL| |#![feature(coverage_attribute)]
LL| |//@ edition: 2024
LL| |//@ compile-flags: -Zcoverage-options=branch
LL| |//@ llvm-cov-flags: --show-branches=count
LL| |
Expand Down
4 changes: 2 additions & 2 deletions tests/coverage/branch/if-let.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#![feature(coverage_attribute, let_chains)]
//@ edition: 2021
#![feature(coverage_attribute)]
//@ edition: 2024
//@ compile-flags: -Zcoverage-options=branch
//@ llvm-cov-flags: --show-branches=count

Expand Down
145 changes: 145 additions & 0 deletions tests/ui/drop/drop-order-comparisons-let-chains.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
// See drop-order-comparisons.rs

//@ edition: 2024
//@ run-pass

#![feature(if_let_guard)]

fn t_if_let_chains_then() {
let e = Events::new();
_ = if e.ok(1).is_ok()
&& let true = e.ok(9).is_ok()
&& let Ok(_v) = e.ok(8)
&& let Ok(_) = e.ok(7)
&& let Ok(_) = e.ok(6).as_ref()
&& e.ok(2).is_ok()
&& let Ok(_v) = e.ok(5)
&& let Ok(_) = e.ok(4).as_ref() {
e.mark(3);
};
e.assert(9);
}

fn t_guard_if_let_chains_then() {
let e = Events::new();
_ = match () {
() if e.ok(1).is_ok()
&& let true = e.ok(9).is_ok()
&& let Ok(_v) = e.ok(8)
&& let Ok(_) = e.ok(7)
&& let Ok(_) = e.ok(6).as_ref()
&& e.ok(2).is_ok()
&& let Ok(_v) = e.ok(5)
&& let Ok(_) = e.ok(4).as_ref() => {
e.mark(3);
}
_ => {}
};
e.assert(9);
}

fn t_if_let_chains_then_else() {
let e = Events::new();
_ = if e.ok(1).is_ok()
&& let true = e.ok(8).is_ok()
&& let Ok(_v) = e.ok(7)
&& let Ok(_) = e.ok(6)
&& let Ok(_) = e.ok(5).as_ref()
&& e.ok(2).is_ok()
&& let Ok(_v) = e.ok(4)
&& let Ok(_) = e.err(3) {} else {
e.mark(9);
};
e.assert(9);
}

fn t_guard_if_let_chains_then_else() {
let e = Events::new();
_ = match () {
() if e.ok(1).is_ok()
&& let true = e.ok(8).is_ok()
&& let Ok(_v) = e.ok(7)
&& let Ok(_) = e.ok(6)
&& let Ok(_) = e.ok(5).as_ref()
&& e.ok(2).is_ok()
&& let Ok(_v) = e.ok(4)
&& let Ok(_) = e.err(3) => {}
_ => {
e.mark(9);
}
};
e.assert(9);
}

fn main() {
t_if_let_chains_then();
t_guard_if_let_chains_then();
t_if_let_chains_then_else();
t_guard_if_let_chains_then_else();
}

// # Test scaffolding

use core::cell::RefCell;
use std::collections::HashSet;

/// A buffer to track the order of events.
///
/// First, numbered events are logged into this buffer.
///
/// Then, `assert` is called to verify that the correct number of
/// events were logged, and that they were logged in the expected
/// order.
struct Events(RefCell<Option<Vec<u64>>>);

impl Events {
const fn new() -> Self {
Self(RefCell::new(Some(Vec::new())))
}
#[track_caller]
fn assert(&self, max: u64) {
let buf = &self.0;
let v1 = buf.borrow().as_ref().unwrap().clone();
let mut v2 = buf.borrow().as_ref().unwrap().clone();
*buf.borrow_mut() = None;
v2.sort();
let uniq_len = v2.iter().collect::<HashSet<_>>().len();
// Check that the sequence is sorted.
assert_eq!(v1, v2);
// Check that there are no duplicates.
assert_eq!(v2.len(), uniq_len);
// Check that the length is the expected one.
assert_eq!(max, uniq_len as u64);
// Check that the last marker is the expected one.
assert_eq!(v2.last().unwrap(), &max);
}
/// Return an `Ok` value that logs its drop.
fn ok(&self, m: u64) -> Result<LogDrop<'_>, LogDrop<'_>> {
Ok(LogDrop(self, m))
}
/// Return an `Err` value that logs its drop.
fn err(&self, m: u64) -> Result<LogDrop<'_>, LogDrop<'_>> {
Err(LogDrop(self, m))
}
/// Log an event.
fn mark(&self, m: u64) {
self.0.borrow_mut().as_mut().unwrap().push(m);
}
}

impl Drop for Events {
fn drop(&mut self) {
if self.0.borrow().is_some() {
panic!("failed to call `Events::assert()`");
}
}
}

/// A type that logs its drop events.
struct LogDrop<'b>(&'b Events, u64);

impl<'b> Drop for LogDrop<'b> {
fn drop(&mut self) {
self.0.mark(self.1);
}
}
114 changes: 3 additions & 111 deletions tests/ui/drop/drop-order-comparisons.e2021.fixed
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// N.B. drop-order-comparisons-let-chains.rs is part of this test.
// It is separate because let chains cannot be parsed before Rust 2024.
//
// This tests various aspects of the drop order with a focus on:
//
// - The lifetime of temporaries with the `if let` construct (and with
Expand Down Expand Up @@ -25,7 +28,6 @@
//@ run-pass

#![feature(if_let_guard)]
#![cfg_attr(e2021, feature(let_chains))]
#![cfg_attr(e2021, warn(rust_2024_compatibility))]

fn t_bindings() {
Expand Down Expand Up @@ -311,59 +313,6 @@ fn t_let_else_chained_then() {
e.assert(9);
}

#[cfg(e2021)]
#[rustfmt::skip]
fn t_if_let_chains_then() {
let e = Events::new();
_ = if e.ok(1).is_ok()
&& let true = e.ok(9).is_ok()
&& let Ok(_v) = e.ok(5)
&& let Ok(_) = e.ok(8)
&& let Ok(_) = e.ok(7).as_ref()
&& e.ok(2).is_ok()
&& let Ok(_v) = e.ok(4)
&& let Ok(_) = e.ok(6).as_ref() {
e.mark(3);
};
e.assert(9);
}

#[cfg(e2024)]
#[rustfmt::skip]
fn t_if_let_chains_then() {
let e = Events::new();
_ = if e.ok(1).is_ok()
&& let true = e.ok(9).is_ok()
&& let Ok(_v) = e.ok(8)
&& let Ok(_) = e.ok(7)
&& let Ok(_) = e.ok(6).as_ref()
&& e.ok(2).is_ok()
&& let Ok(_v) = e.ok(5)
&& let Ok(_) = e.ok(4).as_ref() {
e.mark(3);
};
e.assert(9);
}

#[rustfmt::skip]
fn t_guard_if_let_chains_then() {
let e = Events::new();
_ = match () {
() if e.ok(1).is_ok()
&& let true = e.ok(9).is_ok()
&& let Ok(_v) = e.ok(8)
&& let Ok(_) = e.ok(7)
&& let Ok(_) = e.ok(6).as_ref()
&& e.ok(2).is_ok()
&& let Ok(_v) = e.ok(5)
&& let Ok(_) = e.ok(4).as_ref() => {
e.mark(3);
}
_ => {}
};
e.assert(9);
}

#[cfg(e2021)]
#[rustfmt::skip]
fn t_if_let_nested_else() {
Expand Down Expand Up @@ -470,59 +419,6 @@ fn t_let_else_chained_then_else() {
e.assert(9);
}

#[cfg(e2021)]
#[rustfmt::skip]
fn t_if_let_chains_then_else() {
let e = Events::new();
_ = if e.ok(1).is_ok()
&& let true = e.ok(9).is_ok()
&& let Ok(_v) = e.ok(4)
&& let Ok(_) = e.ok(8)
&& let Ok(_) = e.ok(7).as_ref()
&& e.ok(2).is_ok()
&& let Ok(_v) = e.ok(3)
&& let Ok(_) = e.err(6) {} else {
e.mark(5);
};
e.assert(9);
}

#[cfg(e2024)]
#[rustfmt::skip]
fn t_if_let_chains_then_else() {
let e = Events::new();
_ = if e.ok(1).is_ok()
&& let true = e.ok(8).is_ok()
&& let Ok(_v) = e.ok(7)
&& let Ok(_) = e.ok(6)
&& let Ok(_) = e.ok(5).as_ref()
&& e.ok(2).is_ok()
&& let Ok(_v) = e.ok(4)
&& let Ok(_) = e.err(3) {} else {
e.mark(9);
};
e.assert(9);
}

#[rustfmt::skip]
fn t_guard_if_let_chains_then_else() {
let e = Events::new();
_ = match () {
() if e.ok(1).is_ok()
&& let true = e.ok(8).is_ok()
&& let Ok(_v) = e.ok(7)
&& let Ok(_) = e.ok(6)
&& let Ok(_) = e.ok(5).as_ref()
&& e.ok(2).is_ok()
&& let Ok(_v) = e.ok(4)
&& let Ok(_) = e.err(3) => {}
_ => {
e.mark(9);
}
};
e.assert(9);
}

fn main() {
t_bindings();
t_tuples();
Expand All @@ -540,13 +436,9 @@ fn main() {
t_if_let_else_tailexpr();
t_if_let_nested_then();
t_let_else_chained_then();
t_if_let_chains_then();
t_guard_if_let_chains_then();
t_if_let_nested_else();
t_if_let_nested_then_else();
t_let_else_chained_then_else();
t_if_let_chains_then_else();
t_guard_if_let_chains_then_else();
}

// # Test scaffolding
Expand Down
Loading
Loading