From 75f57670b03a33dd165685fe1f252969ef5c16ea Mon Sep 17 00:00:00 2001
From: Ralf Jung <post@ralfj.de>
Date: Wed, 8 May 2024 17:22:35 +0200
Subject: [PATCH 01/12] miri: rename intrinsic_fallback_checks_ub to
 intrinsic_fallback_is_spec

---
 library/core/src/intrinsics.rs                     | 10 +++++-----
 src/tools/miri/src/intrinsics/mod.rs               |  6 +++---
 .../tests/fail/intrinsic_fallback_checks_ub.stderr | 14 --------------
 ..._checks_ub.rs => intrinsic_fallback_is_spec.rs} |  2 +-
 .../tests/fail/intrinsic_fallback_is_spec.stderr   | 14 ++++++++++++++
 5 files changed, 23 insertions(+), 23 deletions(-)
 delete mode 100644 src/tools/miri/tests/fail/intrinsic_fallback_checks_ub.stderr
 rename src/tools/miri/tests/fail/{intrinsic_fallback_checks_ub.rs => intrinsic_fallback_is_spec.rs} (75%)
 create mode 100644 src/tools/miri/tests/fail/intrinsic_fallback_is_spec.stderr

diff --git a/library/core/src/intrinsics.rs b/library/core/src/intrinsics.rs
index d1450bf12ce..1027d483640 100644
--- a/library/core/src/intrinsics.rs
+++ b/library/core/src/intrinsics.rs
@@ -987,7 +987,7 @@ pub const unsafe fn assume(b: bool) {
 #[unstable(feature = "core_intrinsics", issue = "none")]
 #[rustc_intrinsic]
 #[rustc_nounwind]
-#[cfg_attr(not(bootstrap), miri::intrinsic_fallback_checks_ub)]
+#[cfg_attr(not(bootstrap), miri::intrinsic_fallback_is_spec)]
 pub const fn likely(b: bool) -> bool {
     b
 }
@@ -1007,7 +1007,7 @@ pub const fn likely(b: bool) -> bool {
 #[unstable(feature = "core_intrinsics", issue = "none")]
 #[rustc_intrinsic]
 #[rustc_nounwind]
-#[cfg_attr(not(bootstrap), miri::intrinsic_fallback_checks_ub)]
+#[cfg_attr(not(bootstrap), miri::intrinsic_fallback_is_spec)]
 pub const fn unlikely(b: bool) -> bool {
     b
 }
@@ -2471,7 +2471,7 @@ extern "rust-intrinsic" {
 #[rustc_nounwind]
 #[rustc_do_not_const_check]
 #[inline]
-#[cfg_attr(not(bootstrap), miri::intrinsic_fallback_checks_ub)]
+#[cfg_attr(not(bootstrap), miri::intrinsic_fallback_is_spec)]
 pub const fn ptr_guaranteed_cmp<T>(ptr: *const T, other: *const T) -> u8 {
     (ptr == other) as u8
 }
@@ -2736,7 +2736,7 @@ pub const fn ub_checks() -> bool {
 #[unstable(feature = "core_intrinsics", issue = "none")]
 #[rustc_nounwind]
 #[rustc_intrinsic]
-#[cfg_attr(not(bootstrap), miri::intrinsic_fallback_checks_ub)]
+#[cfg_attr(not(bootstrap), miri::intrinsic_fallback_is_spec)]
 pub const unsafe fn const_allocate(_size: usize, _align: usize) -> *mut u8 {
     // const eval overrides this function, but runtime code for now just returns null pointers.
     // See <https://github.com/rust-lang/rust/issues/93935>.
@@ -2757,7 +2757,7 @@ pub const unsafe fn const_allocate(_size: usize, _align: usize) -> *mut u8 {
 #[unstable(feature = "core_intrinsics", issue = "none")]
 #[rustc_nounwind]
 #[rustc_intrinsic]
-#[cfg_attr(not(bootstrap), miri::intrinsic_fallback_checks_ub)]
+#[cfg_attr(not(bootstrap), miri::intrinsic_fallback_is_spec)]
 pub const unsafe fn const_deallocate(_ptr: *mut u8, _size: usize, _align: usize) {
     // Runtime NOP
 }
diff --git a/src/tools/miri/src/intrinsics/mod.rs b/src/tools/miri/src/intrinsics/mod.rs
index effd7f6d543..d94485a42a8 100644
--- a/src/tools/miri/src/intrinsics/mod.rs
+++ b/src/tools/miri/src/intrinsics/mod.rs
@@ -43,18 +43,18 @@ pub trait EvalContextExt<'mir, 'tcx: 'mir>: crate::MiriInterpCxExt<'mir, 'tcx> {
                 if this.tcx.intrinsic(instance.def_id()).unwrap().must_be_overridden {
                     throw_unsup_format!("unimplemented intrinsic: `{intrinsic_name}`")
                 }
-                let intrinsic_fallback_checks_ub = Symbol::intern("intrinsic_fallback_checks_ub");
+                let intrinsic_fallback_is_spec = Symbol::intern("intrinsic_fallback_is_spec");
                 if this
                     .tcx
                     .get_attrs_by_path(
                         instance.def_id(),
-                        &[sym::miri, intrinsic_fallback_checks_ub],
+                        &[sym::miri, intrinsic_fallback_is_spec],
                     )
                     .next()
                     .is_none()
                 {
                     throw_unsup_format!(
-                        "miri can only use intrinsic fallback bodies that check UB. After verifying that `{intrinsic_name}` does so, add the `#[miri::intrinsic_fallback_checks_ub]` attribute to it; also ping @rust-lang/miri when you do that"
+                        "Miri can only use intrinsic fallback bodies that exactly reflect the specification: they fully check for UB and are as non-deterministic as possible. After verifying that `{intrinsic_name}` does so, add the `#[miri::intrinsic_fallback_is_spec]` attribute to it; also ping @rust-lang/miri when you do that"
                     );
                 }
                 Ok(Some(ty::Instance {
diff --git a/src/tools/miri/tests/fail/intrinsic_fallback_checks_ub.stderr b/src/tools/miri/tests/fail/intrinsic_fallback_checks_ub.stderr
deleted file mode 100644
index 699dda52096..00000000000
--- a/src/tools/miri/tests/fail/intrinsic_fallback_checks_ub.stderr
+++ /dev/null
@@ -1,14 +0,0 @@
-error: unsupported operation: miri can only use intrinsic fallback bodies that check UB. After verifying that `ptr_guaranteed_cmp` does so, add the `#[miri::intrinsic_fallback_checks_ub]` attribute to it; also ping @rust-lang/miri when you do that
-  --> $DIR/intrinsic_fallback_checks_ub.rs:LL:CC
-   |
-LL |     ptr_guaranteed_cmp::<()>(std::ptr::null(), std::ptr::null());
-   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ miri can only use intrinsic fallback bodies that check UB. After verifying that `ptr_guaranteed_cmp` does so, add the `#[miri::intrinsic_fallback_checks_ub]` attribute to it; also ping @rust-lang/miri when you do that
-   |
-   = help: this is likely not a bug in the program; it indicates that the program performed an operation that Miri does not support
-   = note: BACKTRACE:
-   = note: inside `main` at $DIR/intrinsic_fallback_checks_ub.rs:LL:CC
-
-note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
-
-error: aborting due to 1 previous error
-
diff --git a/src/tools/miri/tests/fail/intrinsic_fallback_checks_ub.rs b/src/tools/miri/tests/fail/intrinsic_fallback_is_spec.rs
similarity index 75%
rename from src/tools/miri/tests/fail/intrinsic_fallback_checks_ub.rs
rename to src/tools/miri/tests/fail/intrinsic_fallback_is_spec.rs
index 93c9d3d7814..888c548e49b 100644
--- a/src/tools/miri/tests/fail/intrinsic_fallback_checks_ub.rs
+++ b/src/tools/miri/tests/fail/intrinsic_fallback_is_spec.rs
@@ -10,5 +10,5 @@ pub const fn ptr_guaranteed_cmp<T>(ptr: *const T, other: *const T) -> u8 {
 
 fn main() {
     ptr_guaranteed_cmp::<()>(std::ptr::null(), std::ptr::null());
-    //~^ ERROR: can only use intrinsic fallback bodies that check UB.
+    //~^ ERROR: can only use intrinsic fallback bodies that exactly reflect the specification
 }
diff --git a/src/tools/miri/tests/fail/intrinsic_fallback_is_spec.stderr b/src/tools/miri/tests/fail/intrinsic_fallback_is_spec.stderr
new file mode 100644
index 00000000000..db3941a32a5
--- /dev/null
+++ b/src/tools/miri/tests/fail/intrinsic_fallback_is_spec.stderr
@@ -0,0 +1,14 @@
+error: unsupported operation: Miri can only use intrinsic fallback bodies that exactly reflect the specification: they fully check for UB and are as non-deterministic as possible. After verifying that `ptr_guaranteed_cmp` does so, add the `#[miri::intrinsic_fallback_is_spec]` attribute to it; also ping @rust-lang/miri when you do that
+  --> $DIR/intrinsic_fallback_is_spec.rs:LL:CC
+   |
+LL |     ptr_guaranteed_cmp::<()>(std::ptr::null(), std::ptr::null());
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Miri can only use intrinsic fallback bodies that exactly reflect the specification: they fully check for UB and are as non-deterministic as possible. After verifying that `ptr_guaranteed_cmp` does so, add the `#[miri::intrinsic_fallback_is_spec]` attribute to it; also ping @rust-lang/miri when you do that
+   |
+   = help: this is likely not a bug in the program; it indicates that the program performed an operation that Miri does not support
+   = note: BACKTRACE:
+   = note: inside `main` at $DIR/intrinsic_fallback_is_spec.rs:LL:CC
+
+note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace
+
+error: aborting due to 1 previous error
+

From 7faa879486f6aa2871634b54cef3f886591ef323 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Le=C3=B3n=20Orell=20Valerian=20Liehr?= <me@fmease.dev>
Date: Sat, 11 May 2024 15:40:34 +0200
Subject: [PATCH 02/12] Pattern types: Prohibit generic args on const params

---
 compiler/rustc_hir_analysis/messages.ftl      |  6 +--
 .../src/hir_ty_lowering/errors.rs             | 12 +++---
 .../src/hir_ty_lowering/mod.rs                | 11 ++++--
 .../bad_const_generics_args_on_const_param.rs | 10 +++++
 ..._const_generics_args_on_const_param.stderr | 38 +++++++++++++++++++
 tests/ui/type/pattern_types/bad_pat.stderr    |  2 +-
 6 files changed, 66 insertions(+), 13 deletions(-)
 create mode 100644 tests/ui/type/pattern_types/bad_const_generics_args_on_const_param.rs
 create mode 100644 tests/ui/type/pattern_types/bad_const_generics_args_on_const_param.stderr

diff --git a/compiler/rustc_hir_analysis/messages.ftl b/compiler/rustc_hir_analysis/messages.ftl
index 3edea0191fa..cf492a2a3fe 100644
--- a/compiler/rustc_hir_analysis/messages.ftl
+++ b/compiler/rustc_hir_analysis/messages.ftl
@@ -371,9 +371,9 @@ hir_analysis_pass_to_variadic_function = can't pass `{$ty}` to variadic function
     .suggestion = cast the value to `{$cast_ty}`
     .help = cast the value to `{$cast_ty}`
 
-hir_analysis_pattern_type_non_const_range = "range patterns must have constant range start and end"
-hir_analysis_pattern_type_wild_pat = "wildcard patterns are not permitted for pattern types"
-    .label = "this type is the same as the inner type without a pattern"
+hir_analysis_pattern_type_non_const_range = range patterns must have constant range start and end
+hir_analysis_pattern_type_wild_pat = wildcard patterns are not permitted for pattern types
+    .label = this type is the same as the inner type without a pattern
 hir_analysis_placeholder_not_allowed_item_signatures = the placeholder `_` is not allowed within types on item signatures for {$kind}
     .label = not allowed in type signatures
 
diff --git a/compiler/rustc_hir_analysis/src/hir_ty_lowering/errors.rs b/compiler/rustc_hir_analysis/src/hir_ty_lowering/errors.rs
index 8e64a9425bb..a8153c4a088 100644
--- a/compiler/rustc_hir_analysis/src/hir_ty_lowering/errors.rs
+++ b/compiler/rustc_hir_analysis/src/hir_ty_lowering/errors.rs
@@ -1388,7 +1388,7 @@ pub enum GenericsArgsErrExtend<'tcx> {
         span: Span,
     },
     SelfTyParam(Span),
-    TyParam(DefId),
+    Param(DefId),
     DefVariant,
     None,
 }
@@ -1504,11 +1504,11 @@ fn generics_args_err_extend<'a>(
         GenericsArgsErrExtend::DefVariant => {
             err.note("enum variants can't have type parameters");
         }
-        GenericsArgsErrExtend::TyParam(def_id) => {
-            if let Some(span) = tcx.def_ident_span(def_id) {
-                let name = tcx.item_name(def_id);
-                err.span_note(span, format!("type parameter `{name}` defined here"));
-            }
+        GenericsArgsErrExtend::Param(def_id) => {
+            let span = tcx.def_ident_span(def_id).unwrap();
+            let kind = tcx.def_descr(def_id);
+            let name = tcx.item_name(def_id);
+            err.span_note(span, format!("{kind} `{name}` defined here"));
         }
         GenericsArgsErrExtend::SelfTyParam(span) => {
             err.span_suggestion_verbose(
diff --git a/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs b/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs
index f7213442ac2..37c6ef84f24 100644
--- a/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs
+++ b/compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs
@@ -1756,7 +1756,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
                 assert_eq!(opt_self_ty, None);
                 let _ = self.prohibit_generic_args(
                     path.segments.iter(),
-                    GenericsArgsErrExtend::TyParam(def_id),
+                    GenericsArgsErrExtend::Param(def_id),
                 );
                 self.lower_ty_param(hir_id)
             }
@@ -2196,10 +2196,15 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
 
                                 hir::ExprKind::Path(hir::QPath::Resolved(
                                     _,
-                                    &hir::Path {
-                                        res: Res::Def(DefKind::ConstParam, def_id), ..
+                                    path @ &hir::Path {
+                                        res: Res::Def(DefKind::ConstParam, def_id),
+                                        ..
                                     },
                                 )) => {
+                                    let _ = self.prohibit_generic_args(
+                                        path.segments.iter(),
+                                        GenericsArgsErrExtend::Param(def_id),
+                                    );
                                     let ty = tcx
                                         .type_of(def_id)
                                         .no_bound_vars()
diff --git a/tests/ui/type/pattern_types/bad_const_generics_args_on_const_param.rs b/tests/ui/type/pattern_types/bad_const_generics_args_on_const_param.rs
new file mode 100644
index 00000000000..050b7b44b4e
--- /dev/null
+++ b/tests/ui/type/pattern_types/bad_const_generics_args_on_const_param.rs
@@ -0,0 +1,10 @@
+#![feature(pattern_types, core_pattern_type)]
+#![allow(internal_features)]
+
+type Pat<const START: u32, const END: u32> =
+    std::pat::pattern_type!(u32 is START::<(), i32, 2>..=END::<_, Assoc = ()>);
+//~^ ERROR type and const arguments are not allowed on const parameter `START`
+//~| ERROR type arguments are not allowed on const parameter `END`
+//~| ERROR associated type bindings are not allowed here
+
+fn main() {}
diff --git a/tests/ui/type/pattern_types/bad_const_generics_args_on_const_param.stderr b/tests/ui/type/pattern_types/bad_const_generics_args_on_const_param.stderr
new file mode 100644
index 00000000000..40effe924da
--- /dev/null
+++ b/tests/ui/type/pattern_types/bad_const_generics_args_on_const_param.stderr
@@ -0,0 +1,38 @@
+error[E0109]: type and const arguments are not allowed on const parameter `START`
+  --> $DIR/bad_const_generics_args_on_const_param.rs:5:44
+   |
+LL |     std::pat::pattern_type!(u32 is START::<(), i32, 2>..=END::<_, Assoc = ()>);
+   |                                    -----   ^^  ^^^  ^ type and const arguments not allowed
+   |                                    |
+   |                                    not allowed on const parameter `START`
+   |
+note: const parameter `START` defined here
+  --> $DIR/bad_const_generics_args_on_const_param.rs:4:16
+   |
+LL | type Pat<const START: u32, const END: u32> =
+   |                ^^^^^
+
+error[E0109]: type arguments are not allowed on const parameter `END`
+  --> $DIR/bad_const_generics_args_on_const_param.rs:5:64
+   |
+LL |     std::pat::pattern_type!(u32 is START::<(), i32, 2>..=END::<_, Assoc = ()>);
+   |                                                          ---   ^ type argument not allowed
+   |                                                          |
+   |                                                          not allowed on const parameter `END`
+   |
+note: const parameter `END` defined here
+  --> $DIR/bad_const_generics_args_on_const_param.rs:4:34
+   |
+LL | type Pat<const START: u32, const END: u32> =
+   |                                  ^^^
+
+error[E0229]: associated type bindings are not allowed here
+  --> $DIR/bad_const_generics_args_on_const_param.rs:5:67
+   |
+LL |     std::pat::pattern_type!(u32 is START::<(), i32, 2>..=END::<_, Assoc = ()>);
+   |                                                                   ^^^^^^^^^^ associated type not allowed here
+
+error: aborting due to 3 previous errors
+
+Some errors have detailed explanations: E0109, E0229.
+For more information about an error, try `rustc --explain E0109`.
diff --git a/tests/ui/type/pattern_types/bad_pat.stderr b/tests/ui/type/pattern_types/bad_pat.stderr
index 4f0f0bc9742..2abf27100c1 100644
--- a/tests/ui/type/pattern_types/bad_pat.stderr
+++ b/tests/ui/type/pattern_types/bad_pat.stderr
@@ -14,7 +14,7 @@ LL | type Positive2 = pattern_type!(i32 is 0..=);
    |
    = note: inclusive ranges must be bounded at the end (`..=b` or `a..=b`)
 
-error: "wildcard patterns are not permitted for pattern types"
+error: wildcard patterns are not permitted for pattern types
   --> $DIR/bad_pat.rs:11:33
    |
 LL | type Wild = pattern_type!(() is _);

From f4931437c249ed5275ae20eb4ac58e3e4484fa98 Mon Sep 17 00:00:00 2001
From: David Tolnay <dtolnay@gmail.com>
Date: Sun, 12 May 2024 12:49:49 -0700
Subject: [PATCH 03/12] Clean up unneeded warnings from let-else syntax test

---
 tests/ui/parser/bad-let-else-statement.rs     |  59 ++--
 tests/ui/parser/bad-let-else-statement.stderr | 300 +++---------------
 2 files changed, 69 insertions(+), 290 deletions(-)

diff --git a/tests/ui/parser/bad-let-else-statement.rs b/tests/ui/parser/bad-let-else-statement.rs
index de41a07568f..8ed55f94959 100644
--- a/tests/ui/parser/bad-let-else-statement.rs
+++ b/tests/ui/parser/bad-let-else-statement.rs
@@ -3,8 +3,7 @@
 #![feature(explicit_tail_calls)]
 
 fn a() {
-    let foo = {
-        //~^ WARN irrefutable `let...else` pattern
+    let 0 = {
         1
     } else {
         //~^ ERROR right curly brace `}` before `else` in a `let...else` statement not allowed
@@ -22,8 +21,7 @@ fn b() {
 }
 
 fn c() {
-    let foo = if true {
-        //~^ WARN irrefutable `let...else` pattern
+    let 0 = if true {
         1
     } else {
         0
@@ -43,8 +41,7 @@ fn d() {
 }
 
 fn e() {
-    let foo = match true {
-        //~^ WARN irrefutable `let...else` pattern
+    let 0 = match true {
         true => 1,
         false => 0
     } else {
@@ -53,10 +50,12 @@ fn e() {
     };
 }
 
-struct X {a: i32}
 fn f() {
-    let foo = X {
-        //~^ WARN irrefutable `let...else` pattern
+    struct X {
+        a: i32,
+    }
+
+    let X { a: 0 } = X {
         a: 1
     } else {
         //~^ ERROR right curly brace `}` before `else` in a `let...else` statement not allowed
@@ -74,8 +73,7 @@ fn g() {
 }
 
 fn h() {
-    let foo = const {
-        //~^ WARN irrefutable `let...else` pattern
+    let 0 = const {
         1
     } else {
         //~^ ERROR right curly brace `}` before `else` in a `let...else` statement not allowed
@@ -84,8 +82,7 @@ fn h() {
 }
 
 fn i() {
-    let foo = &{
-        //~^ WARN irrefutable `let...else` pattern
+    let 0 = &{
         1
     } else {
         //~^ ERROR right curly brace `}` before `else` in a `let...else` statement not allowed
@@ -94,8 +91,8 @@ fn i() {
 }
 
 fn j() {
-    let bar = 0;
-    let foo = bar = { //~ ERROR: cannot assign twice
+    let mut bar = 0;
+    let foo = bar = {
         //~^ WARN irrefutable `let...else` pattern
         1
     } else {
@@ -105,8 +102,7 @@ fn j() {
 }
 
 fn k() {
-    let foo = 1 + {
-        //~^ WARN irrefutable `let...else` pattern
+    let 0 = 1 + {
         1
     } else {
         //~^ ERROR right curly brace `}` before `else` in a `let...else` statement not allowed
@@ -115,8 +111,8 @@ fn k() {
 }
 
 fn l() {
-    let foo = 1..{
-        //~^ WARN irrefutable `let...else` pattern
+    const RANGE: std::ops::Range<u8> = 0..0;
+    let RANGE = 1..{
         1
     } else {
         //~^ ERROR right curly brace `}` before `else` in a `let...else` statement not allowed
@@ -125,8 +121,7 @@ fn l() {
 }
 
 fn m() {
-    let foo = return {
-        //~^ WARN irrefutable `let...else` pattern
+    let 0 = return {
         ()
     } else {
         //~^ ERROR right curly brace `}` before `else` in a `let...else` statement not allowed
@@ -135,8 +130,7 @@ fn m() {
 }
 
 fn n() {
-    let foo = -{
-        //~^ WARN irrefutable `let...else` pattern
+    let 0 = -{
         1
     } else {
         //~^ ERROR right curly brace `}` before `else` in a `let...else` statement not allowed
@@ -145,8 +139,7 @@ fn n() {
 }
 
 fn o() -> Result<(), ()> {
-    let foo = do yeet {
-        //~^ WARN irrefutable `let...else` pattern
+    let 0 = do yeet {
         ()
     } else {
         //~^ ERROR right curly brace `}` before `else` in a `let...else` statement not allowed
@@ -155,8 +148,7 @@ fn o() -> Result<(), ()> {
 }
 
 fn p() {
-    let foo = become {
-        //~^ WARN irrefutable `let...else` pattern
+    let 0 = become {
         ()
     } else {
         //~^ ERROR right curly brace `}` before `else` in a `let...else` statement not allowed
@@ -185,22 +177,23 @@ fn r() {
 
 fn s() {
     macro_rules! a {
-        () => { {} }
-        //~^ WARN irrefutable `let...else` pattern
-        //~| WARN irrefutable `let...else` pattern
+        () => {
+            { 1 }
+        };
     }
 
     macro_rules! b {
         (1) => {
-            let x = a!() else { return; };
+            let 0 = a!() else { return; };
         };
         (2) => {
-            let x = a! {} else { return; };
+            let 0 = a! {} else { return; };
             //~^ ERROR right curly brace `}` before `else` in a `let...else` statement not allowed
         };
     }
 
-    b!(1); b!(2);
+    b!(1);
+    b!(2);
 }
 
 fn main() {}
diff --git a/tests/ui/parser/bad-let-else-statement.stderr b/tests/ui/parser/bad-let-else-statement.stderr
index 3f7e176b3e3..60b4600ff35 100644
--- a/tests/ui/parser/bad-let-else-statement.stderr
+++ b/tests/ui/parser/bad-let-else-statement.stderr
@@ -1,19 +1,18 @@
 error: right curly brace `}` before `else` in a `let...else` statement not allowed
-  --> $DIR/bad-let-else-statement.rs:9:5
+  --> $DIR/bad-let-else-statement.rs:8:5
    |
 LL |     } else {
    |     ^
    |
 help: wrap the expression in parentheses
    |
-LL ~     let foo = ({
-LL |
+LL ~     let 0 = ({
 LL |         1
 LL ~     }) else {
    |
 
 error: `for...else` loops are not supported
-  --> $DIR/bad-let-else-statement.rs:18:7
+  --> $DIR/bad-let-else-statement.rs:17:7
    |
 LL |       let foo = for i in 1..2 {
    |                 --- `else` is attached to this loop
@@ -28,22 +27,22 @@ LL | |     };
    = note: consider moving this `else` clause to a separate `if` statement and use a `bool` variable to control if it should run
 
 error: right curly brace `}` before `else` in a `let...else` statement not allowed
-  --> $DIR/bad-let-else-statement.rs:30:5
+  --> $DIR/bad-let-else-statement.rs:28:5
    |
 LL |     } else {
    |     ^
    |
 help: wrap the expression in parentheses
    |
-LL ~     let foo = (if true {
-LL |
- ...
+LL ~     let 0 = (if true {
+LL |         1
+LL |     } else {
 LL |         0
 LL ~     }) else {
    |
 
 error: `loop...else` loops are not supported
-  --> $DIR/bad-let-else-statement.rs:39:7
+  --> $DIR/bad-let-else-statement.rs:37:7
    |
 LL |       let foo = loop {
    |                 ---- `else` is attached to this loop
@@ -58,36 +57,34 @@ LL | |     };
    = note: consider moving this `else` clause to a separate `if` statement and use a `bool` variable to control if it should run
 
 error: right curly brace `}` before `else` in a `let...else` statement not allowed
-  --> $DIR/bad-let-else-statement.rs:50:5
+  --> $DIR/bad-let-else-statement.rs:47:5
    |
 LL |     } else {
    |     ^
    |
 help: wrap the expression in parentheses
    |
-LL ~     let foo = (match true {
-LL |
+LL ~     let 0 = (match true {
 LL |         true => 1,
 LL |         false => 0
 LL ~     }) else {
    |
 
 error: right curly brace `}` before `else` in a `let...else` statement not allowed
-  --> $DIR/bad-let-else-statement.rs:61:5
+  --> $DIR/bad-let-else-statement.rs:60:5
    |
 LL |     } else {
    |     ^
    |
 help: wrap the expression in parentheses
    |
-LL ~     let foo = (X {
-LL |
+LL ~     let X { a: 0 } = (X {
 LL |         a: 1
 LL ~     }) else {
    |
 
 error: `while...else` loops are not supported
-  --> $DIR/bad-let-else-statement.rs:70:7
+  --> $DIR/bad-let-else-statement.rs:69:7
    |
 LL |       let foo = while false {
    |                 ----- `else` is attached to this loop
@@ -102,35 +99,33 @@ LL | |     };
    = note: consider moving this `else` clause to a separate `if` statement and use a `bool` variable to control if it should run
 
 error: right curly brace `}` before `else` in a `let...else` statement not allowed
-  --> $DIR/bad-let-else-statement.rs:80:5
+  --> $DIR/bad-let-else-statement.rs:78:5
    |
 LL |     } else {
    |     ^
    |
 help: wrap the expression in parentheses
    |
-LL ~     let foo = (const {
-LL |
+LL ~     let 0 = (const {
 LL |         1
 LL ~     }) else {
    |
 
 error: right curly brace `}` before `else` in a `let...else` statement not allowed
-  --> $DIR/bad-let-else-statement.rs:90:5
+  --> $DIR/bad-let-else-statement.rs:87:5
    |
 LL |     } else {
    |     ^
    |
 help: wrap the expression in parentheses
    |
-LL ~     let foo = &({
-LL |
+LL ~     let 0 = &({
 LL |         1
 LL ~     }) else {
    |
 
 error: right curly brace `}` before `else` in a `let...else` statement not allowed
-  --> $DIR/bad-let-else-statement.rs:101:5
+  --> $DIR/bad-let-else-statement.rs:98:5
    |
 LL |     } else {
    |     ^
@@ -144,91 +139,85 @@ LL ~     }) else {
    |
 
 error: right curly brace `}` before `else` in a `let...else` statement not allowed
-  --> $DIR/bad-let-else-statement.rs:111:5
+  --> $DIR/bad-let-else-statement.rs:107:5
    |
 LL |     } else {
    |     ^
    |
 help: wrap the expression in parentheses
    |
-LL ~     let foo = 1 + ({
-LL |
+LL ~     let 0 = 1 + ({
 LL |         1
 LL ~     }) else {
    |
 
 error: right curly brace `}` before `else` in a `let...else` statement not allowed
-  --> $DIR/bad-let-else-statement.rs:121:5
+  --> $DIR/bad-let-else-statement.rs:117:5
    |
 LL |     } else {
    |     ^
    |
 help: wrap the expression in parentheses
    |
-LL ~     let foo = 1..({
-LL |
+LL ~     let RANGE = 1..({
 LL |         1
 LL ~     }) else {
    |
 
 error: right curly brace `}` before `else` in a `let...else` statement not allowed
-  --> $DIR/bad-let-else-statement.rs:131:5
+  --> $DIR/bad-let-else-statement.rs:126:5
    |
 LL |     } else {
    |     ^
    |
 help: wrap the expression in parentheses
    |
-LL ~     let foo = return ({
-LL |
+LL ~     let 0 = return ({
 LL |         ()
 LL ~     }) else {
    |
 
 error: right curly brace `}` before `else` in a `let...else` statement not allowed
-  --> $DIR/bad-let-else-statement.rs:141:5
+  --> $DIR/bad-let-else-statement.rs:135:5
    |
 LL |     } else {
    |     ^
    |
 help: wrap the expression in parentheses
    |
-LL ~     let foo = -({
-LL |
+LL ~     let 0 = -({
 LL |         1
 LL ~     }) else {
    |
 
 error: right curly brace `}` before `else` in a `let...else` statement not allowed
-  --> $DIR/bad-let-else-statement.rs:151:5
+  --> $DIR/bad-let-else-statement.rs:144:5
    |
 LL |     } else {
    |     ^
    |
 help: wrap the expression in parentheses
    |
-LL ~     let foo = do yeet ({
-LL |
+LL ~     let 0 = do yeet ({
 LL |         ()
 LL ~     }) else {
    |
 
 error: right curly brace `}` before `else` in a `let...else` statement not allowed
-  --> $DIR/bad-let-else-statement.rs:161:5
+  --> $DIR/bad-let-else-statement.rs:153:5
    |
 LL |     } else {
    |     ^
    |
 help: wrap the expression in parentheses
    |
-LL ~     let foo = become ({
-LL |
+LL ~     let 0 = become ({
 LL |         ()
 LL ~     }) else {
    |
 
 error: right curly brace `}` before `else` in a `let...else` statement not allowed
-  --> $DIR/bad-let-else-statement.rs:171:5
+  --> $DIR/bad-let-else-statement.rs:163:5
    |
 LL |     } else {
    |     ^
@@ -242,7 +231,7 @@ LL ~     }) else {
    |
 
 error: right curly brace `}` before `else` in a `let...else` statement not allowed
-  --> $DIR/bad-let-else-statement.rs:181:31
+  --> $DIR/bad-let-else-statement.rs:173:31
    |
 LL |     let bad = format_args! {""} else { return; };
    |                               ^
@@ -253,24 +242,24 @@ LL |     let bad = format_args! ("") else { return; };
    |                            ~  ~
 
 error: right curly brace `}` before `else` in a `let...else` statement not allowed
-  --> $DIR/bad-let-else-statement.rs:198:25
+  --> $DIR/bad-let-else-statement.rs:190:25
    |
-LL |             let x = a! {} else { return; };
+LL |             let 0 = a! {} else { return; };
    |                         ^
 ...
-LL |     b!(1); b!(2);
-   |            ----- in this macro invocation
+LL |     b!(2);
+   |     ----- in this macro invocation
    |
    = note: this error originates in the macro `b` (in Nightly builds, run with -Z macro-backtrace for more info)
 help: use parentheses instead of braces for this macro
    |
-LL |             let x = a! () else { return; };
+LL |             let 0 = a! () else { return; };
    |                        ~~
 
 warning: irrefutable `let...else` pattern
-  --> $DIR/bad-let-else-statement.rs:6:5
+  --> $DIR/bad-let-else-statement.rs:95:5
    |
-LL | /     let foo = {
+LL | /     let foo = bar = {
 LL | |
 LL | |         1
 LL | |     } else {
@@ -281,169 +270,7 @@ LL | |     } else {
    = note: `#[warn(irrefutable_let_patterns)]` on by default
 
 warning: irrefutable `let...else` pattern
-  --> $DIR/bad-let-else-statement.rs:25:5
-   |
-LL | /     let foo = if true {
-LL | |
-LL | |         1
-LL | |     } else {
-LL | |         0
-LL | |     } else {
-   | |_____^
-   |
-   = note: this pattern will always match, so the `else` clause is useless
-   = help: consider removing the `else` clause
-
-warning: irrefutable `let...else` pattern
-  --> $DIR/bad-let-else-statement.rs:46:5
-   |
-LL | /     let foo = match true {
-LL | |
-LL | |         true => 1,
-LL | |         false => 0
-LL | |     } else {
-   | |_____^
-   |
-   = note: this pattern will always match, so the `else` clause is useless
-   = help: consider removing the `else` clause
-
-warning: irrefutable `let...else` pattern
-  --> $DIR/bad-let-else-statement.rs:58:5
-   |
-LL | /     let foo = X {
-LL | |
-LL | |         a: 1
-LL | |     } else {
-   | |_____^
-   |
-   = note: this pattern will always match, so the `else` clause is useless
-   = help: consider removing the `else` clause
-
-warning: irrefutable `let...else` pattern
-  --> $DIR/bad-let-else-statement.rs:77:5
-   |
-LL | /     let foo = const {
-LL | |
-LL | |         1
-LL | |     } else {
-   | |_____^
-   |
-   = note: this pattern will always match, so the `else` clause is useless
-   = help: consider removing the `else` clause
-
-warning: irrefutable `let...else` pattern
-  --> $DIR/bad-let-else-statement.rs:87:5
-   |
-LL | /     let foo = &{
-LL | |
-LL | |         1
-LL | |     } else {
-   | |_____^
-   |
-   = note: this pattern will always match, so the `else` clause is useless
-   = help: consider removing the `else` clause
-
-warning: irrefutable `let...else` pattern
-  --> $DIR/bad-let-else-statement.rs:98:5
-   |
-LL | /     let foo = bar = {
-LL | |
-LL | |         1
-LL | |     } else {
-   | |_____^
-   |
-   = note: this pattern will always match, so the `else` clause is useless
-   = help: consider removing the `else` clause
-
-error[E0384]: cannot assign twice to immutable variable `bar`
-  --> $DIR/bad-let-else-statement.rs:98:15
-   |
-LL |       let bar = 0;
-   |           ---
-   |           |
-   |           first assignment to `bar`
-   |           help: consider making this binding mutable: `mut bar`
-LL |       let foo = bar = {
-   |  _______________^
-LL | |
-LL | |         1
-LL | |     } else {
-   | |_____^ cannot assign twice to immutable variable
-
-warning: irrefutable `let...else` pattern
-  --> $DIR/bad-let-else-statement.rs:108:5
-   |
-LL | /     let foo = 1 + {
-LL | |
-LL | |         1
-LL | |     } else {
-   | |_____^
-   |
-   = note: this pattern will always match, so the `else` clause is useless
-   = help: consider removing the `else` clause
-
-warning: irrefutable `let...else` pattern
-  --> $DIR/bad-let-else-statement.rs:118:5
-   |
-LL | /     let foo = 1..{
-LL | |
-LL | |         1
-LL | |     } else {
-   | |_____^
-   |
-   = note: this pattern will always match, so the `else` clause is useless
-   = help: consider removing the `else` clause
-
-warning: irrefutable `let...else` pattern
-  --> $DIR/bad-let-else-statement.rs:128:5
-   |
-LL | /     let foo = return {
-LL | |
-LL | |         ()
-LL | |     } else {
-   | |_____^
-   |
-   = note: this pattern will always match, so the `else` clause is useless
-   = help: consider removing the `else` clause
-
-warning: irrefutable `let...else` pattern
-  --> $DIR/bad-let-else-statement.rs:138:5
-   |
-LL | /     let foo = -{
-LL | |
-LL | |         1
-LL | |     } else {
-   | |_____^
-   |
-   = note: this pattern will always match, so the `else` clause is useless
-   = help: consider removing the `else` clause
-
-warning: irrefutable `let...else` pattern
-  --> $DIR/bad-let-else-statement.rs:148:5
-   |
-LL | /     let foo = do yeet {
-LL | |
-LL | |         ()
-LL | |     } else {
-   | |_____^
-   |
-   = note: this pattern will always match, so the `else` clause is useless
-   = help: consider removing the `else` clause
-
-warning: irrefutable `let...else` pattern
-  --> $DIR/bad-let-else-statement.rs:158:5
-   |
-LL | /     let foo = become {
-LL | |
-LL | |         ()
-LL | |     } else {
-   | |_____^
-   |
-   = note: this pattern will always match, so the `else` clause is useless
-   = help: consider removing the `else` clause
-
-warning: irrefutable `let...else` pattern
-  --> $DIR/bad-let-else-statement.rs:168:5
+  --> $DIR/bad-let-else-statement.rs:160:5
    |
 LL | /     let foo = |x: i32| {
 LL | |
@@ -455,7 +282,7 @@ LL | |     } else {
    = help: consider removing the `else` clause
 
 warning: irrefutable `let...else` pattern
-  --> $DIR/bad-let-else-statement.rs:178:5
+  --> $DIR/bad-let-else-statement.rs:170:5
    |
 LL |     let ok = format_args!("") else { return; };
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -464,7 +291,7 @@ LL |     let ok = format_args!("") else { return; };
    = help: consider removing the `else` clause
 
 warning: irrefutable `let...else` pattern
-  --> $DIR/bad-let-else-statement.rs:181:5
+  --> $DIR/bad-let-else-statement.rs:173:5
    |
 LL |     let bad = format_args! {""} else { return; };
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -472,46 +299,5 @@ LL |     let bad = format_args! {""} else { return; };
    = note: this pattern will always match, so the `else` clause is useless
    = help: consider removing the `else` clause
 
-warning: irrefutable `let...else` pattern
-  --> $DIR/bad-let-else-statement.rs:188:19
-   |
-LL |           () => { {} }
-   |  ___________________^
-LL | |
-LL | |
-LL | |     }
-...  |
-LL | |         (1) => {
-LL | |             let x = a!() else { return; };
-   | |____________^
-...
-LL |       b!(1); b!(2);
-   |       ----- in this macro invocation
-   |
-   = note: this pattern will always match, so the `else` clause is useless
-   = help: consider removing the `else` clause
-   = note: this warning originates in the macro `b` (in Nightly builds, run with -Z macro-backtrace for more info)
+error: aborting due to 19 previous errors; 4 warnings emitted
 
-warning: irrefutable `let...else` pattern
-  --> $DIR/bad-let-else-statement.rs:188:19
-   |
-LL |           () => { {} }
-   |  ___________________^
-LL | |
-LL | |
-LL | |     }
-...  |
-LL | |         (2) => {
-LL | |             let x = a! {} else { return; };
-   | |____________^
-...
-LL |       b!(1); b!(2);
-   |              ----- in this macro invocation
-   |
-   = note: this pattern will always match, so the `else` clause is useless
-   = help: consider removing the `else` clause
-   = note: this warning originates in the macro `b` (in Nightly builds, run with -Z macro-backtrace for more info)
-
-error: aborting due to 20 previous errors; 18 warnings emitted
-
-For more information about this error, try `rustc --explain E0384`.

From 75a34ca26294cb79bd8b126c0fc18f4c207faaf0 Mon Sep 17 00:00:00 2001
From: David Tolnay <dtolnay@gmail.com>
Date: Sun, 12 May 2024 13:02:54 -0700
Subject: [PATCH 04/12] Add test of trailing brace in a cast expression

---
 tests/ui/parser/bad-let-else-statement.rs     | 14 ++++++++++++++
 tests/ui/parser/bad-let-else-statement.stderr | 14 +++++++++++++-
 2 files changed, 27 insertions(+), 1 deletion(-)

diff --git a/tests/ui/parser/bad-let-else-statement.rs b/tests/ui/parser/bad-let-else-statement.rs
index 8ed55f94959..2dce9ed24d2 100644
--- a/tests/ui/parser/bad-let-else-statement.rs
+++ b/tests/ui/parser/bad-let-else-statement.rs
@@ -196,4 +196,18 @@ fn s() {
     b!(2);
 }
 
+fn t() {
+    macro_rules! primitive {
+        (8) => { u8 };
+    }
+
+    let foo = &std::ptr::null as &'static dyn std::ops::Fn() -> *const primitive! {
+        //~^ WARN irrefutable `let...else` pattern
+        8
+    } else {
+        // FIXME: right curly brace `}` before `else` in a `let...else` statement not allowed
+        return;
+    };
+}
+
 fn main() {}
diff --git a/tests/ui/parser/bad-let-else-statement.stderr b/tests/ui/parser/bad-let-else-statement.stderr
index 60b4600ff35..76097aaca83 100644
--- a/tests/ui/parser/bad-let-else-statement.stderr
+++ b/tests/ui/parser/bad-let-else-statement.stderr
@@ -299,5 +299,17 @@ LL |     let bad = format_args! {""} else { return; };
    = note: this pattern will always match, so the `else` clause is useless
    = help: consider removing the `else` clause
 
-error: aborting due to 19 previous errors; 4 warnings emitted
+warning: irrefutable `let...else` pattern
+  --> $DIR/bad-let-else-statement.rs:204:5
+   |
+LL | /     let foo = &std::ptr::null as &'static dyn std::ops::Fn() -> *const primitive! {
+LL | |
+LL | |         8
+LL | |     } else {
+   | |_____^
+   |
+   = note: this pattern will always match, so the `else` clause is useless
+   = help: consider removing the `else` clause
+
+error: aborting due to 19 previous errors; 5 warnings emitted
 

From a36b94d0887d42b692935c918c8cc869ca6c61b4 Mon Sep 17 00:00:00 2001
From: David Tolnay <dtolnay@gmail.com>
Date: Sun, 12 May 2024 12:28:10 -0700
Subject: [PATCH 05/12] Disallow cast with trailing braced macro in let-else

---
 compiler/rustc_ast/src/util/classify.rs       | 95 ++++++++++++++++++-
 compiler/rustc_parse/src/parser/stmt.rs       | 28 +++---
 tests/ui/parser/bad-let-else-statement.rs     |  2 +-
 tests/ui/parser/bad-let-else-statement.stderr | 16 +++-
 4 files changed, 124 insertions(+), 17 deletions(-)

diff --git a/compiler/rustc_ast/src/util/classify.rs b/compiler/rustc_ast/src/util/classify.rs
index f6e9e1a87c4..382c903625f 100644
--- a/compiler/rustc_ast/src/util/classify.rs
+++ b/compiler/rustc_ast/src/util/classify.rs
@@ -81,8 +81,17 @@ pub fn expr_requires_semi_to_be_stmt(e: &ast::Expr) -> bool {
     }
 }
 
+pub enum TrailingBrace<'a> {
+    /// Trailing brace in a macro call, like the one in `x as *const brace! {}`.
+    /// We will suggest changing the macro call to a different delimiter.
+    MacCall(&'a ast::MacCall),
+    /// Trailing brace in any other expression, such as `a + B {}`. We will
+    /// suggest wrapping the innermost expression in parentheses: `a + (B {})`.
+    Expr(&'a ast::Expr),
+}
+
 /// If an expression ends with `}`, returns the innermost expression ending in the `}`
-pub fn expr_trailing_brace(mut expr: &ast::Expr) -> Option<&ast::Expr> {
+pub fn expr_trailing_brace(mut expr: &ast::Expr) -> Option<TrailingBrace<'_>> {
     loop {
         match &expr.kind {
             AddrOf(_, _, e)
@@ -111,10 +120,14 @@ pub fn expr_trailing_brace(mut expr: &ast::Expr) -> Option<&ast::Expr> {
             | Struct(..)
             | TryBlock(..)
             | While(..)
-            | ConstBlock(_) => break Some(expr),
+            | ConstBlock(_) => break Some(TrailingBrace::Expr(expr)),
+
+            Cast(_, ty) => {
+                break type_trailing_braced_mac_call(ty).map(TrailingBrace::MacCall);
+            }
 
             MacCall(mac) => {
-                break (mac.args.delim == Delimiter::Brace).then_some(expr);
+                break (mac.args.delim == Delimiter::Brace).then_some(TrailingBrace::MacCall(mac));
             }
 
             InlineAsm(_) | OffsetOf(_, _) | IncludedBytes(_) | FormatArgs(_) => {
@@ -131,7 +144,6 @@ pub fn expr_trailing_brace(mut expr: &ast::Expr) -> Option<&ast::Expr> {
             | MethodCall(_)
             | Tup(_)
             | Lit(_)
-            | Cast(_, _)
             | Type(_, _)
             | Await(_, _)
             | Field(_, _)
@@ -148,3 +160,78 @@ pub fn expr_trailing_brace(mut expr: &ast::Expr) -> Option<&ast::Expr> {
         }
     }
 }
+
+/// If the type's last token is `}`, it must be due to a braced macro call, such
+/// as in `*const brace! { ... }`. Returns that trailing macro call.
+fn type_trailing_braced_mac_call(mut ty: &ast::Ty) -> Option<&ast::MacCall> {
+    loop {
+        match &ty.kind {
+            ast::TyKind::MacCall(mac) => {
+                break (mac.args.delim == Delimiter::Brace).then_some(mac);
+            }
+
+            ast::TyKind::Ptr(mut_ty) | ast::TyKind::Ref(_, mut_ty) => {
+                ty = &mut_ty.ty;
+            }
+
+            ast::TyKind::BareFn(fn_ty) => match &fn_ty.decl.output {
+                ast::FnRetTy::Default(_) => break None,
+                ast::FnRetTy::Ty(ret) => ty = ret,
+            },
+
+            ast::TyKind::Path(_, path) => match path_return_type(path) {
+                Some(trailing_ty) => ty = trailing_ty,
+                None => break None,
+            },
+
+            ast::TyKind::TraitObject(bounds, _) | ast::TyKind::ImplTrait(_, bounds, _) => {
+                match bounds.last() {
+                    Some(ast::GenericBound::Trait(bound, _)) => {
+                        match path_return_type(&bound.trait_ref.path) {
+                            Some(trailing_ty) => ty = trailing_ty,
+                            None => break None,
+                        }
+                    }
+                    Some(ast::GenericBound::Outlives(_)) | None => break None,
+                }
+            }
+
+            ast::TyKind::Slice(..)
+            | ast::TyKind::Array(..)
+            | ast::TyKind::Never
+            | ast::TyKind::Tup(..)
+            | ast::TyKind::Paren(..)
+            | ast::TyKind::Typeof(..)
+            | ast::TyKind::Infer
+            | ast::TyKind::ImplicitSelf
+            | ast::TyKind::CVarArgs
+            | ast::TyKind::Pat(..)
+            | ast::TyKind::Dummy
+            | ast::TyKind::Err(..) => break None,
+
+            // These end in brace, but cannot occur in a let-else statement.
+            // They are only parsed as fields of a data structure. For the
+            // purpose of denying trailing braces in the expression of a
+            // let-else, we can disregard these.
+            ast::TyKind::AnonStruct(..) | ast::TyKind::AnonUnion(..) => break None,
+        }
+    }
+}
+
+/// Returns the trailing return type in the given path, if it has one.
+///
+/// ```ignore (illustrative)
+/// ::std::ops::FnOnce(&str) -> fn() -> *const c_void
+///                             ^^^^^^^^^^^^^^^^^^^^^
+/// ```
+fn path_return_type(path: &ast::Path) -> Option<&ast::Ty> {
+    let last_segment = path.segments.last()?;
+    let args = last_segment.args.as_ref()?;
+    match &**args {
+        ast::GenericArgs::Parenthesized(args) => match &args.output {
+            ast::FnRetTy::Default(_) => None,
+            ast::FnRetTy::Ty(ret) => Some(ret),
+        },
+        ast::GenericArgs::AngleBracketed(_) => None,
+    }
+}
diff --git a/compiler/rustc_parse/src/parser/stmt.rs b/compiler/rustc_parse/src/parser/stmt.rs
index d70afebf1b2..941b145e2db 100644
--- a/compiler/rustc_parse/src/parser/stmt.rs
+++ b/compiler/rustc_parse/src/parser/stmt.rs
@@ -15,7 +15,7 @@ use ast::Label;
 use rustc_ast as ast;
 use rustc_ast::ptr::P;
 use rustc_ast::token::{self, Delimiter, TokenKind};
-use rustc_ast::util::classify;
+use rustc_ast::util::classify::{self, TrailingBrace};
 use rustc_ast::{AttrStyle, AttrVec, LocalKind, MacCall, MacCallStmt, MacStmtStyle};
 use rustc_ast::{Block, BlockCheckMode, Expr, ExprKind, HasAttrs, Local, Recovered, Stmt};
 use rustc_ast::{StmtKind, DUMMY_NODE_ID};
@@ -407,18 +407,24 @@ impl<'a> Parser<'a> {
 
     fn check_let_else_init_trailing_brace(&self, init: &ast::Expr) {
         if let Some(trailing) = classify::expr_trailing_brace(init) {
-            let sugg = match &trailing.kind {
-                ExprKind::MacCall(mac) => errors::WrapInParentheses::MacroArgs {
-                    left: mac.args.dspan.open,
-                    right: mac.args.dspan.close,
-                },
-                _ => errors::WrapInParentheses::Expression {
-                    left: trailing.span.shrink_to_lo(),
-                    right: trailing.span.shrink_to_hi(),
-                },
+            let (span, sugg) = match trailing {
+                TrailingBrace::MacCall(mac) => (
+                    mac.span(),
+                    errors::WrapInParentheses::MacroArgs {
+                        left: mac.args.dspan.open,
+                        right: mac.args.dspan.close,
+                    },
+                ),
+                TrailingBrace::Expr(expr) => (
+                    expr.span,
+                    errors::WrapInParentheses::Expression {
+                        left: expr.span.shrink_to_lo(),
+                        right: expr.span.shrink_to_hi(),
+                    },
+                ),
             };
             self.dcx().emit_err(errors::InvalidCurlyInLetElse {
-                span: trailing.span.with_lo(trailing.span.hi() - BytePos(1)),
+                span: span.with_lo(span.hi() - BytePos(1)),
                 sugg,
             });
         }
diff --git a/tests/ui/parser/bad-let-else-statement.rs b/tests/ui/parser/bad-let-else-statement.rs
index 2dce9ed24d2..ff6619cbc98 100644
--- a/tests/ui/parser/bad-let-else-statement.rs
+++ b/tests/ui/parser/bad-let-else-statement.rs
@@ -205,7 +205,7 @@ fn t() {
         //~^ WARN irrefutable `let...else` pattern
         8
     } else {
-        // FIXME: right curly brace `}` before `else` in a `let...else` statement not allowed
+        //~^ ERROR right curly brace `}` before `else` in a `let...else` statement not allowed
         return;
     };
 }
diff --git a/tests/ui/parser/bad-let-else-statement.stderr b/tests/ui/parser/bad-let-else-statement.stderr
index 76097aaca83..0bf6a346dfb 100644
--- a/tests/ui/parser/bad-let-else-statement.stderr
+++ b/tests/ui/parser/bad-let-else-statement.stderr
@@ -241,6 +241,20 @@ help: use parentheses instead of braces for this macro
 LL |     let bad = format_args! ("") else { return; };
    |                            ~  ~
 
+error: right curly brace `}` before `else` in a `let...else` statement not allowed
+  --> $DIR/bad-let-else-statement.rs:207:5
+   |
+LL |     } else {
+   |     ^
+   |
+help: use parentheses instead of braces for this macro
+   |
+LL ~     let foo = &std::ptr::null as &'static dyn std::ops::Fn() -> *const primitive! (
+LL |
+LL |         8
+LL ~     ) else {
+   |
+
 error: right curly brace `}` before `else` in a `let...else` statement not allowed
   --> $DIR/bad-let-else-statement.rs:190:25
    |
@@ -311,5 +325,5 @@ LL | |     } else {
    = note: this pattern will always match, so the `else` clause is useless
    = help: consider removing the `else` clause
 
-error: aborting due to 19 previous errors; 5 warnings emitted
+error: aborting due to 20 previous errors; 5 warnings emitted
 

From 2e97dae8d468623474d05dd84c270583ec3ed374 Mon Sep 17 00:00:00 2001
From: Michael Goulet <michael@errs.io>
Date: Sat, 18 May 2024 12:40:36 -0400
Subject: [PATCH 06/12] An async closure may implement FnMut/Fn if it has no
 self-borrows

---
 compiler/rustc_middle/src/ty/sty.rs           | 39 +++++++++++++++++++
 .../src/solve/assembly/structural_traits.rs   | 13 +++----
 .../src/traits/select/candidate_assembly.rs   | 15 ++-----
 .../async-closures/implements-fnmut.rs        | 23 +++++++++++
 4 files changed, 70 insertions(+), 20 deletions(-)
 create mode 100644 tests/ui/async-await/async-closures/implements-fnmut.rs

diff --git a/compiler/rustc_middle/src/ty/sty.rs b/compiler/rustc_middle/src/ty/sty.rs
index 9dbcd938e6e..6526bed5bdd 100644
--- a/compiler/rustc_middle/src/ty/sty.rs
+++ b/compiler/rustc_middle/src/ty/sty.rs
@@ -401,6 +401,45 @@ impl<'tcx> CoroutineClosureArgs<'tcx> {
     pub fn coroutine_witness_ty(self) -> Ty<'tcx> {
         self.split().coroutine_witness_ty
     }
+
+    pub fn has_self_borrows(&self) -> bool {
+        match self.coroutine_captures_by_ref_ty().kind() {
+            ty::FnPtr(sig) => sig
+                .skip_binder()
+                .visit_with(&mut HasRegionsBoundAt { binder: ty::INNERMOST })
+                .is_break(),
+            ty::Error(_) => true,
+            _ => bug!(),
+        }
+    }
+}
+/// Unlike `has_escaping_bound_vars` or `outermost_exclusive_binder`, this will
+/// detect only regions bound *at* the debruijn index.
+struct HasRegionsBoundAt {
+    binder: ty::DebruijnIndex,
+}
+// FIXME: Could be optimized to not walk into components with no escaping bound vars.
+impl<'tcx> TypeVisitor<TyCtxt<'tcx>> for HasRegionsBoundAt {
+    type Result = ControlFlow<()>;
+    fn visit_binder<T: TypeVisitable<TyCtxt<'tcx>>>(
+        &mut self,
+        t: &ty::Binder<'tcx, T>,
+    ) -> Self::Result {
+        self.binder.shift_in(1);
+        t.super_visit_with(self)?;
+        self.binder.shift_out(1);
+        ControlFlow::Continue(())
+    }
+
+    fn visit_region(&mut self, r: ty::Region<'tcx>) -> Self::Result {
+        if let ty::ReBound(binder, _) = *r
+            && self.binder == binder
+        {
+            ControlFlow::Break(())
+        } else {
+            ControlFlow::Continue(())
+        }
+    }
 }
 
 #[derive(Copy, Clone, PartialEq, Eq, Debug, TypeFoldable, TypeVisitable)]
diff --git a/compiler/rustc_trait_selection/src/solve/assembly/structural_traits.rs b/compiler/rustc_trait_selection/src/solve/assembly/structural_traits.rs
index 90cc33e0275..f5e397f12d2 100644
--- a/compiler/rustc_trait_selection/src/solve/assembly/structural_traits.rs
+++ b/compiler/rustc_trait_selection/src/solve/assembly/structural_traits.rs
@@ -300,14 +300,11 @@ pub(in crate::solve) fn extract_tupled_inputs_and_output_from_callable<'tcx>(
                     return Err(NoSolution);
                 }
 
-                // If `Fn`/`FnMut`, we only implement this goal if we
-                // have no captures.
-                let no_borrows = match args.tupled_upvars_ty().kind() {
-                    ty::Tuple(tys) => tys.is_empty(),
-                    ty::Error(_) => false,
-                    _ => bug!("tuple_fields called on non-tuple"),
-                };
-                if closure_kind != ty::ClosureKind::FnOnce && !no_borrows {
+                // A coroutine-closure implements `FnOnce` *always*, since it may
+                // always be called once. It additionally implements `Fn`/`FnMut`
+                // only if it has no upvars referencing the closure-env lifetime,
+                // and if the closure kind permits it.
+                if closure_kind != ty::ClosureKind::FnOnce && args.has_self_borrows() {
                     return Err(NoSolution);
                 }
 
diff --git a/compiler/rustc_trait_selection/src/traits/select/candidate_assembly.rs b/compiler/rustc_trait_selection/src/traits/select/candidate_assembly.rs
index b9e853a0678..fd7c47ad6fb 100644
--- a/compiler/rustc_trait_selection/src/traits/select/candidate_assembly.rs
+++ b/compiler/rustc_trait_selection/src/traits/select/candidate_assembly.rs
@@ -418,20 +418,11 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> {
                     // Ambiguity if upvars haven't been constrained yet
                     && !args.tupled_upvars_ty().is_ty_var()
                 {
-                    let no_borrows = match args.tupled_upvars_ty().kind() {
-                        ty::Tuple(tys) => tys.is_empty(),
-                        ty::Error(_) => false,
-                        _ => bug!("tuple_fields called on non-tuple"),
-                    };
                     // A coroutine-closure implements `FnOnce` *always*, since it may
                     // always be called once. It additionally implements `Fn`/`FnMut`
-                    // only if it has no upvars (therefore no borrows from the closure
-                    // that would need to be represented with a lifetime) and if the
-                    // closure kind permits it.
-                    // FIXME(async_closures): Actually, it could also implement `Fn`/`FnMut`
-                    // if it takes all of its upvars by copy, and none by ref. This would
-                    // require us to record a bit more information during upvar analysis.
-                    if no_borrows && closure_kind.extends(kind) {
+                    // only if it has no upvars referencing the closure-env lifetime,
+                    // and if the closure kind permits it.
+                    if closure_kind.extends(kind) && !args.has_self_borrows() {
                         candidates.vec.push(ClosureCandidate { is_const });
                     } else if kind == ty::ClosureKind::FnOnce {
                         candidates.vec.push(ClosureCandidate { is_const });
diff --git a/tests/ui/async-await/async-closures/implements-fnmut.rs b/tests/ui/async-await/async-closures/implements-fnmut.rs
new file mode 100644
index 00000000000..1ed326cd061
--- /dev/null
+++ b/tests/ui/async-await/async-closures/implements-fnmut.rs
@@ -0,0 +1,23 @@
+//@ check-pass
+//@ edition: 2021
+
+// Demonstrates that an async closure may implement `FnMut` (not just `async FnMut`!)
+// if it has no self-borrows. In this case, `&Ty` is not borrowed from the closure env,
+// since it's fine to reborrow it with its original lifetime. See the doc comment on
+// `should_reborrow_from_env_of_parent_coroutine_closure` for more detail for when we
+// must borrow from the closure env.
+
+#![feature(async_closure)]
+
+fn main() {}
+
+fn needs_fn_mut<T>(x: impl FnMut() -> T) {}
+
+fn hello(x: &Ty) {
+    needs_fn_mut(async || { x.hello(); });
+}
+
+struct Ty;
+impl Ty {
+    fn hello(&self) {}
+}

From 63fe640f5d2eb402247906e22b8bf14bf8e90b61 Mon Sep 17 00:00:00 2001
From: Lzu Tao <taolzu@gmail.com>
Date: Sun, 19 May 2024 23:08:59 +0000
Subject: [PATCH 07/12] Update check-cfg lists for core

---
 library/core/Cargo.toml | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/library/core/Cargo.toml b/library/core/Cargo.toml
index 11d33971f25..0c86f430f32 100644
--- a/library/core/Cargo.toml
+++ b/library/core/Cargo.toml
@@ -36,3 +36,12 @@ optimize_for_size = []
 # Make `RefCell` store additional debugging information, which is printed out when
 # a borrow error occurs
 debug_refcell = []
+
+[lints.rust.unexpected_cfgs]
+level = "warn"
+check-cfg = [
+    'cfg(no_fp_fmt_parse)',
+    'cfg(bootstrap)',
+    'cfg(stdarch_intel_sde)',
+    'cfg(feature, values("all_lane_counts"))',
+]

From 0734ae22f5e821023155e2df24628a1c6df54f2e Mon Sep 17 00:00:00 2001
From: Lzu Tao <taolzu@gmail.com>
Date: Sun, 19 May 2024 23:14:48 +0000
Subject: [PATCH 08/12] Update check-cfg lists for alloc

---
 library/alloc/Cargo.toml | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/library/alloc/Cargo.toml b/library/alloc/Cargo.toml
index 2e7fcb9dbd3..cddf87a92c5 100644
--- a/library/alloc/Cargo.toml
+++ b/library/alloc/Cargo.toml
@@ -40,3 +40,12 @@ compiler-builtins-weak-intrinsics = ["compiler_builtins/weak-intrinsics"]
 panic_immediate_abort = ["core/panic_immediate_abort"]
 # Choose algorithms that are optimized for binary size instead of runtime performance
 optimize_for_size = ["core/optimize_for_size"]
+
+[lints.rust.unexpected_cfgs]
+level = "warn"
+check-cfg = [
+    'cfg(no_global_oom_handling)',
+    'cfg(bootstrap)',
+    'cfg(no_rc)',
+    'cfg(no_sync)',
+]

From 73602bf4088b31daa0da10d3ea926e1145faa5d5 Mon Sep 17 00:00:00 2001
From: Lzu Tao <taolzu@gmail.com>
Date: Sun, 19 May 2024 23:27:41 +0000
Subject: [PATCH 09/12] Update check-cfg lists for std

---
 library/std/Cargo.toml | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/library/std/Cargo.toml b/library/std/Cargo.toml
index 5b36867fe24..4b8ee4c1309 100644
--- a/library/std/Cargo.toml
+++ b/library/std/Cargo.toml
@@ -97,3 +97,13 @@ heap_size = 0x8000000
 name = "stdbenches"
 path = "benches/lib.rs"
 test = true
+
+[lints.rust.unexpected_cfgs]
+level = "warn"
+check-cfg = [
+    'cfg(bootstrap)',
+    'cfg(backtrace_in_libstd)',
+    'cfg(netbsd10)',
+    'cfg(target_arch, values("xtensa"))',
+    'cfg(feature, values("std", "as_crate"))',
+]

From c7d2f4592fb19a8c3d7ae5e6a1594edb4608c75f Mon Sep 17 00:00:00 2001
From: Lzu Tao <taolzu@gmail.com>
Date: Mon, 20 May 2024 15:06:14 +0000
Subject: [PATCH 10/12] addresss reviews

---
 library/alloc/Cargo.toml | 5 ++++-
 library/core/Cargo.toml  | 6 +++++-
 src/bootstrap/src/lib.rs | 1 +
 3 files changed, 10 insertions(+), 2 deletions(-)

diff --git a/library/alloc/Cargo.toml b/library/alloc/Cargo.toml
index cddf87a92c5..3960f716812 100644
--- a/library/alloc/Cargo.toml
+++ b/library/alloc/Cargo.toml
@@ -43,9 +43,12 @@ optimize_for_size = ["core/optimize_for_size"]
 
 [lints.rust.unexpected_cfgs]
 level = "warn"
+# x.py uses beta cargo, so `check-cfg` entries do not yet take effect
+# for rust-lang/rust. But for users of `-Zbuild-std` it does.
+# The unused warning is waiting for rust-lang/cargo#13925 to reach beta.
 check-cfg = [
-    'cfg(no_global_oom_handling)',
     'cfg(bootstrap)',
+    'cfg(no_global_oom_handling)',
     'cfg(no_rc)',
     'cfg(no_sync)',
 ]
diff --git a/library/core/Cargo.toml b/library/core/Cargo.toml
index 0c86f430f32..756f68e6048 100644
--- a/library/core/Cargo.toml
+++ b/library/core/Cargo.toml
@@ -39,9 +39,13 @@ debug_refcell = []
 
 [lints.rust.unexpected_cfgs]
 level = "warn"
+# x.py uses beta cargo, so `check-cfg` entries do not yet take effect
+# for rust-lang/rust. But for users of `-Zbuild-std` it does.
+# The unused warning is waiting for rust-lang/cargo#13925 to reach beta.
 check-cfg = [
     'cfg(no_fp_fmt_parse)',
     'cfg(bootstrap)',
     'cfg(stdarch_intel_sde)',
-    'cfg(feature, values("all_lane_counts"))',
+    # This matches `EXTRA_CHECK_CFGS` in `src/bootstrap/src/lib.rs`.
+    'cfg(feature, values(any()))',
 ]
diff --git a/src/bootstrap/src/lib.rs b/src/bootstrap/src/lib.rs
index 698a576effa..0578638de5c 100644
--- a/src/bootstrap/src/lib.rs
+++ b/src/bootstrap/src/lib.rs
@@ -84,6 +84,7 @@ const EXTRA_CHECK_CFGS: &[(Option<Mode>, &str, Option<&[&'static str]>)] = &[
     (Some(Mode::ToolRustc), "rust_analyzer", None),
     (Some(Mode::ToolStd), "rust_analyzer", None),
     (Some(Mode::Codegen), "parallel_compiler", None),
+    // NOTE: consider updating `check-cfg` entries in `std/Cargo.toml` too.
     (Some(Mode::Std), "stdarch_intel_sde", None),
     (Some(Mode::Std), "no_fp_fmt_parse", None),
     (Some(Mode::Std), "no_global_oom_handling", None),

From df3a32066fe4d9a1c8f922d7abfa5f76578a876a Mon Sep 17 00:00:00 2001
From: Lzu Tao <taolzu@gmail.com>
Date: Mon, 20 May 2024 15:08:28 +0000
Subject: [PATCH 11/12] tidy alphabetica

---
 library/core/Cargo.toml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/library/core/Cargo.toml b/library/core/Cargo.toml
index 756f68e6048..daf2612833d 100644
--- a/library/core/Cargo.toml
+++ b/library/core/Cargo.toml
@@ -43,8 +43,8 @@ level = "warn"
 # for rust-lang/rust. But for users of `-Zbuild-std` it does.
 # The unused warning is waiting for rust-lang/cargo#13925 to reach beta.
 check-cfg = [
-    'cfg(no_fp_fmt_parse)',
     'cfg(bootstrap)',
+    'cfg(no_fp_fmt_parse)',
     'cfg(stdarch_intel_sde)',
     # This matches `EXTRA_CHECK_CFGS` in `src/bootstrap/src/lib.rs`.
     'cfg(feature, values(any()))',

From 30e0ab84f985d4da0f9eb802e047f5462f4a8a65 Mon Sep 17 00:00:00 2001
From: Lzu Tao <taolzu@gmail.com>
Date: Tue, 21 May 2024 09:30:43 +0000
Subject: [PATCH 12/12] maybe replace check-cfg values in bootstrap with ones
 in Cargo.toml

---
 src/bootstrap/src/lib.rs | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/src/bootstrap/src/lib.rs b/src/bootstrap/src/lib.rs
index 0578638de5c..38de5e38000 100644
--- a/src/bootstrap/src/lib.rs
+++ b/src/bootstrap/src/lib.rs
@@ -85,6 +85,8 @@ const EXTRA_CHECK_CFGS: &[(Option<Mode>, &str, Option<&[&'static str]>)] = &[
     (Some(Mode::ToolStd), "rust_analyzer", None),
     (Some(Mode::Codegen), "parallel_compiler", None),
     // NOTE: consider updating `check-cfg` entries in `std/Cargo.toml` too.
+    // cfg(bootstrap) remove these once the bootstrap compiler supports
+    // `lints.rust.unexpected_cfgs.check-cfg`
     (Some(Mode::Std), "stdarch_intel_sde", None),
     (Some(Mode::Std), "no_fp_fmt_parse", None),
     (Some(Mode::Std), "no_global_oom_handling", None),