about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/ci.yml14
-rw-r--r--Cargo.lock1
-rw-r--r--compiler/rustc_ast/src/ast.rs44
-rw-r--r--compiler/rustc_ast/src/util/parser.rs216
-rw-r--r--compiler/rustc_ast_pretty/src/pprust/state/expr.rs7
-rw-r--r--compiler/rustc_attr_data_structures/src/attributes.rs13
-rw-r--r--compiler/rustc_const_eval/src/check_consts/mod.rs5
-rw-r--r--compiler/rustc_data_structures/src/flat_map_in_place.rs42
-rw-r--r--compiler/rustc_hir/src/hir.rs4
-rw-r--r--compiler/rustc_hir_pretty/src/lib.rs7
-rw-r--r--compiler/rustc_lint/Cargo.toml1
-rw-r--r--compiler/rustc_lint/messages.ftl5
-rw-r--r--compiler/rustc_lint/src/if_let_rescope.rs77
-rw-r--r--compiler/rustc_middle/src/ty/instance.rs5
-rw-r--r--compiler/rustc_middle/src/ty/mod.rs1
-rw-r--r--compiler/rustc_middle/src/ty/print/pretty.rs5
-rw-r--r--compiler/rustc_middle/src/ty/significant_drop_order.rs172
-rw-r--r--compiler/rustc_mir_transform/src/lint_tail_expr_drop_order.rs174
-rw-r--r--compiler/rustc_parse/src/parser/diagnostics.rs36
-rw-r--r--compiler/rustc_parse/src/parser/expr.rs94
-rw-r--r--compiler/rustc_passes/messages.ftl6
-rw-r--r--compiler/rustc_passes/src/errors.rs39
-rw-r--r--compiler/rustc_passes/src/stability.rs60
-rw-r--r--compiler/rustc_ty_utils/src/abi.rs74
-rw-r--r--library/core/src/ops/arith.rs1
-rw-r--r--library/core/src/ops/deref.rs3
-rw-r--r--library/core/src/ops/range.rs140
-rw-r--r--library/std/src/thread/mod.rs11
-rw-r--r--src/bootstrap/src/core/build_steps/test.rs8
-rw-r--r--src/ci/github-actions/jobs.yml6
-rw-r--r--src/tools/clippy/clippy_lints/src/operators/float_equality_without_abs.rs2
-rw-r--r--src/tools/clippy/clippy_utils/src/lib.rs1
-rw-r--r--src/tools/clippy/clippy_utils/src/sugg.rs163
-rw-r--r--src/tools/compiletest/src/common.rs20
-rw-r--r--src/tools/compiletest/src/header.rs10
-rw-r--r--src/tools/compiletest/src/header/tests.rs3
-rw-r--r--src/tools/compiletest/src/lib.rs21
-rw-r--r--src/tools/compiletest/src/runtest.rs16
-rw-r--r--src/tools/compiletest/src/runtest/run_make.rs17
-rw-r--r--src/tools/compiletest/src/runtest/ui.rs2
-rw-r--r--tests/codegen/virtual-call-attrs-issue-137646.rs37
-rw-r--r--tests/ui/drop/drop-order-comparisons.e2021.stderr90
-rw-r--r--tests/ui/drop/lint-if-let-rescope-gated.edition2021.stderr9
-rw-r--r--tests/ui/drop/lint-if-let-rescope-with-macro.stderr9
-rw-r--r--tests/ui/drop/lint-if-let-rescope.stderr72
-rw-r--r--tests/ui/impl-trait/method-suggestion-no-duplication.stderr3
-rw-r--r--tests/ui/traits/const-traits/staged-api.rs32
-rw-r--r--tests/ui/traits/const-traits/staged-api.stderr69
-rw-r--r--tests/ui/virtual-call-attrs-issue-137646.rs45
49 files changed, 1207 insertions, 685 deletions
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index de144ee6104..949932b8e2a 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -179,6 +179,20 @@ jobs:
       - name: show the current environment
         run: src/ci/scripts/dump-environment.sh
 
+      # Temporary fix to unblock CI
+      # Remove the latest Windows SDK for 32-bit Windows MSVC builds.
+      # See issue https://github.com/rust-lang/rust/issues/137733 for more details.
+      - name: Remove Windows SDK 10.0.26100.0
+        shell: powershell
+        if: ${{ matrix.name == 'i686-msvc-1' || matrix.name == 'i686-msvc-2' || matrix.name == 'dist-i686-msvc' }}
+        run: |
+          $kits = (Get-ItemProperty -path 'HKLM:\SOFTWARE\Microsoft\Windows Kits\Installed Roots').KitsRoot10
+          $sdk_version = "10.0.26100.0"
+
+          foreach ($kind in 'Bin', 'Lib', 'Include') {
+            Remove-Item -Force -Recurse $kits\$kind\$sdk_version -ErrorAction Continue
+          }
+
       - name: run the build
         # Redirect stderr to stdout to avoid reordering the two streams in the GHA logs.
         run: src/ci/scripts/run-build-from-ci.sh 2>&1
diff --git a/Cargo.lock b/Cargo.lock
index fb4cf235c6f..2227f5f3e99 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -3916,6 +3916,7 @@ dependencies = [
  "rustc_target",
  "rustc_trait_selection",
  "rustc_type_ir",
+ "smallvec",
  "tracing",
  "unicode-security",
 ]
diff --git a/compiler/rustc_ast/src/ast.rs b/compiler/rustc_ast/src/ast.rs
index 29c1d34a125..5c44fda2262 100644
--- a/compiler/rustc_ast/src/ast.rs
+++ b/compiler/rustc_ast/src/ast.rs
@@ -39,7 +39,7 @@ pub use crate::format::*;
 use crate::ptr::P;
 use crate::token::{self, CommentKind, Delimiter};
 use crate::tokenstream::{DelimSpan, LazyAttrTokenStream, TokenStream};
-use crate::util::parser::{AssocOp, ExprPrecedence};
+use crate::util::parser::{ExprPrecedence, Fixity};
 
 /// A "Label" is an identifier of some point in sources,
 /// e.g. in the following code:
@@ -937,8 +937,37 @@ impl BinOpKind {
         matches!(self, BinOpKind::And | BinOpKind::Or)
     }
 
+    pub fn precedence(&self) -> ExprPrecedence {
+        use BinOpKind::*;
+        match *self {
+            Mul | Div | Rem => ExprPrecedence::Product,
+            Add | Sub => ExprPrecedence::Sum,
+            Shl | Shr => ExprPrecedence::Shift,
+            BitAnd => ExprPrecedence::BitAnd,
+            BitXor => ExprPrecedence::BitXor,
+            BitOr => ExprPrecedence::BitOr,
+            Lt | Gt | Le | Ge | Eq | Ne => ExprPrecedence::Compare,
+            And => ExprPrecedence::LAnd,
+            Or => ExprPrecedence::LOr,
+        }
+    }
+
+    pub fn fixity(&self) -> Fixity {
+        use BinOpKind::*;
+        match self {
+            Eq | Ne | Lt | Le | Gt | Ge => Fixity::None,
+            Add | Sub | Mul | Div | Rem | And | Or | BitXor | BitAnd | BitOr | Shl | Shr => {
+                Fixity::Left
+            }
+        }
+    }
+
     pub fn is_comparison(self) -> bool {
-        crate::util::parser::AssocOp::from_ast_binop(self).is_comparison()
+        use BinOpKind::*;
+        match self {
+            Eq | Ne | Lt | Le | Gt | Ge => true,
+            Add | Sub | Mul | Div | Rem | And | Or | BitXor | BitAnd | BitOr | Shl | Shr => false,
+        }
     }
 
     /// Returns `true` if the binary operator takes its arguments by value.
@@ -1332,7 +1361,7 @@ impl Expr {
             ExprKind::Range(..) => ExprPrecedence::Range,
 
             // Binop-like expr kinds, handled by `AssocOp`.
-            ExprKind::Binary(op, ..) => AssocOp::from_ast_binop(op.node).precedence(),
+            ExprKind::Binary(op, ..) => op.node.precedence(),
             ExprKind::Cast(..) => ExprPrecedence::Cast,
 
             ExprKind::Assign(..) |
@@ -1424,6 +1453,15 @@ pub enum RangeLimits {
     Closed,
 }
 
+impl RangeLimits {
+    pub fn as_str(&self) -> &'static str {
+        match self {
+            RangeLimits::HalfOpen => "..",
+            RangeLimits::Closed => "..=",
+        }
+    }
+}
+
 /// A method call (e.g. `x.foo::<Bar, Baz>(a, b, c)`).
 #[derive(Clone, Encodable, Decodable, Debug)]
 pub struct MethodCall {
diff --git a/compiler/rustc_ast/src/util/parser.rs b/compiler/rustc_ast/src/util/parser.rs
index 8f2b7a23c01..69454967eed 100644
--- a/compiler/rustc_ast/src/util/parser.rs
+++ b/compiler/rustc_ast/src/util/parser.rs
@@ -1,59 +1,21 @@
 use rustc_span::kw;
 
-use crate::ast::{self, BinOpKind};
+use crate::ast::{self, BinOpKind, RangeLimits};
 use crate::token::{self, BinOpToken, Token};
 
-/// Associative operator with precedence.
-///
-/// This is the enum which specifies operator precedence and fixity to the parser.
+/// Associative operator.
 #[derive(Copy, Clone, PartialEq, Debug)]
 pub enum AssocOp {
-    /// `+`
-    Add,
-    /// `-`
-    Subtract,
-    /// `*`
-    Multiply,
-    /// `/`
-    Divide,
-    /// `%`
-    Modulus,
-    /// `&&`
-    LAnd,
-    /// `||`
-    LOr,
-    /// `^`
-    BitXor,
-    /// `&`
-    BitAnd,
-    /// `|`
-    BitOr,
-    /// `<<`
-    ShiftLeft,
-    /// `>>`
-    ShiftRight,
-    /// `==`
-    Equal,
-    /// `<`
-    Less,
-    /// `<=`
-    LessEqual,
-    /// `!=`
-    NotEqual,
-    /// `>`
-    Greater,
-    /// `>=`
-    GreaterEqual,
+    /// A binary op.
+    Binary(BinOpKind),
+    /// `?=` where ? is one of the assignable BinOps
+    AssignOp(BinOpKind),
     /// `=`
     Assign,
-    /// `?=` where ? is one of the BinOpToken
-    AssignOp(BinOpToken),
     /// `as`
-    As,
-    /// `..` range
-    DotDot,
-    /// `..=` range
-    DotDotEq,
+    Cast,
+    /// `..` or `..=` range
+    Range(RangeLimits),
 }
 
 #[derive(PartialEq, Debug)]
@@ -67,81 +29,56 @@ pub enum Fixity {
 }
 
 impl AssocOp {
-    /// Creates a new AssocOP from a token
+    /// Creates a new AssocOp from a token.
     pub fn from_token(t: &Token) -> Option<AssocOp> {
         use AssocOp::*;
         match t.kind {
-            token::BinOpEq(k) => Some(AssignOp(k)),
             token::Eq => Some(Assign),
-            token::BinOp(BinOpToken::Star) => Some(Multiply),
-            token::BinOp(BinOpToken::Slash) => Some(Divide),
-            token::BinOp(BinOpToken::Percent) => Some(Modulus),
-            token::BinOp(BinOpToken::Plus) => Some(Add),
-            token::BinOp(BinOpToken::Minus) => Some(Subtract),
-            token::BinOp(BinOpToken::Shl) => Some(ShiftLeft),
-            token::BinOp(BinOpToken::Shr) => Some(ShiftRight),
-            token::BinOp(BinOpToken::And) => Some(BitAnd),
-            token::BinOp(BinOpToken::Caret) => Some(BitXor),
-            token::BinOp(BinOpToken::Or) => Some(BitOr),
-            token::Lt => Some(Less),
-            token::Le => Some(LessEqual),
-            token::Ge => Some(GreaterEqual),
-            token::Gt => Some(Greater),
-            token::EqEq => Some(Equal),
-            token::Ne => Some(NotEqual),
-            token::AndAnd => Some(LAnd),
-            token::OrOr => Some(LOr),
-            token::DotDot => Some(DotDot),
-            token::DotDotEq => Some(DotDotEq),
+            token::BinOp(BinOpToken::Plus) => Some(Binary(BinOpKind::Add)),
+            token::BinOp(BinOpToken::Minus) => Some(Binary(BinOpKind::Sub)),
+            token::BinOp(BinOpToken::Star) => Some(Binary(BinOpKind::Mul)),
+            token::BinOp(BinOpToken::Slash) => Some(Binary(BinOpKind::Div)),
+            token::BinOp(BinOpToken::Percent) => Some(Binary(BinOpKind::Rem)),
+            token::BinOp(BinOpToken::Caret) => Some(Binary(BinOpKind::BitXor)),
+            token::BinOp(BinOpToken::And) => Some(Binary(BinOpKind::BitAnd)),
+            token::BinOp(BinOpToken::Or) => Some(Binary(BinOpKind::BitOr)),
+            token::BinOp(BinOpToken::Shl) => Some(Binary(BinOpKind::Shl)),
+            token::BinOp(BinOpToken::Shr) => Some(Binary(BinOpKind::Shr)),
+            token::BinOpEq(BinOpToken::Plus) => Some(AssignOp(BinOpKind::Add)),
+            token::BinOpEq(BinOpToken::Minus) => Some(AssignOp(BinOpKind::Sub)),
+            token::BinOpEq(BinOpToken::Star) => Some(AssignOp(BinOpKind::Mul)),
+            token::BinOpEq(BinOpToken::Slash) => Some(AssignOp(BinOpKind::Div)),
+            token::BinOpEq(BinOpToken::Percent) => Some(AssignOp(BinOpKind::Rem)),
+            token::BinOpEq(BinOpToken::Caret) => Some(AssignOp(BinOpKind::BitXor)),
+            token::BinOpEq(BinOpToken::And) => Some(AssignOp(BinOpKind::BitAnd)),
+            token::BinOpEq(BinOpToken::Or) => Some(AssignOp(BinOpKind::BitOr)),
+            token::BinOpEq(BinOpToken::Shl) => Some(AssignOp(BinOpKind::Shl)),
+            token::BinOpEq(BinOpToken::Shr) => Some(AssignOp(BinOpKind::Shr)),
+            token::Lt => Some(Binary(BinOpKind::Lt)),
+            token::Le => Some(Binary(BinOpKind::Le)),
+            token::Ge => Some(Binary(BinOpKind::Ge)),
+            token::Gt => Some(Binary(BinOpKind::Gt)),
+            token::EqEq => Some(Binary(BinOpKind::Eq)),
+            token::Ne => Some(Binary(BinOpKind::Ne)),
+            token::AndAnd => Some(Binary(BinOpKind::And)),
+            token::OrOr => Some(Binary(BinOpKind::Or)),
+            token::DotDot => Some(Range(RangeLimits::HalfOpen)),
             // DotDotDot is no longer supported, but we need some way to display the error
-            token::DotDotDot => Some(DotDotEq),
+            token::DotDotEq | token::DotDotDot => Some(Range(RangeLimits::Closed)),
             // `<-` should probably be `< -`
-            token::LArrow => Some(Less),
-            _ if t.is_keyword(kw::As) => Some(As),
+            token::LArrow => Some(Binary(BinOpKind::Lt)),
+            _ if t.is_keyword(kw::As) => Some(Cast),
             _ => None,
         }
     }
 
-    /// Creates a new AssocOp from ast::BinOpKind.
-    pub fn from_ast_binop(op: BinOpKind) -> Self {
-        use AssocOp::*;
-        match op {
-            BinOpKind::Lt => Less,
-            BinOpKind::Gt => Greater,
-            BinOpKind::Le => LessEqual,
-            BinOpKind::Ge => GreaterEqual,
-            BinOpKind::Eq => Equal,
-            BinOpKind::Ne => NotEqual,
-            BinOpKind::Mul => Multiply,
-            BinOpKind::Div => Divide,
-            BinOpKind::Rem => Modulus,
-            BinOpKind::Add => Add,
-            BinOpKind::Sub => Subtract,
-            BinOpKind::Shl => ShiftLeft,
-            BinOpKind::Shr => ShiftRight,
-            BinOpKind::BitAnd => BitAnd,
-            BinOpKind::BitXor => BitXor,
-            BinOpKind::BitOr => BitOr,
-            BinOpKind::And => LAnd,
-            BinOpKind::Or => LOr,
-        }
-    }
-
     /// Gets the precedence of this operator
     pub fn precedence(&self) -> ExprPrecedence {
         use AssocOp::*;
         match *self {
-            As => ExprPrecedence::Cast,
-            Multiply | Divide | Modulus => ExprPrecedence::Product,
-            Add | Subtract => ExprPrecedence::Sum,
-            ShiftLeft | ShiftRight => ExprPrecedence::Shift,
-            BitAnd => ExprPrecedence::BitAnd,
-            BitXor => ExprPrecedence::BitXor,
-            BitOr => ExprPrecedence::BitOr,
-            Less | Greater | LessEqual | GreaterEqual | Equal | NotEqual => ExprPrecedence::Compare,
-            LAnd => ExprPrecedence::LAnd,
-            LOr => ExprPrecedence::LOr,
-            DotDot | DotDotEq => ExprPrecedence::Range,
+            Cast => ExprPrecedence::Cast,
+            Binary(bin_op) => bin_op.precedence(),
+            Range(_) => ExprPrecedence::Range,
             Assign | AssignOp(_) => ExprPrecedence::Assign,
         }
     }
@@ -152,22 +89,17 @@ impl AssocOp {
         // NOTE: it is a bug to have an operators that has same precedence but different fixities!
         match *self {
             Assign | AssignOp(_) => Fixity::Right,
-            As | Multiply | Divide | Modulus | Add | Subtract | ShiftLeft | ShiftRight | BitAnd
-            | BitXor | BitOr | LAnd | LOr => Fixity::Left,
-            Less | Greater | LessEqual | GreaterEqual | Equal | NotEqual | DotDot | DotDotEq => {
-                Fixity::None
-            }
+            Binary(binop) => binop.fixity(),
+            Cast => Fixity::Left,
+            Range(_) => Fixity::None,
         }
     }
 
     pub fn is_comparison(&self) -> bool {
         use AssocOp::*;
         match *self {
-            Less | Greater | LessEqual | GreaterEqual | Equal | NotEqual => true,
-            Assign | AssignOp(_) | As | Multiply | Divide | Modulus | Add | Subtract
-            | ShiftLeft | ShiftRight | BitAnd | BitXor | BitOr | LAnd | LOr | DotDot | DotDotEq => {
-                false
-            }
+            Binary(binop) => binop.is_comparison(),
+            Assign | AssignOp(_) | Cast | Range(_) => false,
         }
     }
 
@@ -175,34 +107,7 @@ impl AssocOp {
         use AssocOp::*;
         match *self {
             Assign | AssignOp(_) => true,
-            Less | Greater | LessEqual | GreaterEqual | Equal | NotEqual | As | Multiply
-            | Divide | Modulus | Add | Subtract | ShiftLeft | ShiftRight | BitAnd | BitXor
-            | BitOr | LAnd | LOr | DotDot | DotDotEq => false,
-        }
-    }
-
-    pub fn to_ast_binop(&self) -> Option<BinOpKind> {
-        use AssocOp::*;
-        match *self {
-            Less => Some(BinOpKind::Lt),
-            Greater => Some(BinOpKind::Gt),
-            LessEqual => Some(BinOpKind::Le),
-            GreaterEqual => Some(BinOpKind::Ge),
-            Equal => Some(BinOpKind::Eq),
-            NotEqual => Some(BinOpKind::Ne),
-            Multiply => Some(BinOpKind::Mul),
-            Divide => Some(BinOpKind::Div),
-            Modulus => Some(BinOpKind::Rem),
-            Add => Some(BinOpKind::Add),
-            Subtract => Some(BinOpKind::Sub),
-            ShiftLeft => Some(BinOpKind::Shl),
-            ShiftRight => Some(BinOpKind::Shr),
-            BitAnd => Some(BinOpKind::BitAnd),
-            BitXor => Some(BinOpKind::BitXor),
-            BitOr => Some(BinOpKind::BitOr),
-            LAnd => Some(BinOpKind::And),
-            LOr => Some(BinOpKind::Or),
-            Assign | AssignOp(_) | As | DotDot | DotDotEq => None,
+            Cast | Binary(_) | Range(_) => false,
         }
     }
 
@@ -212,20 +117,23 @@ impl AssocOp {
     /// parentheses while having a high degree of confidence on the correctness of the suggestion.
     pub fn can_continue_expr_unambiguously(&self) -> bool {
         use AssocOp::*;
+        use BinOpKind::*;
         matches!(
             self,
-            BitXor | // `{ 42 } ^ 3`
             Assign | // `{ 42 } = { 42 }`
-            Divide | // `{ 42 } / 42`
-            Modulus | // `{ 42 } % 2`
-            ShiftRight | // `{ 42 } >> 2`
-            LessEqual | // `{ 42 } <= 3`
-            Greater | // `{ 42 } > 3`
-            GreaterEqual | // `{ 42 } >= 3`
+            Binary(
+                BitXor | // `{ 42 } ^ 3`
+                Div | // `{ 42 } / 42`
+                Rem | // `{ 42 } % 2`
+                Shr | // `{ 42 } >> 2`
+                Le | // `{ 42 } <= 3`
+                Gt | // `{ 42 } > 3`
+                Ge   // `{ 42 } >= 3`
+            ) |
             AssignOp(_) | // `{ 42 } +=`
             // Equal | // `{ 42 } == { 42 }`    Accepting these here would regress incorrect
             // NotEqual | // `{ 42 } != { 42 }  struct literals parser recovery.
-            As // `{ 42 } as usize`
+            Cast // `{ 42 } as usize`
         )
     }
 }
diff --git a/compiler/rustc_ast_pretty/src/pprust/state/expr.rs b/compiler/rustc_ast_pretty/src/pprust/state/expr.rs
index 4b1374ceef3..496323a35b8 100644
--- a/compiler/rustc_ast_pretty/src/pprust/state/expr.rs
+++ b/compiler/rustc_ast_pretty/src/pprust/state/expr.rs
@@ -5,7 +5,7 @@ use itertools::{Itertools, Position};
 use rustc_ast::ptr::P;
 use rustc_ast::util::classify;
 use rustc_ast::util::literal::escape_byte_str_symbol;
-use rustc_ast::util::parser::{self, AssocOp, ExprPrecedence, Fixity};
+use rustc_ast::util::parser::{self, ExprPrecedence, Fixity};
 use rustc_ast::{
     self as ast, BlockCheckMode, FormatAlignment, FormatArgPosition, FormatArgsPiece, FormatCount,
     FormatDebugHex, FormatSign, FormatTrait, token,
@@ -279,12 +279,11 @@ impl<'a> State<'a> {
         rhs: &ast::Expr,
         fixup: FixupContext,
     ) {
-        let assoc_op = AssocOp::from_ast_binop(op.node);
-        let binop_prec = assoc_op.precedence();
+        let binop_prec = op.node.precedence();
         let left_prec = lhs.precedence();
         let right_prec = rhs.precedence();
 
-        let (mut left_needs_paren, right_needs_paren) = match assoc_op.fixity() {
+        let (mut left_needs_paren, right_needs_paren) = match op.node.fixity() {
             Fixity::Left => (left_prec < binop_prec, right_prec <= binop_prec),
             Fixity::Right => (left_prec <= binop_prec, right_prec < binop_prec),
             Fixity::None => (left_prec <= binop_prec, right_prec <= binop_prec),
diff --git a/compiler/rustc_attr_data_structures/src/attributes.rs b/compiler/rustc_attr_data_structures/src/attributes.rs
index 9ac8de0227d..d2d1285b075 100644
--- a/compiler/rustc_attr_data_structures/src/attributes.rs
+++ b/compiler/rustc_attr_data_structures/src/attributes.rs
@@ -138,13 +138,14 @@ impl Deprecation {
     }
 }
 
-/// Attributes represent parsed, *built in*, inert attributes. That means,
-/// attributes that are not actually ever expanded.
-/// For more information on this, see the module docs on the rustc_attr_parsing crate.
+/// Represent parsed, *built in*, inert attributes.
+///
+/// That means attributes that are not actually ever expanded.
+/// For more information on this, see the module docs on the [`rustc_attr_parsing`] crate.
 /// They're instead used as markers, to guide the compilation process in various way in most every stage of the compiler.
 /// These are kept around after the AST, into the HIR and further on.
 ///
-/// The word parsed could be a little misleading here, because the parser already parses
+/// The word "parsed" could be a little misleading here, because the parser already parses
 /// attributes early on. However, the result, an [`ast::Attribute`]
 /// is only parsed at a high level, still containing a token stream in many cases. That is
 /// because the structure of the contents varies from attribute to attribute.
@@ -153,7 +154,9 @@ impl Deprecation {
 /// the place where `must_use` is checked) little to no extra parsing or validating needs to
 /// happen.
 ///
-/// For more docs, look in [`rustc_attr`](https://doc.rust-lang.org/stable/nightly-rustc/rustc_attr/index.html)
+/// For more docs, look in [`rustc_attr_parsing`].
+///
+/// [`rustc_attr_parsing`]: https://doc.rust-lang.org/nightly/nightly-rustc/rustc_attr_parsing/index.html
 #[derive(Clone, Debug, HashStable_Generic, Encodable, Decodable, PrintAttribute)]
 pub enum AttributeKind {
     // tidy-alphabetical-start
diff --git a/compiler/rustc_const_eval/src/check_consts/mod.rs b/compiler/rustc_const_eval/src/check_consts/mod.rs
index 659d4a30456..607cb2e497d 100644
--- a/compiler/rustc_const_eval/src/check_consts/mod.rs
+++ b/compiler/rustc_const_eval/src/check_consts/mod.rs
@@ -94,6 +94,11 @@ pub fn rustc_allow_const_fn_unstable(
 /// world into two functions: those that are safe to expose on stable (and hence may not use
 /// unstable features, not even recursively), and those that are not.
 pub fn is_fn_or_trait_safe_to_expose_on_stable(tcx: TyCtxt<'_>, def_id: DefId) -> bool {
+    // A default body in a `#[const_trait]` is const-stable when the trait is const-stable.
+    if tcx.is_const_default_method(def_id) {
+        return is_fn_or_trait_safe_to_expose_on_stable(tcx, tcx.parent(def_id));
+    }
+
     match tcx.lookup_const_stability(def_id) {
         None => {
             // In a `staged_api` crate, we do enforce recursive const stability for all unmarked
diff --git a/compiler/rustc_data_structures/src/flat_map_in_place.rs b/compiler/rustc_data_structures/src/flat_map_in_place.rs
index e66b00b7557..6d718059f9c 100644
--- a/compiler/rustc_data_structures/src/flat_map_in_place.rs
+++ b/compiler/rustc_data_structures/src/flat_map_in_place.rs
@@ -1,4 +1,4 @@
-use std::ptr;
+use std::{mem, ptr};
 
 use smallvec::{Array, SmallVec};
 use thin_vec::ThinVec;
@@ -13,39 +13,44 @@ pub trait FlatMapInPlace<T>: Sized {
 // The implementation of this method is syntactically identical for all the
 // different vector types.
 macro_rules! flat_map_in_place {
-    () => {
+    ($vec:ident $( where T: $bound:path)?) => {
         fn flat_map_in_place<F, I>(&mut self, mut f: F)
         where
             F: FnMut(T) -> I,
             I: IntoIterator<Item = T>,
         {
+            struct LeakGuard<'a, T $(: $bound)?>(&'a mut $vec<T>);
+
+            impl<'a, T $(: $bound)?> Drop for LeakGuard<'a, T> {
+                fn drop(&mut self) {
+                    unsafe {
+                        self.0.set_len(0); // make sure we just leak elements in case of panic
+                    }
+                }
+            }
+
+            let this = LeakGuard(self);
+
             let mut read_i = 0;
             let mut write_i = 0;
             unsafe {
-                let mut old_len = self.len();
-                self.set_len(0); // make sure we just leak elements in case of panic
-
-                while read_i < old_len {
+                while read_i < this.0.len() {
                     // move the read_i'th item out of the vector and map it
                     // to an iterator
-                    let e = ptr::read(self.as_ptr().add(read_i));
+                    let e = ptr::read(this.0.as_ptr().add(read_i));
                     let iter = f(e).into_iter();
                     read_i += 1;
 
                     for e in iter {
                         if write_i < read_i {
-                            ptr::write(self.as_mut_ptr().add(write_i), e);
+                            ptr::write(this.0.as_mut_ptr().add(write_i), e);
                             write_i += 1;
                         } else {
                             // If this is reached we ran out of space
                             // in the middle of the vector.
                             // However, the vector is in a valid state here,
                             // so we just do a somewhat inefficient insert.
-                            self.set_len(old_len);
-                            self.insert(write_i, e);
-
-                            old_len = self.len();
-                            self.set_len(0);
+                            this.0.insert(write_i, e);
 
                             read_i += 1;
                             write_i += 1;
@@ -54,20 +59,23 @@ macro_rules! flat_map_in_place {
                 }
 
                 // write_i tracks the number of actually written new items.
-                self.set_len(write_i);
+                this.0.set_len(write_i);
+
+                // The ThinVec is in a sane state again. Prevent the LeakGuard from leaking the data.
+                mem::forget(this);
             }
         }
     };
 }
 
 impl<T> FlatMapInPlace<T> for Vec<T> {
-    flat_map_in_place!();
+    flat_map_in_place!(Vec);
 }
 
 impl<T, A: Array<Item = T>> FlatMapInPlace<T> for SmallVec<A> {
-    flat_map_in_place!();
+    flat_map_in_place!(SmallVec where T: Array);
 }
 
 impl<T> FlatMapInPlace<T> for ThinVec<T> {
-    flat_map_in_place!();
+    flat_map_in_place!(ThinVec);
 }
diff --git a/compiler/rustc_hir/src/hir.rs b/compiler/rustc_hir/src/hir.rs
index f0eaec55dbd..3c75bc588a1 100644
--- a/compiler/rustc_hir/src/hir.rs
+++ b/compiler/rustc_hir/src/hir.rs
@@ -4,7 +4,7 @@ use std::fmt;
 use rustc_abi::ExternAbi;
 use rustc_ast::attr::AttributeExt;
 use rustc_ast::token::CommentKind;
-use rustc_ast::util::parser::{AssocOp, ExprPrecedence};
+use rustc_ast::util::parser::ExprPrecedence;
 use rustc_ast::{
     self as ast, FloatTy, InlineAsmOptions, InlineAsmTemplatePiece, IntTy, Label, LitIntType,
     LitKind, TraitObjectSyntax, UintTy, UnsafeBinderCastKind,
@@ -2124,7 +2124,7 @@ impl Expr<'_> {
             | ExprKind::Become(..) => ExprPrecedence::Jump,
 
             // Binop-like expr kinds, handled by `AssocOp`.
-            ExprKind::Binary(op, ..) => AssocOp::from_ast_binop(op.node).precedence(),
+            ExprKind::Binary(op, ..) => op.node.precedence(),
             ExprKind::Cast(..) => ExprPrecedence::Cast,
 
             ExprKind::Assign(..) |
diff --git a/compiler/rustc_hir_pretty/src/lib.rs b/compiler/rustc_hir_pretty/src/lib.rs
index 5c7426d76b3..b3377b48769 100644
--- a/compiler/rustc_hir_pretty/src/lib.rs
+++ b/compiler/rustc_hir_pretty/src/lib.rs
@@ -10,7 +10,7 @@ use std::cell::Cell;
 use std::vec;
 
 use rustc_abi::ExternAbi;
-use rustc_ast::util::parser::{self, AssocOp, ExprPrecedence, Fixity};
+use rustc_ast::util::parser::{self, ExprPrecedence, Fixity};
 use rustc_ast::{AttrStyle, DUMMY_NODE_ID, DelimArgs};
 use rustc_ast_pretty::pp::Breaks::{Consistent, Inconsistent};
 use rustc_ast_pretty::pp::{self, Breaks};
@@ -1296,12 +1296,11 @@ impl<'a> State<'a> {
     }
 
     fn print_expr_binary(&mut self, op: hir::BinOp, lhs: &hir::Expr<'_>, rhs: &hir::Expr<'_>) {
-        let assoc_op = AssocOp::from_ast_binop(op.node);
-        let binop_prec = assoc_op.precedence();
+        let binop_prec = op.node.precedence();
         let left_prec = lhs.precedence();
         let right_prec = rhs.precedence();
 
-        let (mut left_needs_paren, right_needs_paren) = match assoc_op.fixity() {
+        let (mut left_needs_paren, right_needs_paren) = match op.node.fixity() {
             Fixity::Left => (left_prec < binop_prec, right_prec <= binop_prec),
             Fixity::Right => (left_prec <= binop_prec, right_prec < binop_prec),
             Fixity::None => (left_prec <= binop_prec, right_prec <= binop_prec),
diff --git a/compiler/rustc_lint/Cargo.toml b/compiler/rustc_lint/Cargo.toml
index 24e7b40c8a2..d6014f5006a 100644
--- a/compiler/rustc_lint/Cargo.toml
+++ b/compiler/rustc_lint/Cargo.toml
@@ -24,6 +24,7 @@ rustc_span = { path = "../rustc_span" }
 rustc_target = { path = "../rustc_target" }
 rustc_trait_selection = { path = "../rustc_trait_selection" }
 rustc_type_ir = { path = "../rustc_type_ir" }
+smallvec = { version = "1.8.1", features = ["union", "may_dangle"] }
 tracing = "0.1"
 unicode-security = "0.1.0"
 # tidy-alphabetical-end
diff --git a/compiler/rustc_lint/messages.ftl b/compiler/rustc_lint/messages.ftl
index 67936763427..d51865810b9 100644
--- a/compiler/rustc_lint/messages.ftl
+++ b/compiler/rustc_lint/messages.ftl
@@ -333,6 +333,11 @@ lint_identifier_uncommon_codepoints = identifier contains {$codepoints_len ->
         *[other] {" "}{$identifier_type}
     } Unicode general security profile
 
+lint_if_let_dtor = {$dtor_kind ->
+    [dyn] value may invoke a custom destructor because it contains a trait object
+    *[concrete] value invokes this custom destructor
+    }
+
 lint_if_let_rescope = `if let` assigns a shorter lifetime since Edition 2024
     .label = this value has a significant drop implementation which may observe a major change in drop order and requires your discretion
     .help = the value is now dropped here in Edition 2024
diff --git a/compiler/rustc_lint/src/if_let_rescope.rs b/compiler/rustc_lint/src/if_let_rescope.rs
index 23f037f3692..39ea8d8e324 100644
--- a/compiler/rustc_lint/src/if_let_rescope.rs
+++ b/compiler/rustc_lint/src/if_let_rescope.rs
@@ -7,13 +7,17 @@ use rustc_errors::{
     Applicability, Diag, EmissionGuarantee, SubdiagMessageOp, Subdiagnostic, SuggestionStyle,
 };
 use rustc_hir::{self as hir, HirIdSet};
-use rustc_macros::LintDiagnostic;
-use rustc_middle::ty::TyCtxt;
+use rustc_macros::{LintDiagnostic, Subdiagnostic};
 use rustc_middle::ty::adjustment::Adjust;
+use rustc_middle::ty::significant_drop_order::{
+    extract_component_with_significant_dtor, ty_dtor_span,
+};
+use rustc_middle::ty::{self, Ty, TyCtxt};
 use rustc_session::lint::{FutureIncompatibilityReason, LintId};
 use rustc_session::{declare_lint, impl_lint_pass};
-use rustc_span::Span;
 use rustc_span::edition::Edition;
+use rustc_span::{DUMMY_SP, Span};
+use smallvec::SmallVec;
 
 use crate::{LateContext, LateLintPass};
 
@@ -130,6 +134,7 @@ impl IfLetRescope {
             hir::ExprKind::If(_cond, _conseq, Some(alt)) => alt.span.shrink_to_hi(),
             _ => return,
         };
+        let mut seen_dyn = false;
         let mut add_bracket_to_match_head = match_head_needs_bracket(tcx, expr);
         let mut significant_droppers = vec![];
         let mut lifetime_ends = vec![];
@@ -137,6 +142,7 @@ impl IfLetRescope {
         let mut alt_heads = vec![];
         let mut match_heads = vec![];
         let mut consequent_heads = vec![];
+        let mut destructors = vec![];
         let mut first_if_to_lint = None;
         let mut first_if_to_rewrite = false;
         let mut empty_alt = false;
@@ -160,11 +166,25 @@ impl IfLetRescope {
                 let before_conseq = conseq.span.shrink_to_lo();
                 let lifetime_end = source_map.end_point(conseq.span);
 
-                if let ControlFlow::Break(significant_dropper) =
+                if let ControlFlow::Break((drop_span, drop_tys)) =
                     (FindSignificantDropper { cx }).check_if_let_scrutinee(init)
                 {
+                    destructors.extend(drop_tys.into_iter().filter_map(|ty| {
+                        if let Some(span) = ty_dtor_span(tcx, ty) {
+                            Some(DestructorLabel { span, dtor_kind: "concrete" })
+                        } else if matches!(ty.kind(), ty::Dynamic(..)) {
+                            if seen_dyn {
+                                None
+                            } else {
+                                seen_dyn = true;
+                                Some(DestructorLabel { span: DUMMY_SP, dtor_kind: "dyn" })
+                            }
+                        } else {
+                            None
+                        }
+                    }));
                     first_if_to_lint = first_if_to_lint.or_else(|| Some((span, expr.hir_id)));
-                    significant_droppers.push(significant_dropper);
+                    significant_droppers.push(drop_span);
                     lifetime_ends.push(lifetime_end);
                     if ty_ascription.is_some()
                         || !expr.span.can_be_used_for_suggestions()
@@ -227,6 +247,7 @@ impl IfLetRescope {
                 hir_id,
                 span,
                 IfLetRescopeLint {
+                    destructors,
                     significant_droppers,
                     lifetime_ends,
                     rewrite: first_if_to_rewrite.then_some(IfLetRescopeRewrite {
@@ -288,6 +309,8 @@ impl<'tcx> LateLintPass<'tcx> for IfLetRescope {
 #[derive(LintDiagnostic)]
 #[diag(lint_if_let_rescope)]
 struct IfLetRescopeLint {
+    #[subdiagnostic]
+    destructors: Vec<DestructorLabel>,
     #[label]
     significant_droppers: Vec<Span>,
     #[help]
@@ -347,6 +370,14 @@ impl Subdiagnostic for IfLetRescopeRewrite {
     }
 }
 
+#[derive(Subdiagnostic)]
+#[note(lint_if_let_dtor)]
+struct DestructorLabel {
+    #[primary_span]
+    span: Span,
+    dtor_kind: &'static str,
+}
+
 struct AltHead(Span);
 
 struct ConsequentRewrite {
@@ -374,7 +405,10 @@ impl<'tcx> FindSignificantDropper<'_, 'tcx> {
     /// of the scrutinee itself, and also recurses into the expression to find any ref
     /// exprs (or autoref) which would promote temporaries that would be scoped to the
     /// end of this `if`.
-    fn check_if_let_scrutinee(&mut self, init: &'tcx hir::Expr<'tcx>) -> ControlFlow<Span> {
+    fn check_if_let_scrutinee(
+        &mut self,
+        init: &'tcx hir::Expr<'tcx>,
+    ) -> ControlFlow<(Span, SmallVec<[Ty<'tcx>; 4]>)> {
         self.check_promoted_temp_with_drop(init)?;
         self.visit_expr(init)
     }
@@ -385,28 +419,35 @@ impl<'tcx> FindSignificantDropper<'_, 'tcx> {
     /// An expression is a promoted temporary if it has an addr taken (i.e. `&expr` or autoref)
     /// or is the scrutinee of the `if let`, *and* the expression is not a place
     /// expr, and it has a significant drop.
-    fn check_promoted_temp_with_drop(&self, expr: &'tcx hir::Expr<'tcx>) -> ControlFlow<Span> {
-        if !expr.is_place_expr(|base| {
+    fn check_promoted_temp_with_drop(
+        &self,
+        expr: &'tcx hir::Expr<'tcx>,
+    ) -> ControlFlow<(Span, SmallVec<[Ty<'tcx>; 4]>)> {
+        if expr.is_place_expr(|base| {
             self.cx
                 .typeck_results()
                 .adjustments()
                 .get(base.hir_id)
                 .is_some_and(|x| x.iter().any(|adj| matches!(adj.kind, Adjust::Deref(_))))
-        }) && self
-            .cx
-            .typeck_results()
-            .expr_ty(expr)
-            .has_significant_drop(self.cx.tcx, self.cx.typing_env())
-        {
-            ControlFlow::Break(expr.span)
-        } else {
-            ControlFlow::Continue(())
+        }) {
+            return ControlFlow::Continue(());
         }
+
+        let drop_tys = extract_component_with_significant_dtor(
+            self.cx.tcx,
+            self.cx.typing_env(),
+            self.cx.typeck_results().expr_ty(expr),
+        );
+        if drop_tys.is_empty() {
+            return ControlFlow::Continue(());
+        }
+
+        ControlFlow::Break((expr.span, drop_tys))
     }
 }
 
 impl<'tcx> Visitor<'tcx> for FindSignificantDropper<'_, 'tcx> {
-    type Result = ControlFlow<Span>;
+    type Result = ControlFlow<(Span, SmallVec<[Ty<'tcx>; 4]>)>;
 
     fn visit_block(&mut self, b: &'tcx hir::Block<'tcx>) -> Self::Result {
         // Blocks introduce temporary terminating scope for all of its
diff --git a/compiler/rustc_middle/src/ty/instance.rs b/compiler/rustc_middle/src/ty/instance.rs
index e9c19331e4a..98ca71b86be 100644
--- a/compiler/rustc_middle/src/ty/instance.rs
+++ b/compiler/rustc_middle/src/ty/instance.rs
@@ -111,8 +111,9 @@ pub enum InstanceKind<'tcx> {
 
     /// Dynamic dispatch to `<dyn Trait as Trait>::fn`.
     ///
-    /// This `InstanceKind` does not have callable MIR. Calls to `Virtual` instances must be
-    /// codegen'd as virtual calls through the vtable.
+    /// This `InstanceKind` may have a callable MIR as the default implementation.
+    /// Calls to `Virtual` instances must be codegen'd as virtual calls through the vtable.
+    /// *This means we might not know exactly what is being called.*
     ///
     /// If this is reified to a `fn` pointer, a `ReifyShim` is used (see `ReifyShim` above for more
     /// details on that).
diff --git a/compiler/rustc_middle/src/ty/mod.rs b/compiler/rustc_middle/src/ty/mod.rs
index eb70a35d370..d926d6cc02a 100644
--- a/compiler/rustc_middle/src/ty/mod.rs
+++ b/compiler/rustc_middle/src/ty/mod.rs
@@ -122,6 +122,7 @@ pub mod normalize_erasing_regions;
 pub mod pattern;
 pub mod print;
 pub mod relate;
+pub mod significant_drop_order;
 pub mod trait_def;
 pub mod util;
 pub mod visit;
diff --git a/compiler/rustc_middle/src/ty/print/pretty.rs b/compiler/rustc_middle/src/ty/print/pretty.rs
index 942411945bf..c77b37a302b 100644
--- a/compiler/rustc_middle/src/ty/print/pretty.rs
+++ b/compiler/rustc_middle/src/ty/print/pretty.rs
@@ -1516,10 +1516,7 @@ pub trait PrettyPrinter<'tcx>: Printer<'tcx> + fmt::Write {
             ty::ExprKind::Binop(op) => {
                 let (_, _, c1, c2) = expr.binop_args();
 
-                let precedence = |binop: crate::mir::BinOp| {
-                    use rustc_ast::util::parser::AssocOp;
-                    AssocOp::from_ast_binop(binop.to_hir_binop()).precedence()
-                };
+                let precedence = |binop: crate::mir::BinOp| binop.to_hir_binop().precedence();
                 let op_precedence = precedence(op);
                 let formatted_op = op.to_hir_binop().as_str();
                 let (lhs_parenthesized, rhs_parenthesized) = match (c1.kind(), c2.kind()) {
diff --git a/compiler/rustc_middle/src/ty/significant_drop_order.rs b/compiler/rustc_middle/src/ty/significant_drop_order.rs
new file mode 100644
index 00000000000..7f0d82d89fe
--- /dev/null
+++ b/compiler/rustc_middle/src/ty/significant_drop_order.rs
@@ -0,0 +1,172 @@
+use rustc_data_structures::fx::FxHashSet;
+use rustc_data_structures::unord::UnordSet;
+use rustc_hir::def_id::DefId;
+use rustc_span::Span;
+use smallvec::{SmallVec, smallvec};
+use tracing::{debug, instrument};
+
+use crate::ty::{self, Ty, TyCtxt};
+
+/// An additional filter to exclude well-known types from the ecosystem
+/// because their drops are trivial.
+/// This returns additional types to check if the drops are delegated to those.
+/// A typical example is `hashbrown::HashMap<K, V>`, whose drop is delegated to `K` and `V`.
+fn true_significant_drop_ty<'tcx>(
+    tcx: TyCtxt<'tcx>,
+    ty: Ty<'tcx>,
+) -> Option<SmallVec<[Ty<'tcx>; 2]>> {
+    if let ty::Adt(def, args) = ty.kind() {
+        let mut did = def.did();
+        let mut name_rev = vec![];
+        loop {
+            let key = tcx.def_key(did);
+
+            match key.disambiguated_data.data {
+                rustc_hir::definitions::DefPathData::CrateRoot => {
+                    name_rev.push(tcx.crate_name(did.krate))
+                }
+                rustc_hir::definitions::DefPathData::TypeNs(symbol) => name_rev.push(symbol),
+                _ => return None,
+            }
+            if let Some(parent) = key.parent {
+                did = DefId { krate: did.krate, index: parent };
+            } else {
+                break;
+            }
+        }
+        let name_str: Vec<_> = name_rev.iter().rev().map(|x| x.as_str()).collect();
+        debug!(?name_str);
+        match name_str[..] {
+            // These are the types from Rust core ecosystem
+            ["syn" | "proc_macro2", ..]
+            | ["core" | "std", "task", "LocalWaker" | "Waker"]
+            | ["core" | "std", "task", "wake", "LocalWaker" | "Waker"] => Some(smallvec![]),
+            // These are important types from Rust ecosystem
+            ["tracing", "instrument", "Instrumented"] | ["bytes", "Bytes"] => Some(smallvec![]),
+            ["hashbrown", "raw", "RawTable" | "RawIntoIter"] => {
+                if let [ty, ..] = &***args
+                    && let Some(ty) = ty.as_type()
+                {
+                    Some(smallvec![ty])
+                } else {
+                    None
+                }
+            }
+            ["hashbrown", "raw", "RawDrain"] => {
+                if let [_, ty, ..] = &***args
+                    && let Some(ty) = ty.as_type()
+                {
+                    Some(smallvec![ty])
+                } else {
+                    None
+                }
+            }
+            _ => None,
+        }
+    } else {
+        None
+    }
+}
+
+/// Returns the list of types with a "potentially sigificant" that may be dropped
+/// by dropping a value of type `ty`.
+#[instrument(level = "trace", skip(tcx, typing_env))]
+pub fn extract_component_raw<'tcx>(
+    tcx: TyCtxt<'tcx>,
+    typing_env: ty::TypingEnv<'tcx>,
+    ty: Ty<'tcx>,
+    ty_seen: &mut UnordSet<Ty<'tcx>>,
+) -> SmallVec<[Ty<'tcx>; 4]> {
+    // Droppiness does not depend on regions, so let us erase them.
+    let ty = tcx.try_normalize_erasing_regions(typing_env, ty).unwrap_or(ty);
+
+    let tys = tcx.list_significant_drop_tys(typing_env.as_query_input(ty));
+    debug!(?ty, "components");
+    let mut out_tys = smallvec![];
+    for ty in tys {
+        if let Some(tys) = true_significant_drop_ty(tcx, ty) {
+            // Some types can be further opened up because the drop is simply delegated
+            for ty in tys {
+                if ty_seen.insert(ty) {
+                    out_tys.extend(extract_component_raw(tcx, typing_env, ty, ty_seen));
+                }
+            }
+        } else {
+            if ty_seen.insert(ty) {
+                out_tys.push(ty);
+            }
+        }
+    }
+    out_tys
+}
+
+#[instrument(level = "trace", skip(tcx, typing_env))]
+pub fn extract_component_with_significant_dtor<'tcx>(
+    tcx: TyCtxt<'tcx>,
+    typing_env: ty::TypingEnv<'tcx>,
+    ty: Ty<'tcx>,
+) -> SmallVec<[Ty<'tcx>; 4]> {
+    let mut tys = extract_component_raw(tcx, typing_env, ty, &mut Default::default());
+    let mut deduplicate = FxHashSet::default();
+    tys.retain(|oty| deduplicate.insert(*oty));
+    tys.into_iter().collect()
+}
+
+/// Extract the span of the custom destructor of a type
+/// especially the span of the `impl Drop` header or its entire block
+/// when we are working with current local crate.
+#[instrument(level = "trace", skip(tcx))]
+pub fn ty_dtor_span<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> Option<Span> {
+    match ty.kind() {
+        ty::Bool
+        | ty::Char
+        | ty::Int(_)
+        | ty::Uint(_)
+        | ty::Float(_)
+        | ty::Error(_)
+        | ty::Str
+        | ty::Never
+        | ty::RawPtr(_, _)
+        | ty::Ref(_, _, _)
+        | ty::FnPtr(_, _)
+        | ty::Tuple(_)
+        | ty::Dynamic(_, _, _)
+        | ty::Alias(_, _)
+        | ty::Bound(_, _)
+        | ty::Pat(_, _)
+        | ty::Placeholder(_)
+        | ty::Infer(_)
+        | ty::Slice(_)
+        | ty::Array(_, _)
+        | ty::UnsafeBinder(_) => None,
+
+        ty::Adt(adt_def, _) => {
+            let did = adt_def.did();
+            let try_local_did_span = |did: DefId| {
+                if let Some(local) = did.as_local() {
+                    tcx.source_span(local)
+                } else {
+                    tcx.def_span(did)
+                }
+            };
+            let dtor = if let Some(dtor) = tcx.adt_destructor(did) {
+                dtor.did
+            } else if let Some(dtor) = tcx.adt_async_destructor(did) {
+                dtor.future
+            } else {
+                return Some(try_local_did_span(did));
+            };
+            let def_key = tcx.def_key(dtor);
+            let Some(parent_index) = def_key.parent else { return Some(try_local_did_span(dtor)) };
+            let parent_did = DefId { index: parent_index, krate: dtor.krate };
+            Some(try_local_did_span(parent_did))
+        }
+        ty::Coroutine(did, _)
+        | ty::CoroutineWitness(did, _)
+        | ty::CoroutineClosure(did, _)
+        | ty::Closure(did, _)
+        | ty::FnDef(did, _)
+        | ty::Foreign(did) => Some(tcx.def_span(did)),
+        ty::Param(_) => None,
+    }
+}
diff --git a/compiler/rustc_mir_transform/src/lint_tail_expr_drop_order.rs b/compiler/rustc_mir_transform/src/lint_tail_expr_drop_order.rs
index e3260e45bc5..7d77fffa83f 100644
--- a/compiler/rustc_mir_transform/src/lint_tail_expr_drop_order.rs
+++ b/compiler/rustc_mir_transform/src/lint_tail_expr_drop_order.rs
@@ -6,7 +6,7 @@ use rustc_data_structures::fx::{FxHashMap, FxHashSet};
 use rustc_data_structures::unord::{UnordMap, UnordSet};
 use rustc_errors::Subdiagnostic;
 use rustc_hir::CRATE_HIR_ID;
-use rustc_hir::def_id::{DefId, LocalDefId};
+use rustc_hir::def_id::LocalDefId;
 use rustc_index::bit_set::MixedBitSet;
 use rustc_index::{IndexSlice, IndexVec};
 use rustc_macros::{LintDiagnostic, Subdiagnostic};
@@ -15,7 +15,10 @@ use rustc_middle::mir::{
     self, BasicBlock, Body, ClearCrossCrate, Local, Location, Place, StatementKind, TerminatorKind,
     dump_mir,
 };
-use rustc_middle::ty::{self, Ty, TyCtxt};
+use rustc_middle::ty::significant_drop_order::{
+    extract_component_with_significant_dtor, ty_dtor_span,
+};
+use rustc_middle::ty::{self, TyCtxt};
 use rustc_mir_dataflow::impls::MaybeInitializedPlaces;
 use rustc_mir_dataflow::move_paths::{LookupResult, MoveData, MovePathIndex};
 use rustc_mir_dataflow::{Analysis, MaybeReachable, ResultsCursor};
@@ -23,8 +26,7 @@ use rustc_session::lint::builtin::TAIL_EXPR_DROP_ORDER;
 use rustc_session::lint::{self};
 use rustc_span::{DUMMY_SP, Span, Symbol};
 use rustc_type_ir::data_structures::IndexMap;
-use smallvec::{SmallVec, smallvec};
-use tracing::{debug, instrument};
+use tracing::debug;
 
 fn place_has_common_prefix<'tcx>(left: &Place<'tcx>, right: &Place<'tcx>) -> bool {
     left.local == right.local
@@ -155,170 +157,6 @@ impl<'a, 'mir, 'tcx> DropsReachable<'a, 'mir, 'tcx> {
     }
 }
 
-/// An additional filter to exclude well-known types from the ecosystem
-/// because their drops are trivial.
-/// This returns additional types to check if the drops are delegated to those.
-/// A typical example is `hashbrown::HashMap<K, V>`, whose drop is delegated to `K` and `V`.
-fn true_significant_drop_ty<'tcx>(
-    tcx: TyCtxt<'tcx>,
-    ty: Ty<'tcx>,
-) -> Option<SmallVec<[Ty<'tcx>; 2]>> {
-    if let ty::Adt(def, args) = ty.kind() {
-        let mut did = def.did();
-        let mut name_rev = vec![];
-        loop {
-            let key = tcx.def_key(did);
-
-            match key.disambiguated_data.data {
-                rustc_hir::definitions::DefPathData::CrateRoot => {
-                    name_rev.push(tcx.crate_name(did.krate))
-                }
-                rustc_hir::definitions::DefPathData::TypeNs(symbol) => name_rev.push(symbol),
-                _ => return None,
-            }
-            if let Some(parent) = key.parent {
-                did = DefId { krate: did.krate, index: parent };
-            } else {
-                break;
-            }
-        }
-        let name_str: Vec<_> = name_rev.iter().rev().map(|x| x.as_str()).collect();
-        debug!(?name_str);
-        match name_str[..] {
-            // These are the types from Rust core ecosystem
-            ["syn" | "proc_macro2", ..]
-            | ["core" | "std", "task", "LocalWaker" | "Waker"]
-            | ["core" | "std", "task", "wake", "LocalWaker" | "Waker"] => Some(smallvec![]),
-            // These are important types from Rust ecosystem
-            ["tracing", "instrument", "Instrumented"] | ["bytes", "Bytes"] => Some(smallvec![]),
-            ["hashbrown", "raw", "RawTable" | "RawIntoIter"] => {
-                if let [ty, ..] = &***args
-                    && let Some(ty) = ty.as_type()
-                {
-                    Some(smallvec![ty])
-                } else {
-                    None
-                }
-            }
-            ["hashbrown", "raw", "RawDrain"] => {
-                if let [_, ty, ..] = &***args
-                    && let Some(ty) = ty.as_type()
-                {
-                    Some(smallvec![ty])
-                } else {
-                    None
-                }
-            }
-            _ => None,
-        }
-    } else {
-        None
-    }
-}
-
-/// Returns the list of types with a "potentially sigificant" that may be dropped
-/// by dropping a value of type `ty`.
-#[instrument(level = "debug", skip(tcx, typing_env))]
-fn extract_component_raw<'tcx>(
-    tcx: TyCtxt<'tcx>,
-    typing_env: ty::TypingEnv<'tcx>,
-    ty: Ty<'tcx>,
-    ty_seen: &mut UnordSet<Ty<'tcx>>,
-) -> SmallVec<[Ty<'tcx>; 4]> {
-    // Droppiness does not depend on regions, so let us erase them.
-    let ty = tcx.try_normalize_erasing_regions(typing_env, ty).unwrap_or(ty);
-
-    let tys = tcx.list_significant_drop_tys(typing_env.as_query_input(ty));
-    debug!(?ty, "components");
-    let mut out_tys = smallvec![];
-    for ty in tys {
-        if let Some(tys) = true_significant_drop_ty(tcx, ty) {
-            // Some types can be further opened up because the drop is simply delegated
-            for ty in tys {
-                if ty_seen.insert(ty) {
-                    out_tys.extend(extract_component_raw(tcx, typing_env, ty, ty_seen));
-                }
-            }
-        } else {
-            if ty_seen.insert(ty) {
-                out_tys.push(ty);
-            }
-        }
-    }
-    out_tys
-}
-
-#[instrument(level = "debug", skip(tcx, typing_env))]
-fn extract_component_with_significant_dtor<'tcx>(
-    tcx: TyCtxt<'tcx>,
-    typing_env: ty::TypingEnv<'tcx>,
-    ty: Ty<'tcx>,
-) -> SmallVec<[Ty<'tcx>; 4]> {
-    let mut tys = extract_component_raw(tcx, typing_env, ty, &mut Default::default());
-    let mut deduplicate = FxHashSet::default();
-    tys.retain(|oty| deduplicate.insert(*oty));
-    tys.into_iter().collect()
-}
-
-/// Extract the span of the custom destructor of a type
-/// especially the span of the `impl Drop` header or its entire block
-/// when we are working with current local crate.
-#[instrument(level = "debug", skip(tcx))]
-fn ty_dtor_span<'tcx>(tcx: TyCtxt<'tcx>, ty: Ty<'tcx>) -> Option<Span> {
-    match ty.kind() {
-        ty::Bool
-        | ty::Char
-        | ty::Int(_)
-        | ty::Uint(_)
-        | ty::Float(_)
-        | ty::Error(_)
-        | ty::Str
-        | ty::Never
-        | ty::RawPtr(_, _)
-        | ty::Ref(_, _, _)
-        | ty::FnPtr(_, _)
-        | ty::Tuple(_)
-        | ty::Dynamic(_, _, _)
-        | ty::Alias(_, _)
-        | ty::Bound(_, _)
-        | ty::Pat(_, _)
-        | ty::Placeholder(_)
-        | ty::Infer(_)
-        | ty::Slice(_)
-        | ty::Array(_, _)
-        | ty::UnsafeBinder(_) => None,
-
-        ty::Adt(adt_def, _) => {
-            let did = adt_def.did();
-            let try_local_did_span = |did: DefId| {
-                if let Some(local) = did.as_local() {
-                    tcx.source_span(local)
-                } else {
-                    tcx.def_span(did)
-                }
-            };
-            let dtor = if let Some(dtor) = tcx.adt_destructor(did) {
-                dtor.did
-            } else if let Some(dtor) = tcx.adt_async_destructor(did) {
-                dtor.future
-            } else {
-                return Some(try_local_did_span(did));
-            };
-            let def_key = tcx.def_key(dtor);
-            let Some(parent_index) = def_key.parent else { return Some(try_local_did_span(dtor)) };
-            let parent_did = DefId { index: parent_index, krate: dtor.krate };
-            Some(try_local_did_span(parent_did))
-        }
-        ty::Coroutine(did, _)
-        | ty::CoroutineWitness(did, _)
-        | ty::CoroutineClosure(did, _)
-        | ty::Closure(did, _)
-        | ty::FnDef(did, _)
-        | ty::Foreign(did) => Some(tcx.def_span(did)),
-        ty::Param(_) => None,
-    }
-}
-
 /// Check if a moved place at `idx` is a part of a BID.
 /// The use of this check is that we will consider drops on these
 /// as a drop of the overall BID and, thus, we can exclude it from the diagnosis.
diff --git a/compiler/rustc_parse/src/parser/diagnostics.rs b/compiler/rustc_parse/src/parser/diagnostics.rs
index 67abc2d5394..b35a57ae492 100644
--- a/compiler/rustc_parse/src/parser/diagnostics.rs
+++ b/compiler/rustc_parse/src/parser/diagnostics.rs
@@ -1350,13 +1350,13 @@ impl<'a> Parser<'a> {
             }
             return match (op.node, &outer_op.node) {
                 // `x == y == z`
-                (BinOpKind::Eq, AssocOp::Equal) |
+                (BinOpKind::Eq, AssocOp::Binary(BinOpKind::Eq)) |
                 // `x < y < z` and friends.
-                (BinOpKind::Lt, AssocOp::Less | AssocOp::LessEqual) |
-                (BinOpKind::Le, AssocOp::LessEqual | AssocOp::Less) |
+                (BinOpKind::Lt, AssocOp::Binary(BinOpKind::Lt | BinOpKind::Le)) |
+                (BinOpKind::Le, AssocOp::Binary(BinOpKind::Lt | BinOpKind::Le)) |
                 // `x > y > z` and friends.
-                (BinOpKind::Gt, AssocOp::Greater | AssocOp::GreaterEqual) |
-                (BinOpKind::Ge, AssocOp::GreaterEqual | AssocOp::Greater) => {
+                (BinOpKind::Gt, AssocOp::Binary(BinOpKind::Gt | BinOpKind::Ge)) |
+                (BinOpKind::Ge, AssocOp::Binary(BinOpKind::Gt | BinOpKind::Ge)) => {
                     let expr_to_str = |e: &Expr| {
                         self.span_to_snippet(e.span)
                             .unwrap_or_else(|_| pprust::expr_to_string(e))
@@ -1368,7 +1368,10 @@ impl<'a> Parser<'a> {
                     false // Keep the current parse behavior, where the AST is `(x < y) < z`.
                 }
                 // `x == y < z`
-                (BinOpKind::Eq, AssocOp::Less | AssocOp::LessEqual | AssocOp::Greater | AssocOp::GreaterEqual) => {
+                (
+                    BinOpKind::Eq,
+                    AssocOp::Binary(BinOpKind::Lt | BinOpKind::Le | BinOpKind::Gt | BinOpKind::Ge)
+                ) => {
                     // Consume `z`/outer-op-rhs.
                     let snapshot = self.create_snapshot_for_diagnostic();
                     match self.parse_expr() {
@@ -1389,7 +1392,10 @@ impl<'a> Parser<'a> {
                     }
                 }
                 // `x > y == z`
-                (BinOpKind::Lt | BinOpKind::Le | BinOpKind::Gt | BinOpKind::Ge, AssocOp::Equal) => {
+                (
+                    BinOpKind::Lt | BinOpKind::Le | BinOpKind::Gt | BinOpKind::Ge,
+                    AssocOp::Binary(BinOpKind::Eq)
+                ) => {
                     let snapshot = self.create_snapshot_for_diagnostic();
                     // At this point it is always valid to enclose the lhs in parentheses, no
                     // further checks are necessary.
@@ -1457,10 +1463,10 @@ impl<'a> Parser<'a> {
 
                 // Include `<` to provide this recommendation even in a case like
                 // `Foo<Bar<Baz<Qux, ()>>>`
-                if op.node == BinOpKind::Lt && outer_op.node == AssocOp::Less
-                    || outer_op.node == AssocOp::Greater
+                if op.node == BinOpKind::Lt && outer_op.node == AssocOp::Binary(BinOpKind::Lt)
+                    || outer_op.node == AssocOp::Binary(BinOpKind::Gt)
                 {
-                    if outer_op.node == AssocOp::Less {
+                    if outer_op.node == AssocOp::Binary(BinOpKind::Lt) {
                         let snapshot = self.create_snapshot_for_diagnostic();
                         self.bump();
                         // So far we have parsed `foo<bar<`, consume the rest of the type args.
@@ -2635,10 +2641,12 @@ impl<'a> Parser<'a> {
     ) -> PResult<'a, GenericArg> {
         let is_op_or_dot = AssocOp::from_token(&self.token)
             .and_then(|op| {
-                if let AssocOp::Greater
-                | AssocOp::Less
-                | AssocOp::ShiftRight
-                | AssocOp::GreaterEqual
+                if let AssocOp::Binary(
+                    BinOpKind::Gt
+                    | BinOpKind::Lt
+                    | BinOpKind::Shr
+                    | BinOpKind::Ge
+                )
                 // Don't recover from `foo::<bar = baz>`, because this could be an attempt to
                 // assign a value to a defaulted generic parameter.
                 | AssocOp::Assign
diff --git a/compiler/rustc_parse/src/parser/expr.rs b/compiler/rustc_parse/src/parser/expr.rs
index b2e58c94280..c934c1e36d5 100644
--- a/compiler/rustc_parse/src/parser/expr.rs
+++ b/compiler/rustc_parse/src/parser/expr.rs
@@ -171,7 +171,7 @@ impl<'a> Parser<'a> {
                 break;
             }
             // Check for deprecated `...` syntax
-            if self.token == token::DotDotDot && op.node == AssocOp::DotDotEq {
+            if self.token == token::DotDotDot && op.node == AssocOp::Range(RangeLimits::Closed) {
                 self.err_dotdotdot_syntax(self.token.span);
             }
 
@@ -188,17 +188,12 @@ impl<'a> Parser<'a> {
             }
 
             // Look for JS' `===` and `!==` and recover
-            if (op.node == AssocOp::Equal || op.node == AssocOp::NotEqual)
+            if let AssocOp::Binary(bop @ BinOpKind::Eq | bop @ BinOpKind::Ne) = op.node
                 && self.token == token::Eq
                 && self.prev_token.span.hi() == self.token.span.lo()
             {
                 let sp = op.span.to(self.token.span);
-                let sugg = match op.node {
-                    AssocOp::Equal => "==",
-                    AssocOp::NotEqual => "!=",
-                    _ => unreachable!(),
-                }
-                .into();
+                let sugg = bop.as_str().into();
                 let invalid = format!("{sugg}=");
                 self.dcx().emit_err(errors::InvalidComparisonOperator {
                     span: sp,
@@ -213,7 +208,7 @@ impl<'a> Parser<'a> {
             }
 
             // Look for PHP's `<>` and recover
-            if op.node == AssocOp::Less
+            if op.node == AssocOp::Binary(BinOpKind::Lt)
                 && self.token == token::Gt
                 && self.prev_token.span.hi() == self.token.span.lo()
             {
@@ -231,7 +226,7 @@ impl<'a> Parser<'a> {
             }
 
             // Look for C++'s `<=>` and recover
-            if op.node == AssocOp::LessEqual
+            if op.node == AssocOp::Binary(BinOpKind::Le)
                 && self.token == token::Gt
                 && self.prev_token.span.hi() == self.token.span.lo()
             {
@@ -269,13 +264,13 @@ impl<'a> Parser<'a> {
 
             let op = op.node;
             // Special cases:
-            if op == AssocOp::As {
+            if op == AssocOp::Cast {
                 lhs = self.parse_assoc_op_cast(lhs, lhs_span, ExprKind::Cast)?;
                 continue;
-            } else if op == AssocOp::DotDot || op == AssocOp::DotDotEq {
+            } else if let AssocOp::Range(limits) = op {
                 // If we didn't have to handle `x..`/`x..=`, it would be pretty easy to
                 // generalise it to the Fixity::None code.
-                lhs = self.parse_expr_range(prec, lhs, op, cur_op_span)?;
+                lhs = self.parse_expr_range(prec, lhs, limits, cur_op_span)?;
                 break;
             }
 
@@ -290,46 +285,16 @@ impl<'a> Parser<'a> {
 
             let span = self.mk_expr_sp(&lhs, lhs_span, rhs.span);
             lhs = match op {
-                AssocOp::Add
-                | AssocOp::Subtract
-                | AssocOp::Multiply
-                | AssocOp::Divide
-                | AssocOp::Modulus
-                | AssocOp::LAnd
-                | AssocOp::LOr
-                | AssocOp::BitXor
-                | AssocOp::BitAnd
-                | AssocOp::BitOr
-                | AssocOp::ShiftLeft
-                | AssocOp::ShiftRight
-                | AssocOp::Equal
-                | AssocOp::Less
-                | AssocOp::LessEqual
-                | AssocOp::NotEqual
-                | AssocOp::Greater
-                | AssocOp::GreaterEqual => {
-                    let ast_op = op.to_ast_binop().unwrap();
+                AssocOp::Binary(ast_op) => {
                     let binary = self.mk_binary(source_map::respan(cur_op_span, ast_op), lhs, rhs);
                     self.mk_expr(span, binary)
                 }
                 AssocOp::Assign => self.mk_expr(span, ExprKind::Assign(lhs, rhs, cur_op_span)),
-                AssocOp::AssignOp(k) => {
-                    let aop = match k {
-                        token::Plus => BinOpKind::Add,
-                        token::Minus => BinOpKind::Sub,
-                        token::Star => BinOpKind::Mul,
-                        token::Slash => BinOpKind::Div,
-                        token::Percent => BinOpKind::Rem,
-                        token::Caret => BinOpKind::BitXor,
-                        token::And => BinOpKind::BitAnd,
-                        token::Or => BinOpKind::BitOr,
-                        token::Shl => BinOpKind::Shl,
-                        token::Shr => BinOpKind::Shr,
-                    };
+                AssocOp::AssignOp(aop) => {
                     let aopexpr = self.mk_assign_op(source_map::respan(cur_op_span, aop), lhs, rhs);
                     self.mk_expr(span, aopexpr)
                 }
-                AssocOp::As | AssocOp::DotDot | AssocOp::DotDotEq => {
+                AssocOp::Cast | AssocOp::Range(_) => {
                     self.dcx().span_bug(span, "AssocOp should have been handled by special case")
                 }
             };
@@ -347,13 +312,14 @@ impl<'a> Parser<'a> {
             // An exhaustive check is done in the following block, but these are checked first
             // because they *are* ambiguous but also reasonable looking incorrect syntax, so we
             // want to keep their span info to improve diagnostics in these cases in a later stage.
-            (true, Some(AssocOp::Multiply)) | // `{ 42 } *foo = bar;` or `{ 42 } * 3`
-            (true, Some(AssocOp::Subtract)) | // `{ 42 } -5`
-            (true, Some(AssocOp::Add)) | // `{ 42 } + 42` (unary plus)
-            (true, Some(AssocOp::LAnd)) | // `{ 42 } &&x` (#61475) or `{ 42 } && if x { 1 } else { 0 }`
-            (true, Some(AssocOp::LOr)) | // `{ 42 } || 42` ("logical or" or closure)
-            (true, Some(AssocOp::BitOr)) // `{ 42 } | 42` or `{ 42 } |x| 42`
-            => {
+            (true, Some(AssocOp::Binary(
+                BinOpKind::Mul | // `{ 42 } *foo = bar;` or `{ 42 } * 3`
+                BinOpKind::Sub | // `{ 42 } -5`
+                BinOpKind::Add | // `{ 42 } + 42` (unary plus)
+                BinOpKind::And | // `{ 42 } &&x` (#61475) or `{ 42 } && if x { 1 } else { 0 }`
+                BinOpKind::Or | // `{ 42 } || 42` ("logical or" or closure)
+                BinOpKind::BitOr // `{ 42 } | 42` or `{ 42 } |x| 42`
+            ))) => {
                 // These cases are ambiguous and can't be identified in the parser alone.
                 //
                 // Bitwise AND is left out because guessing intent is hard. We can make
@@ -392,23 +358,21 @@ impl<'a> Parser<'a> {
             // When parsing const expressions, stop parsing when encountering `>`.
             (
                 Some(
-                    AssocOp::ShiftRight
-                    | AssocOp::Greater
-                    | AssocOp::GreaterEqual
-                    | AssocOp::AssignOp(token::BinOpToken::Shr),
+                    AssocOp::Binary(BinOpKind::Shr | BinOpKind::Gt | BinOpKind::Ge)
+                    | AssocOp::AssignOp(BinOpKind::Shr),
                 ),
                 _,
             ) if self.restrictions.contains(Restrictions::CONST_EXPR) => {
                 return None;
             }
-            // When recovering patterns as expressions, stop parsing when encountering an assignment `=`, an alternative `|`, or a range `..`.
+            // When recovering patterns as expressions, stop parsing when encountering an
+            // assignment `=`, an alternative `|`, or a range `..`.
             (
                 Some(
                     AssocOp::Assign
                     | AssocOp::AssignOp(_)
-                    | AssocOp::BitOr
-                    | AssocOp::DotDot
-                    | AssocOp::DotDotEq,
+                    | AssocOp::Binary(BinOpKind::BitOr)
+                    | AssocOp::Range(_),
                 ),
                 _,
             ) if self.restrictions.contains(Restrictions::IS_PAT) => {
@@ -423,7 +387,7 @@ impl<'a> Parser<'a> {
                     incorrect: "and".into(),
                     sub: errors::InvalidLogicalOperatorSub::Conjunction(self.token.span),
                 });
-                (AssocOp::LAnd, span)
+                (AssocOp::Binary(BinOpKind::And), span)
             }
             (None, Some((Ident { name: sym::or, span }, IdentIsRaw::No))) if self.may_recover() => {
                 self.dcx().emit_err(errors::InvalidLogicalOperator {
@@ -431,7 +395,7 @@ impl<'a> Parser<'a> {
                     incorrect: "or".into(),
                     sub: errors::InvalidLogicalOperatorSub::Disjunction(self.token.span),
                 });
-                (AssocOp::LOr, span)
+                (AssocOp::Binary(BinOpKind::Or), span)
             }
             _ => return None,
         };
@@ -449,7 +413,7 @@ impl<'a> Parser<'a> {
         &mut self,
         prec: ExprPrecedence,
         lhs: P<Expr>,
-        op: AssocOp,
+        limits: RangeLimits,
         cur_op_span: Span,
     ) -> PResult<'a, P<Expr>> {
         let rhs = if self.is_at_start_of_range_notation_rhs() {
@@ -465,8 +429,6 @@ impl<'a> Parser<'a> {
         };
         let rhs_span = rhs.as_ref().map_or(cur_op_span, |x| x.span);
         let span = self.mk_expr_sp(&lhs, lhs.span, rhs_span);
-        let limits =
-            if op == AssocOp::DotDot { RangeLimits::HalfOpen } else { RangeLimits::Closed };
         let range = self.mk_range(Some(lhs), rhs, limits);
         Ok(self.mk_expr(span, range))
     }
diff --git a/compiler/rustc_passes/messages.ftl b/compiler/rustc_passes/messages.ftl
index bc43580a7f0..b65430c3480 100644
--- a/compiler/rustc_passes/messages.ftl
+++ b/compiler/rustc_passes/messages.ftl
@@ -725,6 +725,12 @@ passes_target_feature_on_statement =
     .warn = {-passes_previously_accepted}
     .label = {passes_should_be_applied_to_fn.label}
 
+passes_trait_impl_const_stability_mismatch = const stability on the impl does not match the const stability on the trait
+passes_trait_impl_const_stability_mismatch_impl_stable = this impl is (implicitly) stable...
+passes_trait_impl_const_stability_mismatch_impl_unstable = this impl is unstable...
+passes_trait_impl_const_stability_mismatch_trait_stable = ...but the trait is stable
+passes_trait_impl_const_stability_mismatch_trait_unstable = ...but the trait is unstable
+
 passes_trait_impl_const_stable =
     trait implementations cannot be const stable yet
     .note = see issue #67792 <https://github.com/rust-lang/rust/issues/67792> for more information
diff --git a/compiler/rustc_passes/src/errors.rs b/compiler/rustc_passes/src/errors.rs
index 51b5861ee0a..9bb9b2353dc 100644
--- a/compiler/rustc_passes/src/errors.rs
+++ b/compiler/rustc_passes/src/errors.rs
@@ -1553,6 +1553,45 @@ pub(crate) struct TraitImplConstStable {
 }
 
 #[derive(Diagnostic)]
+#[diag(passes_trait_impl_const_stability_mismatch)]
+pub(crate) struct TraitImplConstStabilityMismatch {
+    #[primary_span]
+    pub span: Span,
+    #[subdiagnostic]
+    pub impl_stability: ImplConstStability,
+    #[subdiagnostic]
+    pub trait_stability: TraitConstStability,
+}
+
+#[derive(Subdiagnostic)]
+pub(crate) enum TraitConstStability {
+    #[note(passes_trait_impl_const_stability_mismatch_trait_stable)]
+    Stable {
+        #[primary_span]
+        span: Span,
+    },
+    #[note(passes_trait_impl_const_stability_mismatch_trait_unstable)]
+    Unstable {
+        #[primary_span]
+        span: Span,
+    },
+}
+
+#[derive(Subdiagnostic)]
+pub(crate) enum ImplConstStability {
+    #[note(passes_trait_impl_const_stability_mismatch_impl_stable)]
+    Stable {
+        #[primary_span]
+        span: Span,
+    },
+    #[note(passes_trait_impl_const_stability_mismatch_impl_unstable)]
+    Unstable {
+        #[primary_span]
+        span: Span,
+    },
+}
+
+#[derive(Diagnostic)]
 #[diag(passes_unknown_feature, code = E0635)]
 pub(crate) struct UnknownFeature {
     #[primary_span]
diff --git a/compiler/rustc_passes/src/stability.rs b/compiler/rustc_passes/src/stability.rs
index 8a4bdf3875c..0fcf6a80ec4 100644
--- a/compiler/rustc_passes/src/stability.rs
+++ b/compiler/rustc_passes/src/stability.rs
@@ -313,7 +313,7 @@ impl<'a, 'tcx> Annotator<'a, 'tcx> {
             .map(|(stab, _span)| ConstStability::from_partial(stab, const_stability_indirect));
 
         // If this is a const fn but not annotated with stability markers, see if we can inherit regular stability.
-        if fn_sig.is_some_and(|s| s.header.is_const())  && const_stab.is_none() &&
+        if fn_sig.is_some_and(|s| s.header.is_const()) && const_stab.is_none() &&
             // We only ever inherit unstable features.
             let Some(inherit_regular_stab) =
                 final_stab.filter(|s| s.is_unstable())
@@ -826,24 +826,56 @@ impl<'tcx> Visitor<'tcx> for Checker<'tcx> {
                         }
                     }
 
-                    // `#![feature(const_trait_impl)]` is unstable, so any impl declared stable
-                    // needs to have an error emitted.
                     if features.const_trait_impl()
-                        && self.tcx.is_const_trait_impl(item.owner_id.to_def_id())
-                        && const_stab.is_some_and(|stab| stab.is_const_stable())
+                        && let hir::Constness::Const = constness
                     {
-                        self.tcx.dcx().emit_err(errors::TraitImplConstStable { span: item.span });
+                        let stable_or_implied_stable = match const_stab {
+                            None => true,
+                            Some(stab) if stab.is_const_stable() => {
+                                // `#![feature(const_trait_impl)]` is unstable, so any impl declared stable
+                                // needs to have an error emitted.
+                                // Note: Remove this error once `const_trait_impl` is stabilized
+                                self.tcx
+                                    .dcx()
+                                    .emit_err(errors::TraitImplConstStable { span: item.span });
+                                true
+                            }
+                            Some(_) => false,
+                        };
+
+                        if let Some(trait_id) = t.trait_def_id()
+                            && let Some(const_stab) = self.tcx.lookup_const_stability(trait_id)
+                        {
+                            // the const stability of a trait impl must match the const stability on the trait.
+                            if const_stab.is_const_stable() != stable_or_implied_stable {
+                                let trait_span = self.tcx.def_ident_span(trait_id).unwrap();
+
+                                let impl_stability = if stable_or_implied_stable {
+                                    errors::ImplConstStability::Stable { span: item.span }
+                                } else {
+                                    errors::ImplConstStability::Unstable { span: item.span }
+                                };
+                                let trait_stability = if const_stab.is_const_stable() {
+                                    errors::TraitConstStability::Stable { span: trait_span }
+                                } else {
+                                    errors::TraitConstStability::Unstable { span: trait_span }
+                                };
+
+                                self.tcx.dcx().emit_err(errors::TraitImplConstStabilityMismatch {
+                                    span: item.span,
+                                    impl_stability,
+                                    trait_stability,
+                                });
+                            }
+                        }
                     }
                 }
 
-                match constness {
-                    rustc_hir::Constness::Const => {
-                        if let Some(def_id) = t.trait_def_id() {
-                            // FIXME(const_trait_impl): Improve the span here.
-                            self.tcx.check_const_stability(def_id, t.path.span, t.path.span);
-                        }
-                    }
-                    rustc_hir::Constness::NotConst => {}
+                if let hir::Constness::Const = constness
+                    && let Some(def_id) = t.trait_def_id()
+                {
+                    // FIXME(const_trait_impl): Improve the span here.
+                    self.tcx.check_const_stability(def_id, t.path.span, t.path.span);
                 }
 
                 for impl_item_ref in *items {
diff --git a/compiler/rustc_ty_utils/src/abi.rs b/compiler/rustc_ty_utils/src/abi.rs
index 0ff82f0c256..74ab65b4f58 100644
--- a/compiler/rustc_ty_utils/src/abi.rs
+++ b/compiler/rustc_ty_utils/src/abi.rs
@@ -309,15 +309,11 @@ fn fn_abi_of_fn_ptr<'tcx>(
     query: ty::PseudoCanonicalInput<'tcx, (ty::PolyFnSig<'tcx>, &'tcx ty::List<Ty<'tcx>>)>,
 ) -> Result<&'tcx FnAbi<'tcx, Ty<'tcx>>, &'tcx FnAbiError<'tcx>> {
     let ty::PseudoCanonicalInput { typing_env, value: (sig, extra_args) } = query;
-
-    let cx = LayoutCx::new(tcx, typing_env);
     fn_abi_new_uncached(
-        &cx,
+        &LayoutCx::new(tcx, typing_env),
         tcx.instantiate_bound_regions_with_erased(sig),
         extra_args,
         None,
-        None,
-        false,
     )
 }
 
@@ -326,19 +322,11 @@ fn fn_abi_of_instance<'tcx>(
     query: ty::PseudoCanonicalInput<'tcx, (ty::Instance<'tcx>, &'tcx ty::List<Ty<'tcx>>)>,
 ) -> Result<&'tcx FnAbi<'tcx, Ty<'tcx>>, &'tcx FnAbiError<'tcx>> {
     let ty::PseudoCanonicalInput { typing_env, value: (instance, extra_args) } = query;
-
-    let sig = fn_sig_for_fn_abi(tcx, instance, typing_env);
-
-    let caller_location =
-        instance.def.requires_caller_location(tcx).then(|| tcx.caller_location_ty());
-
     fn_abi_new_uncached(
         &LayoutCx::new(tcx, typing_env),
-        sig,
+        fn_sig_for_fn_abi(tcx, instance, typing_env),
         extra_args,
-        caller_location,
-        Some(instance.def_id()),
-        matches!(instance.def, ty::InstanceKind::Virtual(..)),
+        Some(instance),
     )
 }
 
@@ -547,19 +535,25 @@ fn fn_abi_sanity_check<'tcx>(
     fn_arg_sanity_check(cx, fn_abi, spec_abi, &fn_abi.ret);
 }
 
-// FIXME(eddyb) perhaps group the signature/type-containing (or all of them?)
-// arguments of this method, into a separate `struct`.
-#[tracing::instrument(level = "debug", skip(cx, caller_location, fn_def_id, force_thin_self_ptr))]
+#[tracing::instrument(level = "debug", skip(cx, instance))]
 fn fn_abi_new_uncached<'tcx>(
     cx: &LayoutCx<'tcx>,
     sig: ty::FnSig<'tcx>,
     extra_args: &[Ty<'tcx>],
-    caller_location: Option<Ty<'tcx>>,
-    fn_def_id: Option<DefId>,
-    // FIXME(eddyb) replace this with something typed, like an `enum`.
-    force_thin_self_ptr: bool,
+    instance: Option<ty::Instance<'tcx>>,
 ) -> Result<&'tcx FnAbi<'tcx, Ty<'tcx>>, &'tcx FnAbiError<'tcx>> {
     let tcx = cx.tcx();
+    let (caller_location, determined_fn_def_id, is_virtual_call) = if let Some(instance) = instance
+    {
+        let is_virtual_call = matches!(instance.def, ty::InstanceKind::Virtual(..));
+        (
+            instance.def.requires_caller_location(tcx).then(|| tcx.caller_location_ty()),
+            if is_virtual_call { None } else { Some(instance.def_id()) },
+            is_virtual_call,
+        )
+    } else {
+        (None, None, false)
+    };
     let sig = tcx.normalize_erasing_regions(cx.typing_env, sig);
 
     let conv = conv_from_spec_abi(cx.tcx(), sig.abi, sig.c_variadic);
@@ -568,16 +562,11 @@ fn fn_abi_new_uncached<'tcx>(
     let extra_args = if sig.abi == ExternAbi::RustCall {
         assert!(!sig.c_variadic && extra_args.is_empty());
 
-        if let Some(input) = sig.inputs().last() {
-            if let ty::Tuple(tupled_arguments) = input.kind() {
-                inputs = &sig.inputs()[0..sig.inputs().len() - 1];
-                tupled_arguments
-            } else {
-                bug!(
-                    "argument to function with \"rust-call\" ABI \
-                        is not a tuple"
-                );
-            }
+        if let Some(input) = sig.inputs().last()
+            && let ty::Tuple(tupled_arguments) = input.kind()
+        {
+            inputs = &sig.inputs()[0..sig.inputs().len() - 1];
+            tupled_arguments
         } else {
             bug!(
                 "argument to function with \"rust-call\" ABI \
@@ -590,7 +579,7 @@ fn fn_abi_new_uncached<'tcx>(
     };
 
     let is_drop_in_place =
-        fn_def_id.is_some_and(|def_id| tcx.is_lang_item(def_id, LangItem::DropInPlace));
+        determined_fn_def_id.is_some_and(|def_id| tcx.is_lang_item(def_id, LangItem::DropInPlace));
 
     let arg_of = |ty: Ty<'tcx>, arg_idx: Option<usize>| -> Result<_, &'tcx FnAbiError<'tcx>> {
         let span = tracing::debug_span!("arg_of");
@@ -603,7 +592,7 @@ fn fn_abi_new_uncached<'tcx>(
         });
 
         let layout = cx.layout_of(ty).map_err(|err| &*tcx.arena.alloc(FnAbiError::Layout(*err)))?;
-        let layout = if force_thin_self_ptr && arg_idx == Some(0) {
+        let layout = if is_virtual_call && arg_idx == Some(0) {
             // Don't pass the vtable, it's not an argument of the virtual fn.
             // Instead, pass just the data pointer, but give it the type `*const/mut dyn Trait`
             // or `&/&mut dyn Trait` because this is special-cased elsewhere in codegen
@@ -646,9 +635,22 @@ fn fn_abi_new_uncached<'tcx>(
         c_variadic: sig.c_variadic,
         fixed_count: inputs.len() as u32,
         conv,
-        can_unwind: fn_can_unwind(cx.tcx(), fn_def_id, sig.abi),
+        can_unwind: fn_can_unwind(
+            tcx,
+            // Since `#[rustc_nounwind]` can change unwinding, we cannot infer unwinding by `fn_def_id` for a virtual call.
+            determined_fn_def_id,
+            sig.abi,
+        ),
     };
-    fn_abi_adjust_for_abi(cx, &mut fn_abi, sig.abi, fn_def_id);
+    fn_abi_adjust_for_abi(
+        cx,
+        &mut fn_abi,
+        sig.abi,
+        // If this is a virtual call, we cannot pass the `fn_def_id`, as it might call other
+        // functions from vtable. Internally, `deduced_param_attrs` attempts to infer attributes by
+        // visit the function body.
+        determined_fn_def_id,
+    );
     debug!("fn_abi_new_uncached = {:?}", fn_abi);
     fn_abi_sanity_check(cx, &fn_abi, sig.abi);
     Ok(tcx.arena.alloc(fn_abi))
diff --git a/library/core/src/ops/arith.rs b/library/core/src/ops/arith.rs
index fe7ff2d9ede..54d79beca95 100644
--- a/library/core/src/ops/arith.rs
+++ b/library/core/src/ops/arith.rs
@@ -96,6 +96,7 @@ pub trait Add<Rhs = Self> {
 macro_rules! add_impl {
     ($($t:ty)*) => ($(
         #[stable(feature = "rust1", since = "1.0.0")]
+        #[rustc_const_unstable(feature = "const_ops", issue = "90080")]
         impl const Add for $t {
             type Output = $t;
 
diff --git a/library/core/src/ops/deref.rs b/library/core/src/ops/deref.rs
index 11490ea2bfc..e74f5443ac2 100644
--- a/library/core/src/ops/deref.rs
+++ b/library/core/src/ops/deref.rs
@@ -150,6 +150,7 @@ pub trait Deref {
 }
 
 #[stable(feature = "rust1", since = "1.0.0")]
+#[rustc_const_unstable(feature = "const_deref", issue = "88955")]
 impl<T: ?Sized> const Deref for &T {
     type Target = T;
 
@@ -163,6 +164,7 @@ impl<T: ?Sized> const Deref for &T {
 impl<T: ?Sized> !DerefMut for &T {}
 
 #[stable(feature = "rust1", since = "1.0.0")]
+#[rustc_const_unstable(feature = "const_deref", issue = "88955")]
 impl<T: ?Sized> const Deref for &mut T {
     type Target = T;
 
@@ -273,6 +275,7 @@ pub trait DerefMut: ~const Deref {
 }
 
 #[stable(feature = "rust1", since = "1.0.0")]
+#[rustc_const_unstable(feature = "const_deref", issue = "88955")]
 impl<T: ?Sized> const DerefMut for &mut T {
     fn deref_mut(&mut self) -> &mut T {
         *self
diff --git a/library/core/src/ops/range.rs b/library/core/src/ops/range.rs
index 5580faefacc..e0c442e5292 100644
--- a/library/core/src/ops/range.rs
+++ b/library/core/src/ops/range.rs
@@ -771,13 +771,11 @@ pub trait RangeBounds<T: ?Sized> {
     /// # Examples
     ///
     /// ```
-    /// # fn main() {
     /// use std::ops::Bound::*;
     /// use std::ops::RangeBounds;
     ///
     /// assert_eq!((..10).start_bound(), Unbounded);
     /// assert_eq!((3..10).start_bound(), Included(&3));
-    /// # }
     /// ```
     #[stable(feature = "collections_range", since = "1.28.0")]
     fn start_bound(&self) -> Bound<&T>;
@@ -789,13 +787,11 @@ pub trait RangeBounds<T: ?Sized> {
     /// # Examples
     ///
     /// ```
-    /// # fn main() {
     /// use std::ops::Bound::*;
     /// use std::ops::RangeBounds;
     ///
     /// assert_eq!((3..).end_bound(), Unbounded);
     /// assert_eq!((3..10).end_bound(), Excluded(&10));
-    /// # }
     /// ```
     #[stable(feature = "collections_range", since = "1.28.0")]
     fn end_bound(&self) -> Bound<&T>;
@@ -829,6 +825,71 @@ pub trait RangeBounds<T: ?Sized> {
             Unbounded => true,
         })
     }
+
+    /// Returns `true` if the range contains no items.
+    /// One-sided ranges (`RangeFrom`, etc) always return `true`.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// #![feature(range_bounds_is_empty)]
+    /// use std::ops::RangeBounds;
+    ///
+    /// assert!(!(3..).is_empty());
+    /// assert!(!(..2).is_empty());
+    /// assert!(!RangeBounds::is_empty(&(3..5)));
+    /// assert!( RangeBounds::is_empty(&(3..3)));
+    /// assert!( RangeBounds::is_empty(&(3..2)));
+    /// ```
+    ///
+    /// The range is empty if either side is incomparable:
+    ///
+    /// ```
+    /// #![feature(range_bounds_is_empty)]
+    /// use std::ops::RangeBounds;
+    ///
+    /// assert!(!RangeBounds::is_empty(&(3.0..5.0)));
+    /// assert!( RangeBounds::is_empty(&(3.0..f32::NAN)));
+    /// assert!( RangeBounds::is_empty(&(f32::NAN..5.0)));
+    /// ```
+    ///
+    /// But never empty is either side is unbounded:
+    ///
+    /// ```
+    /// #![feature(range_bounds_is_empty)]
+    /// use std::ops::RangeBounds;
+    ///
+    /// assert!(!(..0).is_empty());
+    /// assert!(!(i32::MAX..).is_empty());
+    /// assert!(!RangeBounds::<u8>::is_empty(&(..)));
+    /// ```
+    ///
+    /// `(Excluded(a), Excluded(b))` is only empty if `a >= b`:
+    ///
+    /// ```
+    /// #![feature(range_bounds_is_empty)]
+    /// use std::ops::Bound::*;
+    /// use std::ops::RangeBounds;
+    ///
+    /// assert!(!(Excluded(1), Excluded(3)).is_empty());
+    /// assert!(!(Excluded(1), Excluded(2)).is_empty());
+    /// assert!( (Excluded(1), Excluded(1)).is_empty());
+    /// assert!( (Excluded(2), Excluded(1)).is_empty());
+    /// assert!( (Excluded(3), Excluded(1)).is_empty());
+    /// ```
+    #[unstable(feature = "range_bounds_is_empty", issue = "137300")]
+    fn is_empty(&self) -> bool
+    where
+        T: PartialOrd,
+    {
+        !match (self.start_bound(), self.end_bound()) {
+            (Unbounded, _) | (_, Unbounded) => true,
+            (Included(start), Excluded(end))
+            | (Excluded(start), Included(end))
+            | (Excluded(start), Excluded(end)) => start < end,
+            (Included(start), Included(end)) => start <= end,
+        }
+    }
 }
 
 /// Used to convert a range into start and end bounds, consuming the
@@ -845,7 +906,6 @@ pub trait IntoBounds<T>: RangeBounds<T> {
     ///
     /// ```
     /// #![feature(range_into_bounds)]
-    ///
     /// use std::ops::Bound::*;
     /// use std::ops::IntoBounds;
     ///
@@ -853,6 +913,76 @@ pub trait IntoBounds<T>: RangeBounds<T> {
     /// assert_eq!((..=7).into_bounds(), (Unbounded, Included(7)));
     /// ```
     fn into_bounds(self) -> (Bound<T>, Bound<T>);
+
+    /// Compute the intersection of  `self` and `other`.
+    ///
+    /// # Examples
+    ///
+    /// ```
+    /// #![feature(range_into_bounds)]
+    /// use std::ops::Bound::*;
+    /// use std::ops::IntoBounds;
+    ///
+    /// assert_eq!((3..).intersect(..5), (Included(3), Excluded(5)));
+    /// assert_eq!((-12..387).intersect(0..256), (Included(0), Excluded(256)));
+    /// assert_eq!((1..5).intersect(..), (Included(1), Excluded(5)));
+    /// assert_eq!((1..=9).intersect(0..10), (Included(1), Included(9)));
+    /// assert_eq!((7..=13).intersect(8..13), (Included(8), Excluded(13)));
+    /// ```
+    ///
+    /// Combine with `is_empty` to determine if two ranges overlap.
+    ///
+    /// ```
+    /// #![feature(range_into_bounds)]
+    /// #![feature(range_bounds_is_empty)]
+    /// use std::ops::{RangeBounds, IntoBounds};
+    ///
+    /// assert!(!(3..).intersect(..5).is_empty());
+    /// assert!(!(-12..387).intersect(0..256).is_empty());
+    /// assert!((1..5).intersect(6..).is_empty());
+    /// ```
+    fn intersect<R>(self, other: R) -> (Bound<T>, Bound<T>)
+    where
+        Self: Sized,
+        T: Ord,
+        R: Sized + IntoBounds<T>,
+    {
+        let (self_start, self_end) = IntoBounds::into_bounds(self);
+        let (other_start, other_end) = IntoBounds::into_bounds(other);
+
+        let start = match (self_start, other_start) {
+            (Included(a), Included(b)) => Included(Ord::max(a, b)),
+            (Excluded(a), Excluded(b)) => Excluded(Ord::max(a, b)),
+            (Unbounded, Unbounded) => Unbounded,
+
+            (x, Unbounded) | (Unbounded, x) => x,
+
+            (Included(i), Excluded(e)) | (Excluded(e), Included(i)) => {
+                if i > e {
+                    Included(i)
+                } else {
+                    Excluded(e)
+                }
+            }
+        };
+        let end = match (self_end, other_end) {
+            (Included(a), Included(b)) => Included(Ord::min(a, b)),
+            (Excluded(a), Excluded(b)) => Excluded(Ord::min(a, b)),
+            (Unbounded, Unbounded) => Unbounded,
+
+            (x, Unbounded) | (Unbounded, x) => x,
+
+            (Included(i), Excluded(e)) | (Excluded(e), Included(i)) => {
+                if i < e {
+                    Included(i)
+                } else {
+                    Excluded(e)
+                }
+            }
+        };
+
+        (start, end)
+    }
 }
 
 use self::Bound::{Excluded, Included, Unbounded};
diff --git a/library/std/src/thread/mod.rs b/library/std/src/thread/mod.rs
index 59b395336f2..3f3ba02361c 100644
--- a/library/std/src/thread/mod.rs
+++ b/library/std/src/thread/mod.rs
@@ -1739,7 +1739,16 @@ struct JoinInner<'scope, T> {
 impl<'scope, T> JoinInner<'scope, T> {
     fn join(mut self) -> Result<T> {
         self.native.join();
-        Arc::get_mut(&mut self.packet).unwrap().result.get_mut().take().unwrap()
+        Arc::get_mut(&mut self.packet)
+            // FIXME(fuzzypixelz): returning an error instead of panicking here
+            // would require updating the documentation of
+            // `std::thread::Result`; currently we can return `Err` if and only
+            // if the thread had panicked.
+            .expect("threads should not terminate unexpectedly")
+            .result
+            .get_mut()
+            .take()
+            .unwrap()
     }
 }
 
diff --git a/src/bootstrap/src/core/build_steps/test.rs b/src/bootstrap/src/core/build_steps/test.rs
index dfcf26cedd6..709711d9d34 100644
--- a/src/bootstrap/src/core/build_steps/test.rs
+++ b/src/bootstrap/src/core/build_steps/test.rs
@@ -1793,14 +1793,18 @@ NOTE: if you're sure you want to do this, please open an issue as to why. In the
         cmd.arg("--src-root").arg(&builder.src);
         cmd.arg("--src-test-suite-root").arg(builder.src.join("tests").join(suite));
 
-        cmd.arg("--build-base").arg(testdir(builder, compiler.host).join(suite));
+        // N.B. it's important to distinguish between the *root* build directory, the *host* build
+        // directory immediately under the root build directory, and the test-suite-specific build
+        // directory.
+        cmd.arg("--build-root").arg(&builder.out);
+        cmd.arg("--build-test-suite-root").arg(testdir(builder, compiler.host).join(suite));
 
         // When top stage is 0, that means that we're testing an externally provided compiler.
         // In that case we need to use its specific sysroot for tests to pass.
         let sysroot = if builder.top_stage == 0 {
             builder.initial_sysroot.clone()
         } else {
-            builder.sysroot(compiler).to_path_buf()
+            builder.sysroot(compiler)
         };
 
         cmd.arg("--sysroot-base").arg(sysroot);
diff --git a/src/ci/github-actions/jobs.yml b/src/ci/github-actions/jobs.yml
index 279c7649ca3..64e64867de2 100644
--- a/src/ci/github-actions/jobs.yml
+++ b/src/ci/github-actions/jobs.yml
@@ -474,13 +474,13 @@ auto:
     env:
       RUST_CONFIGURE_ARGS: --build=i686-pc-windows-msvc  --enable-sanitizers
       SCRIPT: make ci-msvc-py
-    <<: *job-windows-8c
+    <<: *job-windows
 
   - name: i686-msvc-2
     env:
       RUST_CONFIGURE_ARGS: --build=i686-pc-windows-msvc --enable-sanitizers
       SCRIPT: make ci-msvc-ps1
-    <<: *job-windows-8c
+    <<: *job-windows
 
   # x86_64-msvc-ext is split into multiple jobs to run tests in parallel.
   - name: x86_64-msvc-ext1
@@ -595,7 +595,7 @@ auto:
       SCRIPT: python x.py dist bootstrap --include-default-paths
       DIST_REQUIRE_ALL_TOOLS: 1
       CODEGEN_BACKENDS: llvm,cranelift
-    <<: *job-windows-8c
+    <<: *job-windows
 
   - name: dist-aarch64-msvc
     env:
diff --git a/src/tools/clippy/clippy_lints/src/operators/float_equality_without_abs.rs b/src/tools/clippy/clippy_lints/src/operators/float_equality_without_abs.rs
index 34f7dbea84e..74e0a6333db 100644
--- a/src/tools/clippy/clippy_lints/src/operators/float_equality_without_abs.rs
+++ b/src/tools/clippy/clippy_lints/src/operators/float_equality_without_abs.rs
@@ -50,7 +50,7 @@ pub(crate) fn check<'tcx>(
         // format the suggestion
         let suggestion = format!(
             "{}.abs()",
-            sugg::make_assoc(AssocOp::Subtract, &sug_l, &sug_r).maybe_par()
+            sugg::make_assoc(AssocOp::Binary(BinOpKind::Sub), &sug_l, &sug_r).maybe_par()
         );
         // spans the lint
         span_lint_and_then(
diff --git a/src/tools/clippy/clippy_utils/src/lib.rs b/src/tools/clippy/clippy_utils/src/lib.rs
index 7fc25e3617d..f7266ef4d4e 100644
--- a/src/tools/clippy/clippy_utils/src/lib.rs
+++ b/src/tools/clippy/clippy_utils/src/lib.rs
@@ -31,7 +31,6 @@
 // (Currently there is no way to opt into sysroot crates without `extern crate`.)
 extern crate rustc_abi;
 extern crate rustc_ast;
-extern crate rustc_ast_pretty;
 extern crate rustc_attr_parsing;
 extern crate rustc_const_eval;
 extern crate rustc_data_structures;
diff --git a/src/tools/clippy/clippy_utils/src/sugg.rs b/src/tools/clippy/clippy_utils/src/sugg.rs
index d5e0e2e3436..4a9ab17d4a6 100644
--- a/src/tools/clippy/clippy_utils/src/sugg.rs
+++ b/src/tools/clippy/clippy_utils/src/sugg.rs
@@ -5,8 +5,7 @@ use crate::source::{snippet, snippet_opt, snippet_with_applicability, snippet_wi
 use crate::ty::expr_sig;
 use crate::{get_parent_expr_for_hir, higher};
 use rustc_ast::util::parser::AssocOp;
-use rustc_ast::{ast, token};
-use rustc_ast_pretty::pprust::token_kind_to_string;
+use rustc_ast::ast;
 use rustc_errors::Applicability;
 use rustc_hir as hir;
 use rustc_hir::{Closure, ExprKind, HirId, MutTy, TyKind};
@@ -114,10 +113,7 @@ impl<'a> Sugg<'a> {
     /// function variants of `Sugg`, since these use different snippet functions.
     fn hir_from_snippet(expr: &hir::Expr<'_>, mut get_snippet: impl FnMut(Span) -> Cow<'a, str>) -> Self {
         if let Some(range) = higher::Range::hir(expr) {
-            let op = match range.limits {
-                ast::RangeLimits::HalfOpen => AssocOp::DotDot,
-                ast::RangeLimits::Closed => AssocOp::DotDotEq,
-            };
+            let op = AssocOp::Range(range.limits);
             let start = range.start.map_or("".into(), |expr| get_snippet(expr.span));
             let end = range.end.map_or("".into(), |expr| get_snippet(expr.span));
 
@@ -158,16 +154,16 @@ impl<'a> Sugg<'a> {
                 Sugg::BinOp(AssocOp::Assign, get_snippet(lhs.span), get_snippet(rhs.span))
             },
             ExprKind::AssignOp(op, lhs, rhs) => {
-                Sugg::BinOp(hirbinop2assignop(op), get_snippet(lhs.span), get_snippet(rhs.span))
+                Sugg::BinOp(AssocOp::AssignOp(op.node), get_snippet(lhs.span), get_snippet(rhs.span))
             },
             ExprKind::Binary(op, lhs, rhs) => Sugg::BinOp(
-                AssocOp::from_ast_binop(op.node),
+                AssocOp::Binary(op.node),
                 get_snippet(lhs.span),
                 get_snippet(rhs.span),
             ),
             ExprKind::Cast(lhs, ty) |
             //FIXME(chenyukang), remove this after type ascription is removed from AST
-            ExprKind::Type(lhs, ty) => Sugg::BinOp(AssocOp::As, get_snippet(lhs.span), get_snippet(ty.span)),
+            ExprKind::Type(lhs, ty) => Sugg::BinOp(AssocOp::Cast, get_snippet(lhs.span), get_snippet(ty.span)),
         }
     }
 
@@ -179,8 +175,6 @@ impl<'a> Sugg<'a> {
         ctxt: SyntaxContext,
         app: &mut Applicability,
     ) -> Self {
-        use rustc_ast::ast::RangeLimits;
-
         let mut snippet = |span: Span| snippet_with_context(cx, span, ctxt, default, app).0;
 
         match expr.kind {
@@ -229,13 +223,8 @@ impl<'a> Sugg<'a> {
             | ast::ExprKind::Err(_)
             | ast::ExprKind::Dummy
             | ast::ExprKind::UnsafeBinderCast(..) => Sugg::NonParen(snippet(expr.span)),
-            ast::ExprKind::Range(ref lhs, ref rhs, RangeLimits::HalfOpen) => Sugg::BinOp(
-                AssocOp::DotDot,
-                lhs.as_ref().map_or("".into(), |lhs| snippet(lhs.span)),
-                rhs.as_ref().map_or("".into(), |rhs| snippet(rhs.span)),
-            ),
-            ast::ExprKind::Range(ref lhs, ref rhs, RangeLimits::Closed) => Sugg::BinOp(
-                AssocOp::DotDotEq,
+            ast::ExprKind::Range(ref lhs, ref rhs, limits) => Sugg::BinOp(
+                AssocOp::Range(limits),
                 lhs.as_ref().map_or("".into(), |lhs| snippet(lhs.span)),
                 rhs.as_ref().map_or("".into(), |rhs| snippet(rhs.span)),
             ),
@@ -245,19 +234,19 @@ impl<'a> Sugg<'a> {
                 snippet(rhs.span),
             ),
             ast::ExprKind::AssignOp(op, ref lhs, ref rhs) => Sugg::BinOp(
-                astbinop2assignop(op),
+                AssocOp::AssignOp(op.node),
                 snippet(lhs.span),
                 snippet(rhs.span),
             ),
             ast::ExprKind::Binary(op, ref lhs, ref rhs) => Sugg::BinOp(
-                AssocOp::from_ast_binop(op.node),
+                AssocOp::Binary(op.node),
                 snippet(lhs.span),
                 snippet(rhs.span),
             ),
             ast::ExprKind::Cast(ref lhs, ref ty) |
             //FIXME(chenyukang), remove this after type ascription is removed from AST
             ast::ExprKind::Type(ref lhs, ref ty) => Sugg::BinOp(
-                AssocOp::As,
+                AssocOp::Cast,
                 snippet(lhs.span),
                 snippet(ty.span),
             ),
@@ -276,7 +265,7 @@ impl<'a> Sugg<'a> {
 
     /// Convenience method to create the `<lhs> as <rhs>` suggestion.
     pub fn as_ty<R: Display>(self, rhs: R) -> Sugg<'static> {
-        make_assoc(AssocOp::As, &self, &Sugg::NonParen(rhs.to_string().into()))
+        make_assoc(AssocOp::Cast, &self, &Sugg::NonParen(rhs.to_string().into()))
     }
 
     /// Convenience method to create the `&<expr>` suggestion.
@@ -327,11 +316,8 @@ impl<'a> Sugg<'a> {
 
     /// Convenience method to create the `<lhs>..<rhs>` or `<lhs>...<rhs>`
     /// suggestion.
-    pub fn range(self, end: &Self, limit: ast::RangeLimits) -> Sugg<'static> {
-        match limit {
-            ast::RangeLimits::HalfOpen => make_assoc(AssocOp::DotDot, &self, end),
-            ast::RangeLimits::Closed => make_assoc(AssocOp::DotDotEq, &self, end),
-        }
+    pub fn range(self, end: &Self, limits: ast::RangeLimits) -> Sugg<'static> {
+        make_assoc(AssocOp::Range(limits), &self, end)
     }
 
     /// Adds parentheses to any expression that might need them. Suitable to the
@@ -367,33 +353,11 @@ impl<'a> Sugg<'a> {
 /// Generates a string from the operator and both sides.
 fn binop_to_string(op: AssocOp, lhs: &str, rhs: &str) -> String {
     match op {
-        AssocOp::Add
-        | AssocOp::Subtract
-        | AssocOp::Multiply
-        | AssocOp::Divide
-        | AssocOp::Modulus
-        | AssocOp::LAnd
-        | AssocOp::LOr
-        | AssocOp::BitXor
-        | AssocOp::BitAnd
-        | AssocOp::BitOr
-        | AssocOp::ShiftLeft
-        | AssocOp::ShiftRight
-        | AssocOp::Equal
-        | AssocOp::Less
-        | AssocOp::LessEqual
-        | AssocOp::NotEqual
-        | AssocOp::Greater
-        | AssocOp::GreaterEqual => {
-            format!("{lhs} {} {rhs}", op.to_ast_binop().expect("Those are AST ops").as_str())
-        },
+        AssocOp::Binary(op) => format!("{lhs} {} {rhs}", op.as_str()),
         AssocOp::Assign => format!("{lhs} = {rhs}"),
-        AssocOp::AssignOp(op) => {
-            format!("{lhs} {}= {rhs}", token_kind_to_string(&token::BinOp(op)))
-        },
-        AssocOp::As => format!("{lhs} as {rhs}"),
-        AssocOp::DotDot => format!("{lhs}..{rhs}"),
-        AssocOp::DotDotEq => format!("{lhs}..={rhs}"),
+        AssocOp::AssignOp(op) => format!("{lhs} {}= {rhs}", op.as_str()),
+        AssocOp::Cast => format!("{lhs} as {rhs}"),
+        AssocOp::Range(limits) => format!("{lhs}{}{rhs}", limits.as_str()),
     }
 }
 
@@ -468,7 +432,7 @@ impl Neg for Sugg<'_> {
     type Output = Sugg<'static>;
     fn neg(self) -> Sugg<'static> {
         match &self {
-            Self::BinOp(AssocOp::As, ..) => Sugg::MaybeParen(format!("-({self})").into()),
+            Self::BinOp(AssocOp::Cast, ..) => Sugg::MaybeParen(format!("-({self})").into()),
             _ => make_unop("-", self),
         }
     }
@@ -477,16 +441,17 @@ impl Neg for Sugg<'_> {
 impl<'a> Not for Sugg<'a> {
     type Output = Sugg<'a>;
     fn not(self) -> Sugg<'a> {
-        use AssocOp::{Equal, Greater, GreaterEqual, Less, LessEqual, NotEqual};
+        use AssocOp::Binary;
+        use ast::BinOpKind::{Eq, Gt, Ge, Lt, Le, Ne};
 
         if let Sugg::BinOp(op, lhs, rhs) = self {
             let to_op = match op {
-                Equal => NotEqual,
-                NotEqual => Equal,
-                Less => GreaterEqual,
-                GreaterEqual => Less,
-                Greater => LessEqual,
-                LessEqual => Greater,
+                Binary(Eq) => Binary(Ne),
+                Binary(Ne) => Binary(Eq),
+                Binary(Lt) => Binary(Ge),
+                Binary(Ge) => Binary(Lt),
+                Binary(Gt) => Binary(Le),
+                Binary(Le) => Binary(Gt),
                 _ => return make_unop("!", Sugg::BinOp(op, lhs, rhs)),
             };
             Sugg::BinOp(to_op, lhs, rhs)
@@ -538,7 +503,7 @@ pub fn make_unop(op: &str, expr: Sugg<'_>) -> Sugg<'static> {
 pub fn make_assoc(op: AssocOp, lhs: &Sugg<'_>, rhs: &Sugg<'_>) -> Sugg<'static> {
     /// Returns `true` if the operator is a shift operator `<<` or `>>`.
     fn is_shift(op: AssocOp) -> bool {
-        matches!(op, AssocOp::ShiftLeft | AssocOp::ShiftRight)
+        matches!(op, AssocOp::Binary(ast::BinOpKind::Shl | ast::BinOpKind::Shr))
     }
 
     /// Returns `true` if the operator is an arithmetic operator
@@ -546,7 +511,13 @@ pub fn make_assoc(op: AssocOp, lhs: &Sugg<'_>, rhs: &Sugg<'_>) -> Sugg<'static>
     fn is_arith(op: AssocOp) -> bool {
         matches!(
             op,
-            AssocOp::Add | AssocOp::Subtract | AssocOp::Multiply | AssocOp::Divide | AssocOp::Modulus
+            AssocOp::Binary(
+                ast::BinOpKind::Add
+                | ast::BinOpKind::Sub
+                | ast::BinOpKind::Mul
+                | ast::BinOpKind::Div
+                | ast::BinOpKind::Rem
+            )
         )
     }
 
@@ -578,9 +549,9 @@ pub fn make_assoc(op: AssocOp, lhs: &Sugg<'_>, rhs: &Sugg<'_>) -> Sugg<'static>
     Sugg::BinOp(op, lhs.into(), rhs.into())
 }
 
-/// Convenience wrapper around `make_assoc` and `AssocOp::from_ast_binop`.
+/// Convenience wrapper around `make_assoc` and `AssocOp::Binary`.
 pub fn make_binop(op: ast::BinOpKind, lhs: &Sugg<'_>, rhs: &Sugg<'_>) -> Sugg<'static> {
-    make_assoc(AssocOp::from_ast_binop(op), lhs, rhs)
+    make_assoc(AssocOp::Binary(op), lhs, rhs)
 }
 
 #[derive(PartialEq, Eq, Clone, Copy)]
@@ -605,69 +576,19 @@ enum Associativity {
 /// associative.
 #[must_use]
 fn associativity(op: AssocOp) -> Associativity {
-    use rustc_ast::util::parser::AssocOp::{
-        Add, As, Assign, AssignOp, BitAnd, BitOr, BitXor, Divide, DotDot, DotDotEq, Equal, Greater, GreaterEqual, LAnd,
-        LOr, Less, LessEqual, Modulus, Multiply, NotEqual, ShiftLeft, ShiftRight, Subtract,
+    use rustc_ast::util::parser::AssocOp::{Assign, AssignOp, Binary, Cast, Range};
+    use ast::BinOpKind::{
+        Add, BitAnd, BitOr, BitXor, Div, Eq, Gt, Ge, And, Or, Lt, Le, Rem, Mul, Ne, Shl, Shr, Sub,
     };
 
     match op {
         Assign | AssignOp(_) => Associativity::Right,
-        Add | BitAnd | BitOr | BitXor | LAnd | LOr | Multiply | As => Associativity::Both,
-        Divide | Equal | Greater | GreaterEqual | Less | LessEqual | Modulus | NotEqual | ShiftLeft | ShiftRight
-        | Subtract => Associativity::Left,
-        DotDot | DotDotEq => Associativity::None,
+        Binary(Add | BitAnd | BitOr | BitXor | And | Or | Mul) | Cast => Associativity::Both,
+        Binary(Div | Eq | Gt | Ge | Lt | Le | Rem | Ne | Shl | Shr | Sub) => Associativity::Left,
+        Range(_) => Associativity::None,
     }
 }
 
-/// Converts a `hir::BinOp` to the corresponding assigning binary operator.
-fn hirbinop2assignop(op: hir::BinOp) -> AssocOp {
-    use rustc_ast::token::BinOpToken::{And, Caret, Minus, Or, Percent, Plus, Shl, Shr, Slash, Star};
-
-    AssocOp::AssignOp(match op.node {
-        hir::BinOpKind::Add => Plus,
-        hir::BinOpKind::BitAnd => And,
-        hir::BinOpKind::BitOr => Or,
-        hir::BinOpKind::BitXor => Caret,
-        hir::BinOpKind::Div => Slash,
-        hir::BinOpKind::Mul => Star,
-        hir::BinOpKind::Rem => Percent,
-        hir::BinOpKind::Shl => Shl,
-        hir::BinOpKind::Shr => Shr,
-        hir::BinOpKind::Sub => Minus,
-
-        hir::BinOpKind::And
-        | hir::BinOpKind::Eq
-        | hir::BinOpKind::Ge
-        | hir::BinOpKind::Gt
-        | hir::BinOpKind::Le
-        | hir::BinOpKind::Lt
-        | hir::BinOpKind::Ne
-        | hir::BinOpKind::Or => panic!("This operator does not exist"),
-    })
-}
-
-/// Converts an `ast::BinOp` to the corresponding assigning binary operator.
-fn astbinop2assignop(op: ast::BinOp) -> AssocOp {
-    use rustc_ast::ast::BinOpKind::{
-        Add, And, BitAnd, BitOr, BitXor, Div, Eq, Ge, Gt, Le, Lt, Mul, Ne, Or, Rem, Shl, Shr, Sub,
-    };
-    use rustc_ast::token::BinOpToken;
-
-    AssocOp::AssignOp(match op.node {
-        Add => BinOpToken::Plus,
-        BitAnd => BinOpToken::And,
-        BitOr => BinOpToken::Or,
-        BitXor => BinOpToken::Caret,
-        Div => BinOpToken::Slash,
-        Mul => BinOpToken::Star,
-        Rem => BinOpToken::Percent,
-        Shl => BinOpToken::Shl,
-        Shr => BinOpToken::Shr,
-        Sub => BinOpToken::Minus,
-        And | Eq | Ge | Gt | Le | Lt | Ne | Or => panic!("This operator does not exist"),
-    })
-}
-
 /// Returns the indentation before `span` if there are nothing but `[ \t]`
 /// before it on its line.
 fn indentation<T: LintContext>(cx: &T, span: Span) -> Option<String> {
diff --git a/src/tools/compiletest/src/common.rs b/src/tools/compiletest/src/common.rs
index 98ab7adf5a7..b302c6a49f5 100644
--- a/src/tools/compiletest/src/common.rs
+++ b/src/tools/compiletest/src/common.rs
@@ -220,8 +220,10 @@ pub struct Config {
     /// The directory containing the test suite sources. Must be a subdirectory of `src_root`.
     pub src_test_suite_root: PathBuf,
 
-    /// The directory where programs should be built
-    pub build_base: PathBuf,
+    /// Root build directory (e.g. `build/`).
+    pub build_root: PathBuf,
+    /// Test suite specific build directory (e.g. `build/host/test/ui/`).
+    pub build_test_suite_root: PathBuf,
 
     /// The directory containing the compiler sysroot
     pub sysroot_base: PathBuf,
@@ -347,7 +349,7 @@ pub struct Config {
 
     /// If true, this will generate a coverage file with UI test files that run `MachineApplicable`
     /// diagnostics but are missing `run-rustfix` annotations. The generated coverage file is
-    /// created in `/<build_base>/rustfix_missing_coverage.txt`
+    /// created in `<test_suite_build_root>/rustfix_missing_coverage.txt`
     pub rustfix_coverage: bool,
 
     /// whether to run `tidy` (html-tidy) when a rustdoc test fails
@@ -812,12 +814,16 @@ pub const UI_STDERR_16: &str = "16bit.stderr";
 pub const UI_COVERAGE: &str = "coverage";
 pub const UI_COVERAGE_MAP: &str = "cov-map";
 
-/// Absolute path to the directory where all output for all tests in the given
-/// `relative_dir` group should reside. Example:
-///   /path/to/build/host-tuple/test/ui/relative/
+/// Absolute path to the directory where all output for all tests in the given `relative_dir` group
+/// should reside. Example:
+///
+/// ```text
+/// /path/to/build/host-tuple/test/ui/relative/
+/// ```
+///
 /// This is created early when tests are collected to avoid race conditions.
 pub fn output_relative_path(config: &Config, relative_dir: &Path) -> PathBuf {
-    config.build_base.join(relative_dir)
+    config.build_test_suite_root.join(relative_dir)
 }
 
 /// Generates a unique name for the test, such as `testname.revision.mode`.
diff --git a/src/tools/compiletest/src/header.rs b/src/tools/compiletest/src/header.rs
index 3bdf37a1f29..53ee901b8bc 100644
--- a/src/tools/compiletest/src/header.rs
+++ b/src/tools/compiletest/src/header.rs
@@ -1070,10 +1070,11 @@ impl Config {
     }
 }
 
+// FIXME(jieyouxu): fix some of these variable names to more accurately reflect what they do.
 fn expand_variables(mut value: String, config: &Config) -> String {
     const CWD: &str = "{{cwd}}";
     const SRC_BASE: &str = "{{src-base}}";
-    const BUILD_BASE: &str = "{{build-base}}";
+    const TEST_SUITE_BUILD_BASE: &str = "{{build-base}}";
     const RUST_SRC_BASE: &str = "{{rust-src-base}}";
     const SYSROOT_BASE: &str = "{{sysroot-base}}";
     const TARGET_LINKER: &str = "{{target-linker}}";
@@ -1088,12 +1089,13 @@ fn expand_variables(mut value: String, config: &Config) -> String {
         value = value.replace(SRC_BASE, &config.src_test_suite_root.to_str().unwrap());
     }
 
-    if value.contains(BUILD_BASE) {
-        value = value.replace(BUILD_BASE, &config.build_base.to_string_lossy());
+    if value.contains(TEST_SUITE_BUILD_BASE) {
+        value =
+            value.replace(TEST_SUITE_BUILD_BASE, &config.build_test_suite_root.to_str().unwrap());
     }
 
     if value.contains(SYSROOT_BASE) {
-        value = value.replace(SYSROOT_BASE, &config.sysroot_base.to_string_lossy());
+        value = value.replace(SYSROOT_BASE, &config.sysroot_base.to_str().unwrap());
     }
 
     if value.contains(TARGET_LINKER) {
diff --git a/src/tools/compiletest/src/header/tests.rs b/src/tools/compiletest/src/header/tests.rs
index 84c1cb0963e..d7079fdeee6 100644
--- a/src/tools/compiletest/src/header/tests.rs
+++ b/src/tools/compiletest/src/header/tests.rs
@@ -155,7 +155,8 @@ impl ConfigBuilder {
             "--jsondocck-path=",
             "--src-root=",
             "--src-test-suite-root=",
-            "--build-base=",
+            "--build-root=",
+            "--build-test-suite-root=",
             "--sysroot-base=",
             "--cc=c",
             "--cxx=c++",
diff --git a/src/tools/compiletest/src/lib.rs b/src/tools/compiletest/src/lib.rs
index 97982170138..9dff7047bc4 100644
--- a/src/tools/compiletest/src/lib.rs
+++ b/src/tools/compiletest/src/lib.rs
@@ -63,7 +63,8 @@ pub fn parse_config(args: Vec<String>) -> Config {
         .optopt("", "llvm-filecheck", "path to LLVM's FileCheck binary", "DIR")
         .reqopt("", "src-root", "directory containing sources", "PATH")
         .reqopt("", "src-test-suite-root", "directory containing test suite sources", "PATH")
-        .reqopt("", "build-base", "directory to deposit test outputs", "PATH")
+        .reqopt("", "build-root", "path to root build directory", "PATH")
+        .reqopt("", "build-test-suite-root", "path to test suite specific build directory", "PATH")
         .reqopt("", "sysroot-base", "directory containing the compiler sysroot", "PATH")
         .reqopt("", "stage", "stage number under test", "N")
         .reqopt("", "stage-id", "the target-stage identifier", "stageN-TARGET")
@@ -157,7 +158,7 @@ pub fn parse_config(args: Vec<String>) -> Config {
             "",
             "rustfix-coverage",
             "enable this to generate a Rustfix coverage file, which is saved in \
-            `./<build_base>/rustfix_missing_coverage.txt`",
+            `./<build_test_suite_root>/rustfix_missing_coverage.txt`",
         )
         .optflag("", "force-rerun", "rerun tests even if the inputs are unchanged")
         .optflag("", "only-modified", "only run tests that result been modified")
@@ -309,6 +310,10 @@ pub fn parse_config(args: Vec<String>) -> Config {
         src_test_suite_root.display()
     );
 
+    let build_root = opt_path(matches, "build-root");
+    let build_test_suite_root = opt_path(matches, "build-test-suite-root");
+    assert!(build_test_suite_root.starts_with(&build_root));
+
     Config {
         bless: matches.opt_present("bless"),
         compile_lib_path: make_absolute(opt_path(matches, "compile-lib-path")),
@@ -327,7 +332,9 @@ pub fn parse_config(args: Vec<String>) -> Config {
         src_root,
         src_test_suite_root,
 
-        build_base: opt_path(matches, "build-base"),
+        build_root,
+        build_test_suite_root,
+
         sysroot_base: opt_path(matches, "sysroot-base"),
 
         stage,
@@ -438,7 +445,11 @@ pub fn log_config(config: &Config) {
     logv(c, format!("src_root: {}", config.src_root.display()));
     logv(c, format!("src_test_suite_root: {}", config.src_test_suite_root.display()));
 
-    logv(c, format!("build_base: {:?}", config.build_base.display()));
+    logv(c, format!("build_root: {}", config.build_root.display()));
+    logv(c, format!("build_test_suite_root: {}", config.build_test_suite_root.display()));
+
+    logv(c, format!("sysroot_base: {}", config.sysroot_base.display()));
+
     logv(c, format!("stage: {}", config.stage));
     logv(c, format!("stage_id: {}", config.stage_id));
     logv(c, format!("mode: {}", config.mode));
@@ -488,7 +499,7 @@ pub fn run_tests(config: Arc<Config>) {
     // we first make sure that the coverage file does not exist.
     // It will be created later on.
     if config.rustfix_coverage {
-        let mut coverage_file_path = config.build_base.clone();
+        let mut coverage_file_path = config.build_test_suite_root.clone();
         coverage_file_path.push("rustfix_missing_coverage.txt");
         if coverage_file_path.exists() {
             if let Err(e) = fs::remove_file(&coverage_file_path) {
diff --git a/src/tools/compiletest/src/runtest.rs b/src/tools/compiletest/src/runtest.rs
index 536e19bc493..f36d43b211f 100644
--- a/src/tools/compiletest/src/runtest.rs
+++ b/src/tools/compiletest/src/runtest.rs
@@ -535,7 +535,9 @@ impl<'test> TestCx<'test> {
             .arg(&out_dir)
             .arg(&format!("--target={}", target))
             .arg("-L")
-            .arg(&self.config.build_base)
+            // FIXME(jieyouxu): this search path seems questionable. Is this intended for
+            // `rust_test_helpers` in ui tests?
+            .arg(&self.config.build_test_suite_root)
             .arg("-L")
             .arg(aux_dir)
             .arg("-A")
@@ -1366,7 +1368,7 @@ impl<'test> TestCx<'test> {
         // Note: avoid adding a subdirectory of an already filtered directory here, otherwise the
         // same slice of text will be double counted and the truncation might not happen.
         add_path(&self.config.src_test_suite_root);
-        add_path(&self.config.build_base);
+        add_path(&self.config.build_test_suite_root);
 
         read2_abbreviated(child, &filter_paths_from_len).expect("failed to read output")
     }
@@ -1421,7 +1423,7 @@ impl<'test> TestCx<'test> {
     }
 
     fn get_mir_dump_dir(&self) -> PathBuf {
-        let mut mir_dump_dir = PathBuf::from(self.config.build_base.as_path());
+        let mut mir_dump_dir = self.config.build_test_suite_root.clone();
         debug!("input_file: {:?}", self.testpaths.file);
         mir_dump_dir.push(&self.testpaths.relative_dir);
         mir_dump_dir.push(self.testpaths.file.file_stem().unwrap());
@@ -2410,14 +2412,10 @@ impl<'test> TestCx<'test> {
         let rust_src_dir = rust_src_dir.read_link().unwrap_or(rust_src_dir.to_path_buf());
         normalize_path(&rust_src_dir.join("library"), "$SRC_DIR_REAL");
 
-        // Paths into the build directory
-        let test_build_dir = &self.config.build_base;
-        let parent_build_dir = test_build_dir.parent().unwrap().parent().unwrap().parent().unwrap();
-
         // eg. /home/user/rust/build/x86_64-unknown-linux-gnu/test/ui
-        normalize_path(test_build_dir, "$TEST_BUILD_DIR");
+        normalize_path(&self.config.build_test_suite_root, "$TEST_BUILD_DIR");
         // eg. /home/user/rust/build
-        normalize_path(parent_build_dir, "$BUILD_DIR");
+        normalize_path(&self.config.build_root, "$BUILD_DIR");
 
         if json {
             // escaped newlines in json strings should be readable
diff --git a/src/tools/compiletest/src/runtest/run_make.rs b/src/tools/compiletest/src/runtest/run_make.rs
index 74e6af36ea1..9bb3993223e 100644
--- a/src/tools/compiletest/src/runtest/run_make.rs
+++ b/src/tools/compiletest/src/runtest/run_make.rs
@@ -179,12 +179,7 @@ impl TestCx<'_> {
         //    library.
         // 2. We need to run the recipe binary.
 
-        // `self.config.build_base` is actually the build base folder + "test" + test suite name, it
-        // looks like `build/<host_triple>/test/run-make`. But we want `build/<host_triple>/`. Note
-        // that the `build` directory does not need to be called `build`, nor does it need to be
-        // under `src_root`, so we must compute it based off of `self.config.build_base`.
-        let build_root =
-            self.config.build_base.parent().and_then(Path::parent).unwrap().to_path_buf();
+        let host_build_root = self.config.build_root.join(&self.config.host);
 
         // We construct the following directory tree for each rmake.rs test:
         // ```
@@ -242,10 +237,10 @@ impl TestCx<'_> {
 
         let stage_number = self.config.stage;
 
-        let stage_tools_bin = build_root.join(format!("stage{stage_number}-tools-bin"));
+        let stage_tools_bin = host_build_root.join(format!("stage{stage_number}-tools-bin"));
         let support_lib_path = stage_tools_bin.join("librun_make_support.rlib");
 
-        let stage_tools = build_root.join(format!("stage{stage_number}-tools"));
+        let stage_tools = host_build_root.join(format!("stage{stage_number}-tools"));
         let support_lib_deps = stage_tools.join(&self.config.host).join("release").join("deps");
         let support_lib_deps_deps = stage_tools.join("release").join("deps");
 
@@ -311,7 +306,7 @@ impl TestCx<'_> {
         // to work correctly.
         //
         // See <https://github.com/rust-lang/rust/pull/122248> for more background.
-        let stage0_sysroot = build_root.join("stage0-sysroot");
+        let stage0_sysroot = host_build_root.join("stage0-sysroot");
         if std::env::var_os("COMPILETEST_FORCE_STAGE0").is_some() {
             rustc.arg("--sysroot").arg(&stage0_sysroot);
         }
@@ -326,7 +321,7 @@ impl TestCx<'_> {
         // provided through env vars.
 
         // Compute stage-specific standard library paths.
-        let stage_std_path = build_root.join(format!("stage{stage_number}")).join("lib");
+        let stage_std_path = host_build_root.join(format!("stage{stage_number}")).join("lib");
 
         // Compute dynamic library search paths for recipes.
         let recipe_dylib_search_paths = {
@@ -372,7 +367,7 @@ impl TestCx<'_> {
             // Provide path to sources root.
             .env("SOURCE_ROOT", &self.config.src_root)
             // Path to the host build directory.
-            .env("BUILD_ROOT", &build_root)
+            .env("BUILD_ROOT", &host_build_root)
             // Provide path to stage-corresponding rustc.
             .env("RUSTC", &self.config.rustc_path)
             // Provide the directory to libraries that are needed to run the *compiler*. This is not
diff --git a/src/tools/compiletest/src/runtest/ui.rs b/src/tools/compiletest/src/runtest/ui.rs
index 0c6d46188e6..3329e10745f 100644
--- a/src/tools/compiletest/src/runtest/ui.rs
+++ b/src/tools/compiletest/src/runtest/ui.rs
@@ -66,7 +66,7 @@ impl TestCx<'_> {
                 && !self.props.run_rustfix
                 && !self.props.rustfix_only_machine_applicable
             {
-                let mut coverage_file_path = self.config.build_base.clone();
+                let mut coverage_file_path = self.config.build_test_suite_root.clone();
                 coverage_file_path.push("rustfix_missing_coverage.txt");
                 debug!("coverage_file_path: {}", coverage_file_path.display());
 
diff --git a/tests/codegen/virtual-call-attrs-issue-137646.rs b/tests/codegen/virtual-call-attrs-issue-137646.rs
new file mode 100644
index 00000000000..5e453947f27
--- /dev/null
+++ b/tests/codegen/virtual-call-attrs-issue-137646.rs
@@ -0,0 +1,37 @@
+//! Regression test for https://github.com/rust-lang/rust/issues/137646.
+//! Since we don't know the exact implementation of the virtual call,
+//! it might write to parameters, we can't infer the readonly attribute.
+//@ compile-flags: -C opt-level=3 -C no-prepopulate-passes
+
+#![crate_type = "lib"]
+#![feature(rustc_attrs)]
+
+pub trait Trait {
+    #[rustc_nounwind]
+    fn m(&self, _: (i32, i32, i32)) {}
+}
+
+#[no_mangle]
+pub fn foo(trait_: &dyn Trait) {
+    // CHECK-LABEL: @foo(
+    // CHECK: call void
+    // CHECK-NOT: readonly
+    trait_.m((1, 1, 1));
+}
+
+#[no_mangle]
+#[rustc_nounwind]
+pub fn foo_nounwind(trait_: &dyn Trait) {
+    // CHECK-LABEL: @foo_nounwind(
+    // FIXME: Here should be invoke.
+    // COM: CHECK: invoke
+    trait_.m((1, 1, 1));
+}
+
+#[no_mangle]
+pub extern "C" fn c_nounwind(trait_: &dyn Trait) {
+    // CHECK-LABEL: @c_nounwind(
+    // FIXME: Here should be invoke.
+    // COM: CHECK: invoke
+    trait_.m((1, 1, 1));
+}
diff --git a/tests/ui/drop/drop-order-comparisons.e2021.stderr b/tests/ui/drop/drop-order-comparisons.e2021.stderr
index 158d18f6882..601b0a38412 100644
--- a/tests/ui/drop/drop-order-comparisons.e2021.stderr
+++ b/tests/ui/drop/drop-order-comparisons.e2021.stderr
@@ -242,6 +242,15 @@ LL |     _ = (if let Ok(_) = e.ok(4).as_ref() {
    |
    = warning: this changes meaning in Rust 2024
    = note: for more information, see <https://doc.rust-lang.org/nightly/edition-guide/rust-2024/temporary-if-let-scope.html>
+note: value invokes this custom destructor
+  --> $DIR/drop-order-comparisons.rs:571:1
+   |
+LL | / impl<'b> Drop for LogDrop<'b> {
+LL | |     fn drop(&mut self) {
+LL | |         self.0.mark(self.1);
+LL | |     }
+LL | | }
+   | |_^
 help: the value is now dropped here in Edition 2024
   --> $DIR/drop-order-comparisons.rs:127:5
    |
@@ -267,6 +276,15 @@ LL |     _ = (if let Ok(_) = e.err(4).as_ref() {} else {
    |
    = warning: this changes meaning in Rust 2024
    = note: for more information, see <https://doc.rust-lang.org/nightly/edition-guide/rust-2024/temporary-if-let-scope.html>
+note: value invokes this custom destructor
+  --> $DIR/drop-order-comparisons.rs:571:1
+   |
+LL | / impl<'b> Drop for LogDrop<'b> {
+LL | |     fn drop(&mut self) {
+LL | |         self.0.mark(self.1);
+LL | |     }
+LL | | }
+   | |_^
 help: the value is now dropped here in Edition 2024
   --> $DIR/drop-order-comparisons.rs:145:44
    |
@@ -291,6 +309,15 @@ LL |         if let Ok(_) = e.err(4).as_ref() {} else {
    |
    = warning: this changes meaning in Rust 2024
    = note: for more information, see <https://doc.rust-lang.org/nightly/edition-guide/rust-2024/temporary-if-let-scope.html>
+note: value invokes this custom destructor
+  --> $DIR/drop-order-comparisons.rs:571:1
+   |
+LL | / impl<'b> Drop for LogDrop<'b> {
+LL | |     fn drop(&mut self) {
+LL | |         self.0.mark(self.1);
+LL | |     }
+LL | | }
+   | |_^
 help: the value is now dropped here in Edition 2024
   --> $DIR/drop-order-comparisons.rs:247:43
    |
@@ -315,6 +342,15 @@ LL |         if let true = e.err(9).is_ok() {} else {
    |
    = warning: this changes meaning in Rust 2024
    = note: for more information, see <https://doc.rust-lang.org/nightly/edition-guide/rust-2024/temporary-if-let-scope.html>
+note: value invokes this custom destructor
+  --> $DIR/drop-order-comparisons.rs:571:1
+   |
+LL | / impl<'b> Drop for LogDrop<'b> {
+LL | |     fn drop(&mut self) {
+LL | |         self.0.mark(self.1);
+LL | |     }
+LL | | }
+   | |_^
 help: the value is now dropped here in Edition 2024
   --> $DIR/drop-order-comparisons.rs:352:41
    |
@@ -339,6 +375,15 @@ LL |         if let Ok(_v) = e.err(8) {} else {
    |
    = warning: this changes meaning in Rust 2024
    = note: for more information, see <https://doc.rust-lang.org/nightly/edition-guide/rust-2024/temporary-if-let-scope.html>
+note: value invokes this custom destructor
+  --> $DIR/drop-order-comparisons.rs:571:1
+   |
+LL | / impl<'b> Drop for LogDrop<'b> {
+LL | |     fn drop(&mut self) {
+LL | |         self.0.mark(self.1);
+LL | |     }
+LL | | }
+   | |_^
 help: the value is now dropped here in Edition 2024
   --> $DIR/drop-order-comparisons.rs:355:35
    |
@@ -363,6 +408,15 @@ LL |         if let Ok(_) = e.err(7) {} else {
    |
    = warning: this changes meaning in Rust 2024
    = note: for more information, see <https://doc.rust-lang.org/nightly/edition-guide/rust-2024/temporary-if-let-scope.html>
+note: value invokes this custom destructor
+  --> $DIR/drop-order-comparisons.rs:571:1
+   |
+LL | / impl<'b> Drop for LogDrop<'b> {
+LL | |     fn drop(&mut self) {
+LL | |         self.0.mark(self.1);
+LL | |     }
+LL | | }
+   | |_^
 help: the value is now dropped here in Edition 2024
   --> $DIR/drop-order-comparisons.rs:358:34
    |
@@ -387,6 +441,15 @@ LL |         if let Ok(_) = e.err(6).as_ref() {} else {
    |
    = warning: this changes meaning in Rust 2024
    = note: for more information, see <https://doc.rust-lang.org/nightly/edition-guide/rust-2024/temporary-if-let-scope.html>
+note: value invokes this custom destructor
+  --> $DIR/drop-order-comparisons.rs:571:1
+   |
+LL | / impl<'b> Drop for LogDrop<'b> {
+LL | |     fn drop(&mut self) {
+LL | |         self.0.mark(self.1);
+LL | |     }
+LL | | }
+   | |_^
 help: the value is now dropped here in Edition 2024
   --> $DIR/drop-order-comparisons.rs:361:43
    |
@@ -411,6 +474,15 @@ LL |         if let Ok(_v) = e.err(5) {} else {
    |
    = warning: this changes meaning in Rust 2024
    = note: for more information, see <https://doc.rust-lang.org/nightly/edition-guide/rust-2024/temporary-if-let-scope.html>
+note: value invokes this custom destructor
+  --> $DIR/drop-order-comparisons.rs:571:1
+   |
+LL | / impl<'b> Drop for LogDrop<'b> {
+LL | |     fn drop(&mut self) {
+LL | |         self.0.mark(self.1);
+LL | |     }
+LL | | }
+   | |_^
 help: the value is now dropped here in Edition 2024
   --> $DIR/drop-order-comparisons.rs:365:35
    |
@@ -435,6 +507,15 @@ LL |         if let Ok(_) = e.err(4) {} else {
    |
    = warning: this changes meaning in Rust 2024
    = note: for more information, see <https://doc.rust-lang.org/nightly/edition-guide/rust-2024/temporary-if-let-scope.html>
+note: value invokes this custom destructor
+  --> $DIR/drop-order-comparisons.rs:571:1
+   |
+LL | / impl<'b> Drop for LogDrop<'b> {
+LL | |     fn drop(&mut self) {
+LL | |         self.0.mark(self.1);
+LL | |     }
+LL | | }
+   | |_^
 help: the value is now dropped here in Edition 2024
   --> $DIR/drop-order-comparisons.rs:368:34
    |
@@ -459,6 +540,15 @@ LL |         if let Ok(_) = e.err(4).as_ref() {} else {
    |
    = warning: this changes meaning in Rust 2024
    = note: for more information, see <https://doc.rust-lang.org/nightly/edition-guide/rust-2024/temporary-if-let-scope.html>
+note: value invokes this custom destructor
+  --> $DIR/drop-order-comparisons.rs:571:1
+   |
+LL | / impl<'b> Drop for LogDrop<'b> {
+LL | |     fn drop(&mut self) {
+LL | |         self.0.mark(self.1);
+LL | |     }
+LL | | }
+   | |_^
 help: the value is now dropped here in Edition 2024
   --> $DIR/drop-order-comparisons.rs:404:43
    |
diff --git a/tests/ui/drop/lint-if-let-rescope-gated.edition2021.stderr b/tests/ui/drop/lint-if-let-rescope-gated.edition2021.stderr
index 546a5fe0fd0..070ba1c6a4c 100644
--- a/tests/ui/drop/lint-if-let-rescope-gated.edition2021.stderr
+++ b/tests/ui/drop/lint-if-let-rescope-gated.edition2021.stderr
@@ -8,6 +8,15 @@ LL |     if let Some(_value) = Droppy.get() {
    |
    = warning: this changes meaning in Rust 2024
    = note: for more information, see <https://doc.rust-lang.org/nightly/edition-guide/rust-2024/temporary-if-let-scope.html>
+note: value invokes this custom destructor
+  --> $DIR/lint-if-let-rescope-gated.rs:14:1
+   |
+LL | / impl Drop for Droppy {
+LL | |     fn drop(&mut self) {
+LL | |         println!("dropped");
+LL | |     }
+LL | | }
+   | |_^
 help: the value is now dropped here in Edition 2024
   --> $DIR/lint-if-let-rescope-gated.rs:30:5
    |
diff --git a/tests/ui/drop/lint-if-let-rescope-with-macro.stderr b/tests/ui/drop/lint-if-let-rescope-with-macro.stderr
index 029d5c74929..f1ca0ba57de 100644
--- a/tests/ui/drop/lint-if-let-rescope-with-macro.stderr
+++ b/tests/ui/drop/lint-if-let-rescope-with-macro.stderr
@@ -15,6 +15,15 @@ LL | |     };
    |
    = warning: this changes meaning in Rust 2024
    = note: for more information, see <https://doc.rust-lang.org/nightly/edition-guide/rust-2024/temporary-if-let-scope.html>
+note: value invokes this custom destructor
+  --> $DIR/lint-if-let-rescope-with-macro.rs:22:1
+   |
+LL | / impl Drop for Droppy {
+LL | |     fn drop(&mut self) {
+LL | |         println!("dropped");
+LL | |     }
+LL | | }
+   | |_^
 help: the value is now dropped here in Edition 2024
   --> $DIR/lint-if-let-rescope-with-macro.rs:12:38
    |
diff --git a/tests/ui/drop/lint-if-let-rescope.stderr b/tests/ui/drop/lint-if-let-rescope.stderr
index b17239976cc..e95ec8fcea7 100644
--- a/tests/ui/drop/lint-if-let-rescope.stderr
+++ b/tests/ui/drop/lint-if-let-rescope.stderr
@@ -8,6 +8,15 @@ LL |     if let Some(_value) = droppy().get() {
    |
    = warning: this changes meaning in Rust 2024
    = note: for more information, see <https://doc.rust-lang.org/nightly/edition-guide/rust-2024/temporary-if-let-scope.html>
+note: value invokes this custom destructor
+  --> $DIR/lint-if-let-rescope.rs:11:1
+   |
+LL | / impl Drop for Droppy {
+LL | |     fn drop(&mut self) {
+LL | |         println!("dropped");
+LL | |     }
+LL | | }
+   | |_^
 help: the value is now dropped here in Edition 2024
   --> $DIR/lint-if-let-rescope.rs:32:5
    |
@@ -43,6 +52,24 @@ LL |     } else if let Some(_value) = droppy().get() {
    |
    = warning: this changes meaning in Rust 2024
    = note: for more information, see <https://doc.rust-lang.org/nightly/edition-guide/rust-2024/temporary-if-let-scope.html>
+note: value invokes this custom destructor
+  --> $DIR/lint-if-let-rescope.rs:11:1
+   |
+LL | / impl Drop for Droppy {
+LL | |     fn drop(&mut self) {
+LL | |         println!("dropped");
+LL | |     }
+LL | | }
+   | |_^
+note: value invokes this custom destructor
+  --> $DIR/lint-if-let-rescope.rs:11:1
+   |
+LL | / impl Drop for Droppy {
+LL | |     fn drop(&mut self) {
+LL | |         println!("dropped");
+LL | |     }
+LL | | }
+   | |_^
 help: the value is now dropped here in Edition 2024
   --> $DIR/lint-if-let-rescope.rs:42:5
    |
@@ -75,6 +102,15 @@ LL |     } else if let Some(_value) = droppy().get() {
    |
    = warning: this changes meaning in Rust 2024
    = note: for more information, see <https://doc.rust-lang.org/nightly/edition-guide/rust-2024/temporary-if-let-scope.html>
+note: value invokes this custom destructor
+  --> $DIR/lint-if-let-rescope.rs:11:1
+   |
+LL | / impl Drop for Droppy {
+LL | |     fn drop(&mut self) {
+LL | |         println!("dropped");
+LL | |     }
+LL | | }
+   | |_^
 help: the value is now dropped here in Edition 2024
   --> $DIR/lint-if-let-rescope.rs:54:5
    |
@@ -101,6 +137,15 @@ LL |     if let Some(1) = { if let Some(_value) = Droppy.get() { Some(1) } else
    |
    = warning: this changes meaning in Rust 2024
    = note: for more information, see <https://doc.rust-lang.org/nightly/edition-guide/rust-2024/temporary-if-let-scope.html>
+note: value invokes this custom destructor
+  --> $DIR/lint-if-let-rescope.rs:11:1
+   |
+LL | / impl Drop for Droppy {
+LL | |     fn drop(&mut self) {
+LL | |         println!("dropped");
+LL | |     }
+LL | | }
+   | |_^
 help: the value is now dropped here in Edition 2024
   --> $DIR/lint-if-let-rescope.rs:58:69
    |
@@ -122,6 +167,15 @@ LL |     if (if let Some(_value) = droppy().get() { true } else { false }) {
    |
    = warning: this changes meaning in Rust 2024
    = note: for more information, see <https://doc.rust-lang.org/nightly/edition-guide/rust-2024/temporary-if-let-scope.html>
+note: value invokes this custom destructor
+  --> $DIR/lint-if-let-rescope.rs:11:1
+   |
+LL | / impl Drop for Droppy {
+LL | |     fn drop(&mut self) {
+LL | |         println!("dropped");
+LL | |     }
+LL | | }
+   | |_^
 help: the value is now dropped here in Edition 2024
   --> $DIR/lint-if-let-rescope.rs:72:53
    |
@@ -143,6 +197,15 @@ LL |     } else if (((if let Some(_value) = droppy().get() { true } else { false
    |
    = warning: this changes meaning in Rust 2024
    = note: for more information, see <https://doc.rust-lang.org/nightly/edition-guide/rust-2024/temporary-if-let-scope.html>
+note: value invokes this custom destructor
+  --> $DIR/lint-if-let-rescope.rs:11:1
+   |
+LL | / impl Drop for Droppy {
+LL | |     fn drop(&mut self) {
+LL | |         println!("dropped");
+LL | |     }
+LL | | }
+   | |_^
 help: the value is now dropped here in Edition 2024
   --> $DIR/lint-if-let-rescope.rs:78:62
    |
@@ -164,6 +227,15 @@ LL |     while (if let Some(_value) = droppy().get() { false } else { true }) {
    |
    = warning: this changes meaning in Rust 2024
    = note: for more information, see <https://doc.rust-lang.org/nightly/edition-guide/rust-2024/temporary-if-let-scope.html>
+note: value invokes this custom destructor
+  --> $DIR/lint-if-let-rescope.rs:11:1
+   |
+LL | / impl Drop for Droppy {
+LL | |     fn drop(&mut self) {
+LL | |         println!("dropped");
+LL | |     }
+LL | | }
+   | |_^
 help: the value is now dropped here in Edition 2024
   --> $DIR/lint-if-let-rescope.rs:90:57
    |
diff --git a/tests/ui/impl-trait/method-suggestion-no-duplication.stderr b/tests/ui/impl-trait/method-suggestion-no-duplication.stderr
index c401269da83..6bc57f89467 100644
--- a/tests/ui/impl-trait/method-suggestion-no-duplication.stderr
+++ b/tests/ui/impl-trait/method-suggestion-no-duplication.stderr
@@ -8,8 +8,9 @@ LL |     foo(|s| s.is_empty());
    |               ^^^^^^^^ method not found in `Foo`
    |
    = help: items from traits can only be used if the trait is implemented and in scope
-   = note: the following trait defines an item `is_empty`, perhaps you need to implement it:
+   = note: the following traits define an item `is_empty`, perhaps you need to implement one of them:
            candidate #1: `ExactSizeIterator`
+           candidate #2: `RangeBounds`
 
 error: aborting due to 1 previous error
 
diff --git a/tests/ui/traits/const-traits/staged-api.rs b/tests/ui/traits/const-traits/staged-api.rs
index 9a030dafd6b..8dd7226fc29 100644
--- a/tests/ui/traits/const-traits/staged-api.rs
+++ b/tests/ui/traits/const-traits/staged-api.rs
@@ -85,4 +85,36 @@ const fn implicitly_stable_const_context() {
     //~^ ERROR cannot use `#[feature(const_trait_impl)]`
 }
 
+// check that const stability of impls and traits must match
+#[const_trait]
+#[rustc_const_unstable(feature = "beef", issue = "none")]
+trait U {}
+
+#[const_trait]
+#[rustc_const_stable(since = "0.0.0", feature = "beef2")]
+trait S {}
+
+// implied stable
+impl const U for u8 {}
+//~^ const stability on the impl does not match the const stability on the trait
+
+#[rustc_const_stable(since = "0.0.0", feature = "beef2")]
+impl const U for u16 {}
+//~^ const stability on the impl does not match the const stability on the trait
+//~| trait implementations cannot be const stable yet
+
+#[rustc_const_unstable(feature = "beef", issue = "none")]
+impl const U for u32 {}
+
+// implied stable
+impl const S for u8 {}
+
+#[rustc_const_stable(since = "0.0.0", feature = "beef2")]
+impl const S for u16 {}
+//~^ trait implementations cannot be const stable yet
+
+#[rustc_const_unstable(feature = "beef", issue = "none")]
+impl const S for u32 {}
+//~^ const stability on the impl does not match the const stability on the trait
+
 fn main() {}
diff --git a/tests/ui/traits/const-traits/staged-api.stderr b/tests/ui/traits/const-traits/staged-api.stderr
index a7a7a1ee721..cdf577287ee 100644
--- a/tests/ui/traits/const-traits/staged-api.stderr
+++ b/tests/ui/traits/const-traits/staged-api.stderr
@@ -1,3 +1,70 @@
+error: const stability on the impl does not match the const stability on the trait
+  --> $DIR/staged-api.rs:98:1
+   |
+LL | impl const U for u8 {}
+   | ^^^^^^^^^^^^^^^^^^^^^^
+   |
+note: this impl is (implicitly) stable...
+  --> $DIR/staged-api.rs:98:1
+   |
+LL | impl const U for u8 {}
+   | ^^^^^^^^^^^^^^^^^^^^^^
+note: ...but the trait is unstable
+  --> $DIR/staged-api.rs:91:7
+   |
+LL | trait U {}
+   |       ^
+
+error: trait implementations cannot be const stable yet
+  --> $DIR/staged-api.rs:102:1
+   |
+LL | impl const U for u16 {}
+   | ^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = note: see issue #67792 <https://github.com/rust-lang/rust/issues/67792> for more information
+
+error: const stability on the impl does not match the const stability on the trait
+  --> $DIR/staged-api.rs:102:1
+   |
+LL | impl const U for u16 {}
+   | ^^^^^^^^^^^^^^^^^^^^^^^
+   |
+note: this impl is (implicitly) stable...
+  --> $DIR/staged-api.rs:102:1
+   |
+LL | impl const U for u16 {}
+   | ^^^^^^^^^^^^^^^^^^^^^^^
+note: ...but the trait is unstable
+  --> $DIR/staged-api.rs:91:7
+   |
+LL | trait U {}
+   |       ^
+
+error: trait implementations cannot be const stable yet
+  --> $DIR/staged-api.rs:113:1
+   |
+LL | impl const S for u16 {}
+   | ^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = note: see issue #67792 <https://github.com/rust-lang/rust/issues/67792> for more information
+
+error: const stability on the impl does not match the const stability on the trait
+  --> $DIR/staged-api.rs:117:1
+   |
+LL | impl const S for u32 {}
+   | ^^^^^^^^^^^^^^^^^^^^^^^
+   |
+note: this impl is unstable...
+  --> $DIR/staged-api.rs:117:1
+   |
+LL | impl const S for u32 {}
+   | ^^^^^^^^^^^^^^^^^^^^^^^
+note: ...but the trait is stable
+  --> $DIR/staged-api.rs:95:7
+   |
+LL | trait S {}
+   |       ^
+
 error: const function that might be (indirectly) exposed to stable cannot use `#[feature(const_trait_impl)]`
   --> $DIR/staged-api.rs:38:5
    |
@@ -323,5 +390,5 @@ LL + #[rustc_allow_const_fn_unstable(const_trait_impl)]
 LL | const fn implicitly_stable_const_context() {
    |
 
-error: aborting due to 19 previous errors
+error: aborting due to 24 previous errors
 
diff --git a/tests/ui/virtual-call-attrs-issue-137646.rs b/tests/ui/virtual-call-attrs-issue-137646.rs
new file mode 100644
index 00000000000..e80bd5768a4
--- /dev/null
+++ b/tests/ui/virtual-call-attrs-issue-137646.rs
@@ -0,0 +1,45 @@
+//! Regression test for https://github.com/rust-lang/rust/issues/137646.
+//! The parameter value at all calls to `check` should be `(1, 1, 1)`.
+
+//@ run-pass
+
+use std::hint::black_box;
+
+type T = (i32, i32, i32);
+
+pub trait Trait {
+    fn m(&self, _: T, _: T) {}
+}
+
+impl Trait for () {
+    fn m(&self, mut _v1: T, v2: T) {
+        _v1 = (0, 0, 0);
+        check(v2);
+    }
+}
+
+pub fn run_1(trait_: &dyn Trait) {
+    let v1 = (1, 1, 1);
+    let v2 = (1, 1, 1);
+    trait_.m(v1, v2);
+}
+
+pub fn run_2(trait_: &dyn Trait) {
+    let v1 = (1, 1, 1);
+    let v2 = (1, 1, 1);
+    trait_.m(v1, v2);
+    check(v1);
+    check(v2);
+}
+
+#[inline(never)]
+fn check(v: T) {
+    assert_eq!(v, (1, 1, 1));
+}
+
+fn main() {
+    black_box(run_1 as fn(&dyn Trait));
+    black_box(run_2 as fn(&dyn Trait));
+    run_1(&());
+    run_2(&());
+}