diff --git a/Cargo.lock b/Cargo.lock
index 21d522cb9ff..6a375528347 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -3370,7 +3370,6 @@ dependencies = [
  "object 0.29.0",
  "pathdiff",
  "regex",
- "rustc_apfloat",
  "rustc_arena",
  "rustc_ast",
  "rustc_attr",
diff --git a/compiler/rustc_codegen_gcc/src/builder.rs b/compiler/rustc_codegen_gcc/src/builder.rs
index 4d40dd0994d..6994eeb00c3 100644
--- a/compiler/rustc_codegen_gcc/src/builder.rs
+++ b/compiler/rustc_codegen_gcc/src/builder.rs
@@ -15,8 +15,11 @@ use gccjit::{
     Type,
     UnaryOp,
 };
+use rustc_apfloat::{ieee, Float, Round, Status};
 use rustc_codegen_ssa::MemFlags;
-use rustc_codegen_ssa::common::{AtomicOrdering, AtomicRmwBinOp, IntPredicate, RealPredicate, SynchronizationScope};
+use rustc_codegen_ssa::common::{
+    AtomicOrdering, AtomicRmwBinOp, IntPredicate, RealPredicate, SynchronizationScope, TypeKind,
+};
 use rustc_codegen_ssa::mir::operand::{OperandRef, OperandValue};
 use rustc_codegen_ssa::mir::place::PlaceRef;
 use rustc_codegen_ssa::traits::{
@@ -31,6 +34,7 @@ use rustc_codegen_ssa::traits::{
     StaticBuilderMethods,
 };
 use rustc_data_structures::fx::FxHashSet;
+use rustc_middle::bug;
 use rustc_middle::ty::{ParamEnv, Ty, TyCtxt};
 use rustc_middle::ty::layout::{FnAbiError, FnAbiOfHelpers, FnAbiRequest, HasParamEnv, HasTyCtxt, LayoutError, LayoutOfHelpers, TyAndLayout};
 use rustc_span::Span;
@@ -1271,12 +1275,12 @@ impl<'a, 'gcc, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'gcc, 'tcx> {
         val
     }
 
-    fn fptoui_sat(&mut self, _val: RValue<'gcc>, _dest_ty: Type<'gcc>) -> Option<RValue<'gcc>> {
-        None
+    fn fptoui_sat(&mut self, val: RValue<'gcc>, dest_ty: Type<'gcc>) -> RValue<'gcc> {
+        self.fptoint_sat(false, val, dest_ty)
     }
 
-    fn fptosi_sat(&mut self, _val: RValue<'gcc>, _dest_ty: Type<'gcc>) -> Option<RValue<'gcc>> {
-        None
+    fn fptosi_sat(&mut self, val: RValue<'gcc>, dest_ty: Type<'gcc>) -> RValue<'gcc> {
+        self.fptoint_sat(true, val, dest_ty)
     }
 
     fn instrprof_increment(&mut self, _fn_name: RValue<'gcc>, _hash: RValue<'gcc>, _num_counters: RValue<'gcc>, _index: RValue<'gcc>) {
@@ -1285,6 +1289,166 @@ impl<'a, 'gcc, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'gcc, 'tcx> {
 }
 
 impl<'a, 'gcc, 'tcx> Builder<'a, 'gcc, 'tcx> {
+    fn fptoint_sat(&mut self, signed: bool, val: RValue<'gcc>, dest_ty: Type<'gcc>) -> RValue<'gcc> {
+        let src_ty = self.cx.val_ty(val);
+        let (float_ty, int_ty) = if self.cx.type_kind(src_ty) == TypeKind::Vector {
+            assert_eq!(self.cx.vector_length(src_ty), self.cx.vector_length(dest_ty));
+            (self.cx.element_type(src_ty), self.cx.element_type(dest_ty))
+        } else {
+            (src_ty, dest_ty)
+        };
+
+        // FIXME(jistone): the following was originally the fallback SSA implementation, before LLVM 13
+        // added native `fptosi.sat` and `fptoui.sat` conversions, but it was used by GCC as well.
+        // Now that LLVM always relies on its own, the code has been moved to GCC, but the comments are
+        // still LLVM-specific. This should be updated, and use better GCC specifics if possible.
+
+        let int_width = self.cx.int_width(int_ty);
+        let float_width = self.cx.float_width(float_ty);
+        // LLVM's fpto[su]i returns undef when the input val is infinite, NaN, or does not fit into the
+        // destination integer type after rounding towards zero. This `undef` value can cause UB in
+        // safe code (see issue #10184), so we implement a saturating conversion on top of it:
+        // Semantically, the mathematical value of the input is rounded towards zero to the next
+        // mathematical integer, and then the result is clamped into the range of the destination
+        // integer type. Positive and negative infinity are mapped to the maximum and minimum value of
+        // the destination integer type. NaN is mapped to 0.
+        //
+        // Define f_min and f_max as the largest and smallest (finite) floats that are exactly equal to
+        // a value representable in int_ty.
+        // They are exactly equal to int_ty::{MIN,MAX} if float_ty has enough significand bits.
+        // Otherwise, int_ty::MAX must be rounded towards zero, as it is one less than a power of two.
+        // int_ty::MIN, however, is either zero or a negative power of two and is thus exactly
+        // representable. Note that this only works if float_ty's exponent range is sufficiently large.
+        // f16 or 256 bit integers would break this property. Right now the smallest float type is f32
+        // with exponents ranging up to 127, which is barely enough for i128::MIN = -2^127.
+        // On the other hand, f_max works even if int_ty::MAX is greater than float_ty::MAX. Because
+        // we're rounding towards zero, we just get float_ty::MAX (which is always an integer).
+        // This already happens today with u128::MAX = 2^128 - 1 > f32::MAX.
+        let int_max = |signed: bool, int_width: u64| -> u128 {
+            let shift_amount = 128 - int_width;
+            if signed { i128::MAX as u128 >> shift_amount } else { u128::MAX >> shift_amount }
+        };
+        let int_min = |signed: bool, int_width: u64| -> i128 {
+            if signed { i128::MIN >> (128 - int_width) } else { 0 }
+        };
+
+        let compute_clamp_bounds_single = |signed: bool, int_width: u64| -> (u128, u128) {
+            let rounded_min =
+                ieee::Single::from_i128_r(int_min(signed, int_width), Round::TowardZero);
+            assert_eq!(rounded_min.status, Status::OK);
+            let rounded_max =
+                ieee::Single::from_u128_r(int_max(signed, int_width), Round::TowardZero);
+            assert!(rounded_max.value.is_finite());
+            (rounded_min.value.to_bits(), rounded_max.value.to_bits())
+        };
+        let compute_clamp_bounds_double = |signed: bool, int_width: u64| -> (u128, u128) {
+            let rounded_min =
+                ieee::Double::from_i128_r(int_min(signed, int_width), Round::TowardZero);
+            assert_eq!(rounded_min.status, Status::OK);
+            let rounded_max =
+                ieee::Double::from_u128_r(int_max(signed, int_width), Round::TowardZero);
+            assert!(rounded_max.value.is_finite());
+            (rounded_min.value.to_bits(), rounded_max.value.to_bits())
+        };
+        // To implement saturation, we perform the following steps:
+        //
+        // 1. Cast val to an integer with fpto[su]i. This may result in undef.
+        // 2. Compare val to f_min and f_max, and use the comparison results to select:
+        //  a) int_ty::MIN if val < f_min or val is NaN
+        //  b) int_ty::MAX if val > f_max
+        //  c) the result of fpto[su]i otherwise
+        // 3. If val is NaN, return 0.0, otherwise return the result of step 2.
+        //
+        // This avoids resulting undef because values in range [f_min, f_max] by definition fit into the
+        // destination type. It creates an undef temporary, but *producing* undef is not UB. Our use of
+        // undef does not introduce any non-determinism either.
+        // More importantly, the above procedure correctly implements saturating conversion.
+        // Proof (sketch):
+        // If val is NaN, 0 is returned by definition.
+        // Otherwise, val is finite or infinite and thus can be compared with f_min and f_max.
+        // This yields three cases to consider:
+        // (1) if val in [f_min, f_max], the result of fpto[su]i is returned, which agrees with
+        //     saturating conversion for inputs in that range.
+        // (2) if val > f_max, then val is larger than int_ty::MAX. This holds even if f_max is rounded
+        //     (i.e., if f_max < int_ty::MAX) because in those cases, nextUp(f_max) is already larger
+        //     than int_ty::MAX. Because val is larger than int_ty::MAX, the return value of int_ty::MAX
+        //     is correct.
+        // (3) if val < f_min, then val is smaller than int_ty::MIN. As shown earlier, f_min exactly equals
+        //     int_ty::MIN and therefore the return value of int_ty::MIN is correct.
+        // QED.
+
+        let float_bits_to_llval = |bx: &mut Self, bits| {
+            let bits_llval = match float_width {
+                32 => bx.cx().const_u32(bits as u32),
+                64 => bx.cx().const_u64(bits as u64),
+                n => bug!("unsupported float width {}", n),
+            };
+            bx.bitcast(bits_llval, float_ty)
+        };
+        let (f_min, f_max) = match float_width {
+            32 => compute_clamp_bounds_single(signed, int_width),
+            64 => compute_clamp_bounds_double(signed, int_width),
+            n => bug!("unsupported float width {}", n),
+        };
+        let f_min = float_bits_to_llval(self, f_min);
+        let f_max = float_bits_to_llval(self, f_max);
+        let int_max = self.cx.const_uint_big(int_ty, int_max(signed, int_width));
+        let int_min = self.cx.const_uint_big(int_ty, int_min(signed, int_width) as u128);
+        let zero = self.cx.const_uint(int_ty, 0);
+
+        // If we're working with vectors, constants must be "splatted": the constant is duplicated
+        // into each lane of the vector.  The algorithm stays the same, we are just using the
+        // same constant across all lanes.
+        let maybe_splat = |bx: &mut Self, val| {
+            if bx.cx().type_kind(dest_ty) == TypeKind::Vector {
+                bx.vector_splat(bx.vector_length(dest_ty), val)
+            } else {
+                val
+            }
+        };
+        let f_min = maybe_splat(self, f_min);
+        let f_max = maybe_splat(self, f_max);
+        let int_max = maybe_splat(self, int_max);
+        let int_min = maybe_splat(self, int_min);
+        let zero = maybe_splat(self, zero);
+
+        // Step 1 ...
+        let fptosui_result = if signed { self.fptosi(val, dest_ty) } else { self.fptoui(val, dest_ty) };
+        let less_or_nan = self.fcmp(RealPredicate::RealULT, val, f_min);
+        let greater = self.fcmp(RealPredicate::RealOGT, val, f_max);
+
+        // Step 2: We use two comparisons and two selects, with %s1 being the
+        // result:
+        //     %less_or_nan = fcmp ult %val, %f_min
+        //     %greater = fcmp olt %val, %f_max
+        //     %s0 = select %less_or_nan, int_ty::MIN, %fptosi_result
+        //     %s1 = select %greater, int_ty::MAX, %s0
+        // Note that %less_or_nan uses an *unordered* comparison. This
+        // comparison is true if the operands are not comparable (i.e., if val is
+        // NaN). The unordered comparison ensures that s1 becomes int_ty::MIN if
+        // val is NaN.
+        //
+        // Performance note: Unordered comparison can be lowered to a "flipped"
+        // comparison and a negation, and the negation can be merged into the
+        // select. Therefore, it not necessarily any more expensive than an
+        // ordered ("normal") comparison. Whether these optimizations will be
+        // performed is ultimately up to the backend, but at least x86 does
+        // perform them.
+        let s0 = self.select(less_or_nan, int_min, fptosui_result);
+        let s1 = self.select(greater, int_max, s0);
+
+        // Step 3: NaN replacement.
+        // For unsigned types, the above step already yielded int_ty::MIN == 0 if val is NaN.
+        // Therefore we only need to execute this step for signed integer types.
+        if signed {
+            // LLVM has no isNaN predicate, so we use (val == val) instead
+            let cmp = self.fcmp(RealPredicate::RealOEQ, val, val);
+            self.select(cmp, s1, zero)
+        } else {
+            s1
+        }
+    }
+
     #[cfg(feature="master")]
     pub fn shuffle_vector(&mut self, v1: RValue<'gcc>, v2: RValue<'gcc>, mask: RValue<'gcc>) -> RValue<'gcc> {
         let struct_type = mask.get_type().is_struct().expect("mask of struct type");
diff --git a/compiler/rustc_codegen_gcc/src/lib.rs b/compiler/rustc_codegen_gcc/src/lib.rs
index 8a206c0368f..223466fb9b5 100644
--- a/compiler/rustc_codegen_gcc/src/lib.rs
+++ b/compiler/rustc_codegen_gcc/src/lib.rs
@@ -19,6 +19,7 @@
 #![warn(rust_2018_idioms)]
 #![warn(unused_lifetimes)]
 
+extern crate rustc_apfloat;
 extern crate rustc_ast;
 extern crate rustc_codegen_ssa;
 extern crate rustc_data_structures;
diff --git a/compiler/rustc_codegen_llvm/src/builder.rs b/compiler/rustc_codegen_llvm/src/builder.rs
index 073feecb164..e7e373bf45d 100644
--- a/compiler/rustc_codegen_llvm/src/builder.rs
+++ b/compiler/rustc_codegen_llvm/src/builder.rs
@@ -725,11 +725,11 @@ impl<'a, 'll, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'll, 'tcx> {
         unsafe { llvm::LLVMBuildSExt(self.llbuilder, val, dest_ty, UNNAMED) }
     }
 
-    fn fptoui_sat(&mut self, val: &'ll Value, dest_ty: &'ll Type) -> Option<&'ll Value> {
+    fn fptoui_sat(&mut self, val: &'ll Value, dest_ty: &'ll Type) -> &'ll Value {
         self.fptoint_sat(false, val, dest_ty)
     }
 
-    fn fptosi_sat(&mut self, val: &'ll Value, dest_ty: &'ll Type) -> Option<&'ll Value> {
+    fn fptosi_sat(&mut self, val: &'ll Value, dest_ty: &'ll Type) -> &'ll Value {
         self.fptoint_sat(true, val, dest_ty)
     }
 
@@ -1429,12 +1429,7 @@ impl<'a, 'll, 'tcx> Builder<'a, 'll, 'tcx> {
         }
     }
 
-    fn fptoint_sat(
-        &mut self,
-        signed: bool,
-        val: &'ll Value,
-        dest_ty: &'ll Type,
-    ) -> Option<&'ll Value> {
+    fn fptoint_sat(&mut self, signed: bool, val: &'ll Value, dest_ty: &'ll Type) -> &'ll Value {
         let src_ty = self.cx.val_ty(val);
         let (float_ty, int_ty, vector_length) = if self.cx.type_kind(src_ty) == TypeKind::Vector {
             assert_eq!(self.cx.vector_length(src_ty), self.cx.vector_length(dest_ty));
@@ -1459,7 +1454,7 @@ impl<'a, 'll, 'tcx> Builder<'a, 'll, 'tcx> {
             format!("llvm.{}.sat.i{}.f{}", instr, int_width, float_width)
         };
         let f = self.declare_cfn(&name, llvm::UnnamedAddr::No, self.type_func(&[src_ty], dest_ty));
-        Some(self.call(self.type_func(&[src_ty], dest_ty), f, &[val], None))
+        self.call(self.type_func(&[src_ty], dest_ty), f, &[val], None)
     }
 
     pub(crate) fn landing_pad(
diff --git a/compiler/rustc_codegen_ssa/Cargo.toml b/compiler/rustc_codegen_ssa/Cargo.toml
index 46d6344dbb2..d868e3d56ba 100644
--- a/compiler/rustc_codegen_ssa/Cargo.toml
+++ b/compiler/rustc_codegen_ssa/Cargo.toml
@@ -26,7 +26,6 @@ rustc_arena = { path = "../rustc_arena" }
 rustc_ast = { path = "../rustc_ast" }
 rustc_span = { path = "../rustc_span" }
 rustc_middle = { path = "../rustc_middle" }
-rustc_apfloat = { path = "../rustc_apfloat" }
 rustc_attr = { path = "../rustc_attr" }
 rustc_symbol_mangling = { path = "../rustc_symbol_mangling" }
 rustc_data_structures = { path = "../rustc_data_structures" }
diff --git a/compiler/rustc_codegen_ssa/src/traits/builder.rs b/compiler/rustc_codegen_ssa/src/traits/builder.rs
index 9f49749bb41..10cf8948b5a 100644
--- a/compiler/rustc_codegen_ssa/src/traits/builder.rs
+++ b/compiler/rustc_codegen_ssa/src/traits/builder.rs
@@ -1,6 +1,5 @@
 use super::abi::AbiBuilderMethods;
 use super::asm::AsmBuilderMethods;
-use super::consts::ConstMethods;
 use super::coverageinfo::CoverageInfoBuilderMethods;
 use super::debuginfo::DebugInfoBuilderMethods;
 use super::intrinsic::IntrinsicCallMethods;
@@ -15,7 +14,6 @@ use crate::mir::operand::OperandRef;
 use crate::mir::place::PlaceRef;
 use crate::MemFlags;
 
-use rustc_apfloat::{ieee, Float, Round, Status};
 use rustc_middle::ty::layout::{HasParamEnv, TyAndLayout};
 use rustc_middle::ty::Ty;
 use rustc_span::Span;
@@ -188,8 +186,8 @@ pub trait BuilderMethods<'a, 'tcx>:
 
     fn trunc(&mut self, val: Self::Value, dest_ty: Self::Type) -> Self::Value;
     fn sext(&mut self, val: Self::Value, dest_ty: Self::Type) -> Self::Value;
-    fn fptoui_sat(&mut self, val: Self::Value, dest_ty: Self::Type) -> Option<Self::Value>;
-    fn fptosi_sat(&mut self, val: Self::Value, dest_ty: Self::Type) -> Option<Self::Value>;
+    fn fptoui_sat(&mut self, val: Self::Value, dest_ty: Self::Type) -> Self::Value;
+    fn fptosi_sat(&mut self, val: Self::Value, dest_ty: Self::Type) -> Self::Value;
     fn fptoui(&mut self, val: Self::Value, dest_ty: Self::Type) -> Self::Value;
     fn fptosi(&mut self, val: Self::Value, dest_ty: Self::Type) -> Self::Value;
     fn uitofp(&mut self, val: Self::Value, dest_ty: Self::Type) -> Self::Value;
@@ -223,156 +221,7 @@ pub trait BuilderMethods<'a, 'tcx>:
             return if signed { self.fptosi(x, dest_ty) } else { self.fptoui(x, dest_ty) };
         }
 
-        let try_sat_result =
-            if signed { self.fptosi_sat(x, dest_ty) } else { self.fptoui_sat(x, dest_ty) };
-        if let Some(try_sat_result) = try_sat_result {
-            return try_sat_result;
-        }
-
-        let int_width = self.cx().int_width(int_ty);
-        let float_width = self.cx().float_width(float_ty);
-        // LLVM's fpto[su]i returns undef when the input x is infinite, NaN, or does not fit into the
-        // destination integer type after rounding towards zero. This `undef` value can cause UB in
-        // safe code (see issue #10184), so we implement a saturating conversion on top of it:
-        // Semantically, the mathematical value of the input is rounded towards zero to the next
-        // mathematical integer, and then the result is clamped into the range of the destination
-        // integer type. Positive and negative infinity are mapped to the maximum and minimum value of
-        // the destination integer type. NaN is mapped to 0.
-        //
-        // Define f_min and f_max as the largest and smallest (finite) floats that are exactly equal to
-        // a value representable in int_ty.
-        // They are exactly equal to int_ty::{MIN,MAX} if float_ty has enough significand bits.
-        // Otherwise, int_ty::MAX must be rounded towards zero, as it is one less than a power of two.
-        // int_ty::MIN, however, is either zero or a negative power of two and is thus exactly
-        // representable. Note that this only works if float_ty's exponent range is sufficiently large.
-        // f16 or 256 bit integers would break this property. Right now the smallest float type is f32
-        // with exponents ranging up to 127, which is barely enough for i128::MIN = -2^127.
-        // On the other hand, f_max works even if int_ty::MAX is greater than float_ty::MAX. Because
-        // we're rounding towards zero, we just get float_ty::MAX (which is always an integer).
-        // This already happens today with u128::MAX = 2^128 - 1 > f32::MAX.
-        let int_max = |signed: bool, int_width: u64| -> u128 {
-            let shift_amount = 128 - int_width;
-            if signed { i128::MAX as u128 >> shift_amount } else { u128::MAX >> shift_amount }
-        };
-        let int_min = |signed: bool, int_width: u64| -> i128 {
-            if signed { i128::MIN >> (128 - int_width) } else { 0 }
-        };
-
-        let compute_clamp_bounds_single = |signed: bool, int_width: u64| -> (u128, u128) {
-            let rounded_min =
-                ieee::Single::from_i128_r(int_min(signed, int_width), Round::TowardZero);
-            assert_eq!(rounded_min.status, Status::OK);
-            let rounded_max =
-                ieee::Single::from_u128_r(int_max(signed, int_width), Round::TowardZero);
-            assert!(rounded_max.value.is_finite());
-            (rounded_min.value.to_bits(), rounded_max.value.to_bits())
-        };
-        let compute_clamp_bounds_double = |signed: bool, int_width: u64| -> (u128, u128) {
-            let rounded_min =
-                ieee::Double::from_i128_r(int_min(signed, int_width), Round::TowardZero);
-            assert_eq!(rounded_min.status, Status::OK);
-            let rounded_max =
-                ieee::Double::from_u128_r(int_max(signed, int_width), Round::TowardZero);
-            assert!(rounded_max.value.is_finite());
-            (rounded_min.value.to_bits(), rounded_max.value.to_bits())
-        };
-        // To implement saturation, we perform the following steps:
-        //
-        // 1. Cast x to an integer with fpto[su]i. This may result in undef.
-        // 2. Compare x to f_min and f_max, and use the comparison results to select:
-        //  a) int_ty::MIN if x < f_min or x is NaN
-        //  b) int_ty::MAX if x > f_max
-        //  c) the result of fpto[su]i otherwise
-        // 3. If x is NaN, return 0.0, otherwise return the result of step 2.
-        //
-        // This avoids resulting undef because values in range [f_min, f_max] by definition fit into the
-        // destination type. It creates an undef temporary, but *producing* undef is not UB. Our use of
-        // undef does not introduce any non-determinism either.
-        // More importantly, the above procedure correctly implements saturating conversion.
-        // Proof (sketch):
-        // If x is NaN, 0 is returned by definition.
-        // Otherwise, x is finite or infinite and thus can be compared with f_min and f_max.
-        // This yields three cases to consider:
-        // (1) if x in [f_min, f_max], the result of fpto[su]i is returned, which agrees with
-        //     saturating conversion for inputs in that range.
-        // (2) if x > f_max, then x is larger than int_ty::MAX. This holds even if f_max is rounded
-        //     (i.e., if f_max < int_ty::MAX) because in those cases, nextUp(f_max) is already larger
-        //     than int_ty::MAX. Because x is larger than int_ty::MAX, the return value of int_ty::MAX
-        //     is correct.
-        // (3) if x < f_min, then x is smaller than int_ty::MIN. As shown earlier, f_min exactly equals
-        //     int_ty::MIN and therefore the return value of int_ty::MIN is correct.
-        // QED.
-
-        let float_bits_to_llval = |bx: &mut Self, bits| {
-            let bits_llval = match float_width {
-                32 => bx.cx().const_u32(bits as u32),
-                64 => bx.cx().const_u64(bits as u64),
-                n => bug!("unsupported float width {}", n),
-            };
-            bx.bitcast(bits_llval, float_ty)
-        };
-        let (f_min, f_max) = match float_width {
-            32 => compute_clamp_bounds_single(signed, int_width),
-            64 => compute_clamp_bounds_double(signed, int_width),
-            n => bug!("unsupported float width {}", n),
-        };
-        let f_min = float_bits_to_llval(self, f_min);
-        let f_max = float_bits_to_llval(self, f_max);
-        let int_max = self.cx().const_uint_big(int_ty, int_max(signed, int_width));
-        let int_min = self.cx().const_uint_big(int_ty, int_min(signed, int_width) as u128);
-        let zero = self.cx().const_uint(int_ty, 0);
-
-        // If we're working with vectors, constants must be "splatted": the constant is duplicated
-        // into each lane of the vector.  The algorithm stays the same, we are just using the
-        // same constant across all lanes.
-        let maybe_splat = |bx: &mut Self, val| {
-            if bx.cx().type_kind(dest_ty) == TypeKind::Vector {
-                bx.vector_splat(bx.vector_length(dest_ty), val)
-            } else {
-                val
-            }
-        };
-        let f_min = maybe_splat(self, f_min);
-        let f_max = maybe_splat(self, f_max);
-        let int_max = maybe_splat(self, int_max);
-        let int_min = maybe_splat(self, int_min);
-        let zero = maybe_splat(self, zero);
-
-        // Step 1 ...
-        let fptosui_result = if signed { self.fptosi(x, dest_ty) } else { self.fptoui(x, dest_ty) };
-        let less_or_nan = self.fcmp(RealPredicate::RealULT, x, f_min);
-        let greater = self.fcmp(RealPredicate::RealOGT, x, f_max);
-
-        // Step 2: We use two comparisons and two selects, with %s1 being the
-        // result:
-        //     %less_or_nan = fcmp ult %x, %f_min
-        //     %greater = fcmp olt %x, %f_max
-        //     %s0 = select %less_or_nan, int_ty::MIN, %fptosi_result
-        //     %s1 = select %greater, int_ty::MAX, %s0
-        // Note that %less_or_nan uses an *unordered* comparison. This
-        // comparison is true if the operands are not comparable (i.e., if x is
-        // NaN). The unordered comparison ensures that s1 becomes int_ty::MIN if
-        // x is NaN.
-        //
-        // Performance note: Unordered comparison can be lowered to a "flipped"
-        // comparison and a negation, and the negation can be merged into the
-        // select. Therefore, it not necessarily any more expensive than an
-        // ordered ("normal") comparison. Whether these optimizations will be
-        // performed is ultimately up to the backend, but at least x86 does
-        // perform them.
-        let s0 = self.select(less_or_nan, int_min, fptosui_result);
-        let s1 = self.select(greater, int_max, s0);
-
-        // Step 3: NaN replacement.
-        // For unsigned types, the above step already yielded int_ty::MIN == 0 if x is NaN.
-        // Therefore we only need to execute this step for signed integer types.
-        if signed {
-            // LLVM has no isNaN predicate, so we use (x == x) instead
-            let cmp = self.fcmp(RealPredicate::RealOEQ, x, x);
-            self.select(cmp, s1, zero)
-        } else {
-            s1
-        }
+        if signed { self.fptosi_sat(x, dest_ty) } else { self.fptoui_sat(x, dest_ty) }
     }
 
     fn icmp(&mut self, op: IntPredicate, lhs: Self::Value, rhs: Self::Value) -> Self::Value;
diff --git a/compiler/rustc_infer/src/infer/at.rs b/compiler/rustc_infer/src/infer/at.rs
index e37c0cf0fd0..00e23864871 100644
--- a/compiler/rustc_infer/src/infer/at.rs
+++ b/compiler/rustc_infer/src/infer/at.rs
@@ -78,6 +78,10 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
             err_count_on_creation: self.err_count_on_creation,
             in_snapshot: self.in_snapshot.clone(),
             universe: self.universe.clone(),
+            normalize_fn_sig_for_diagnostic: self
+                .normalize_fn_sig_for_diagnostic
+                .as_ref()
+                .map(|f| f.clone()),
         }
     }
 }
diff --git a/compiler/rustc_infer/src/infer/error_reporting/mod.rs b/compiler/rustc_infer/src/infer/error_reporting/mod.rs
index ecf75411e5f..7dc4934db09 100644
--- a/compiler/rustc_infer/src/infer/error_reporting/mod.rs
+++ b/compiler/rustc_infer/src/infer/error_reporting/mod.rs
@@ -961,12 +961,23 @@ impl<'a, 'tcx> InferCtxt<'a, 'tcx> {
         }
     }
 
+    fn normalize_fn_sig_for_diagnostic(&self, sig: ty::PolyFnSig<'tcx>) -> ty::PolyFnSig<'tcx> {
+        if let Some(normalize) = &self.normalize_fn_sig_for_diagnostic {
+            normalize(self, sig)
+        } else {
+            sig
+        }
+    }
+
     /// Given two `fn` signatures highlight only sub-parts that are different.
     fn cmp_fn_sig(
         &self,
         sig1: &ty::PolyFnSig<'tcx>,
         sig2: &ty::PolyFnSig<'tcx>,
     ) -> (DiagnosticStyledString, DiagnosticStyledString) {
+        let sig1 = &self.normalize_fn_sig_for_diagnostic(*sig1);
+        let sig2 = &self.normalize_fn_sig_for_diagnostic(*sig2);
+
         let get_lifetimes = |sig| {
             use rustc_hir::def::Namespace;
             let (_, sig, reg) = ty::print::FmtPrinter::new(self.tcx, Namespace::TypeNS)
diff --git a/compiler/rustc_infer/src/infer/mod.rs b/compiler/rustc_infer/src/infer/mod.rs
index 4689ebb6cee..60ebf8b949d 100644
--- a/compiler/rustc_infer/src/infer/mod.rs
+++ b/compiler/rustc_infer/src/infer/mod.rs
@@ -337,6 +337,9 @@ pub struct InferCtxt<'a, 'tcx> {
     /// when we enter into a higher-ranked (`for<..>`) type or trait
     /// bound.
     universe: Cell<ty::UniverseIndex>,
+
+    normalize_fn_sig_for_diagnostic:
+        Option<Lrc<dyn Fn(&InferCtxt<'_, 'tcx>, ty::PolyFnSig<'tcx>) -> ty::PolyFnSig<'tcx>>>,
 }
 
 /// See the `error_reporting` module for more details.
@@ -540,6 +543,8 @@ pub struct InferCtxtBuilder<'tcx> {
     defining_use_anchor: DefiningAnchor,
     considering_regions: bool,
     fresh_typeck_results: Option<RefCell<ty::TypeckResults<'tcx>>>,
+    normalize_fn_sig_for_diagnostic:
+        Option<Lrc<dyn Fn(&InferCtxt<'_, 'tcx>, ty::PolyFnSig<'tcx>) -> ty::PolyFnSig<'tcx>>>,
 }
 
 pub trait TyCtxtInferExt<'tcx> {
@@ -553,6 +558,7 @@ impl<'tcx> TyCtxtInferExt<'tcx> for TyCtxt<'tcx> {
             defining_use_anchor: DefiningAnchor::Error,
             considering_regions: true,
             fresh_typeck_results: None,
+            normalize_fn_sig_for_diagnostic: None,
         }
     }
 }
@@ -582,6 +588,14 @@ impl<'tcx> InferCtxtBuilder<'tcx> {
         self
     }
 
+    pub fn with_normalize_fn_sig_for_diagnostic(
+        mut self,
+        fun: Lrc<dyn Fn(&InferCtxt<'_, 'tcx>, ty::PolyFnSig<'tcx>) -> ty::PolyFnSig<'tcx>>,
+    ) -> Self {
+        self.normalize_fn_sig_for_diagnostic = Some(fun);
+        self
+    }
+
     /// Given a canonical value `C` as a starting point, create an
     /// inference context that contains each of the bound values
     /// within instantiated as a fresh variable. The `f` closure is
@@ -611,6 +625,7 @@ impl<'tcx> InferCtxtBuilder<'tcx> {
             defining_use_anchor,
             considering_regions,
             ref fresh_typeck_results,
+            ref normalize_fn_sig_for_diagnostic,
         } = *self;
         let in_progress_typeck_results = fresh_typeck_results.as_ref();
         f(InferCtxt {
@@ -629,6 +644,9 @@ impl<'tcx> InferCtxtBuilder<'tcx> {
             in_snapshot: Cell::new(false),
             skip_leak_check: Cell::new(false),
             universe: Cell::new(ty::UniverseIndex::ROOT),
+            normalize_fn_sig_for_diagnostic: normalize_fn_sig_for_diagnostic
+                .as_ref()
+                .map(|f| f.clone()),
         })
     }
 }
diff --git a/compiler/rustc_infer/src/infer/sub.rs b/compiler/rustc_infer/src/infer/sub.rs
index b27571275b7..b7eab5d4328 100644
--- a/compiler/rustc_infer/src/infer/sub.rs
+++ b/compiler/rustc_infer/src/infer/sub.rs
@@ -4,6 +4,7 @@ use super::SubregionOrigin;
 use crate::infer::combine::ConstEquateRelation;
 use crate::infer::{TypeVariableOrigin, TypeVariableOriginKind};
 use crate::traits::Obligation;
+use rustc_middle::ty::error::{ExpectedFound, TypeError};
 use rustc_middle::ty::relate::{Cause, Relate, RelateResult, TypeRelation};
 use rustc_middle::ty::visit::TypeVisitable;
 use rustc_middle::ty::TyVar;
@@ -141,17 +142,27 @@ impl<'tcx> TypeRelation<'tcx> for Sub<'_, '_, 'tcx> {
                     Ok(infcx.tcx.mk_ty_var(var))
                 };
                 let (a, b) = if self.a_is_expected { (a, b) } else { (b, a) };
-                let (a, b) = match (a.kind(), b.kind()) {
+                let (ga, gb) = match (a.kind(), b.kind()) {
                     (&ty::Opaque(..), _) => (a, generalize(b, true)?),
                     (_, &ty::Opaque(..)) => (generalize(a, false)?, b),
                     _ => unreachable!(),
                 };
                 self.fields.obligations.extend(
                     infcx
-                        .handle_opaque_type(a, b, true, &self.fields.trace.cause, self.param_env())?
+                        .handle_opaque_type(ga, gb, true, &self.fields.trace.cause, self.param_env())
+                        // Don't leak any generalized type variables out of this
+                        // subtyping relation in the case of a type error.
+                        .map_err(|err| {
+                            let (ga, gb) = self.fields.infcx.resolve_vars_if_possible((ga, gb));
+                            if let TypeError::Sorts(sorts) = err && sorts.expected == ga && sorts.found == gb {
+                                TypeError::Sorts(ExpectedFound { expected: a, found: b })
+                            } else {
+                                err
+                            }
+                        })?
                         .obligations,
                 );
-                Ok(a)
+                Ok(ga)
             }
 
             _ => {
diff --git a/compiler/rustc_middle/src/ty/error.rs b/compiler/rustc_middle/src/ty/error.rs
index ac89bec702e..da564c66a70 100644
--- a/compiler/rustc_middle/src/ty/error.rs
+++ b/compiler/rustc_middle/src/ty/error.rs
@@ -276,10 +276,23 @@ impl<'tcx> Ty<'tcx> {
             }
             ty::Slice(ty) if ty.is_simple_ty() => format!("slice `{}`", self).into(),
             ty::Slice(_) => "slice".into(),
-            ty::RawPtr(_) => "*-ptr".into(),
+            ty::RawPtr(tymut) => {
+                let tymut_string = match tymut.mutbl {
+                    hir::Mutability::Mut => tymut.to_string(),
+                    hir::Mutability::Not => format!("const {}", tymut.ty),
+                };
+
+                if tymut_string != "_" && (tymut.ty.is_simple_text() || tymut_string.len() < "const raw pointer".len()) {
+                    format!("`*{}`", tymut_string).into()
+                } else {
+                    // Unknown type name, it's long or has type arguments
+                    "raw pointer".into()
+                }
+            },
             ty::Ref(_, ty, mutbl) => {
                 let tymut = ty::TypeAndMut { ty, mutbl };
                 let tymut_string = tymut.to_string();
+
                 if tymut_string != "_"
                     && (ty.is_simple_text() || tymut_string.len() < "mutable reference".len())
                 {
diff --git a/compiler/rustc_passes/src/liveness.rs b/compiler/rustc_passes/src/liveness.rs
index 1f22ebc730a..5b79dd3d3ef 100644
--- a/compiler/rustc_passes/src/liveness.rs
+++ b/compiler/rustc_passes/src/liveness.rs
@@ -229,6 +229,19 @@ enum VarKind {
     Upvar(HirId, Symbol),
 }
 
+struct CollectLitsVisitor<'tcx> {
+    lit_exprs: Vec<&'tcx hir::Expr<'tcx>>,
+}
+
+impl<'tcx> Visitor<'tcx> for CollectLitsVisitor<'tcx> {
+    fn visit_expr(&mut self, expr: &'tcx Expr<'tcx>) {
+        if let hir::ExprKind::Lit(_) = expr.kind {
+            self.lit_exprs.push(expr);
+        }
+        intravisit::walk_expr(self, expr);
+    }
+}
+
 struct IrMaps<'tcx> {
     tcx: TyCtxt<'tcx>,
     live_node_map: HirIdMap<LiveNode>,
@@ -1333,7 +1346,7 @@ impl<'a, 'tcx> Liveness<'a, 'tcx> {
 
 impl<'a, 'tcx> Visitor<'tcx> for Liveness<'a, 'tcx> {
     fn visit_local(&mut self, local: &'tcx hir::Local<'tcx>) {
-        self.check_unused_vars_in_pat(&local.pat, None, |spans, hir_id, ln, var| {
+        self.check_unused_vars_in_pat(&local.pat, None, None, |spans, hir_id, ln, var| {
             if local.init.is_some() {
                 self.warn_about_dead_assign(spans, hir_id, ln, var);
             }
@@ -1348,7 +1361,7 @@ impl<'a, 'tcx> Visitor<'tcx> for Liveness<'a, 'tcx> {
     }
 
     fn visit_arm(&mut self, arm: &'tcx hir::Arm<'tcx>) {
-        self.check_unused_vars_in_pat(&arm.pat, None, |_, _, _, _| {});
+        self.check_unused_vars_in_pat(&arm.pat, None, None, |_, _, _, _| {});
         intravisit::walk_arm(self, arm);
     }
 }
@@ -1387,7 +1400,7 @@ fn check_expr<'tcx>(this: &mut Liveness<'_, 'tcx>, expr: &'tcx Expr<'tcx>) {
         }
 
         hir::ExprKind::Let(let_expr) => {
-            this.check_unused_vars_in_pat(let_expr.pat, None, |_, _, _, _| {});
+            this.check_unused_vars_in_pat(let_expr.pat, None, None, |_, _, _, _| {});
         }
 
         // no correctness conditions related to liveness
@@ -1508,13 +1521,18 @@ impl<'tcx> Liveness<'_, 'tcx> {
 
     fn warn_about_unused_args(&self, body: &hir::Body<'_>, entry_ln: LiveNode) {
         for p in body.params {
-            self.check_unused_vars_in_pat(&p.pat, Some(entry_ln), |spans, hir_id, ln, var| {
-                if !self.live_on_entry(ln, var) {
-                    self.report_unused_assign(hir_id, spans, var, |name| {
-                        format!("value passed to `{}` is never read", name)
-                    });
-                }
-            });
+            self.check_unused_vars_in_pat(
+                &p.pat,
+                Some(entry_ln),
+                Some(body),
+                |spans, hir_id, ln, var| {
+                    if !self.live_on_entry(ln, var) {
+                        self.report_unused_assign(hir_id, spans, var, |name| {
+                            format!("value passed to `{}` is never read", name)
+                        });
+                    }
+                },
+            );
         }
     }
 
@@ -1522,6 +1540,7 @@ impl<'tcx> Liveness<'_, 'tcx> {
         &self,
         pat: &hir::Pat<'_>,
         entry_ln: Option<LiveNode>,
+        opt_body: Option<&hir::Body<'_>>,
         on_used_on_entry: impl Fn(Vec<Span>, HirId, LiveNode, Variable),
     ) {
         // In an or-pattern, only consider the variable; any later patterns must have the same
@@ -1549,7 +1568,7 @@ impl<'tcx> Liveness<'_, 'tcx> {
                     hir_ids_and_spans.into_iter().map(|(_, _, ident_span)| ident_span).collect();
                 on_used_on_entry(spans, id, ln, var);
             } else {
-                self.report_unused(hir_ids_and_spans, ln, var, can_remove);
+                self.report_unused(hir_ids_and_spans, ln, var, can_remove, pat, opt_body);
             }
         }
     }
@@ -1561,6 +1580,8 @@ impl<'tcx> Liveness<'_, 'tcx> {
         ln: LiveNode,
         var: Variable,
         can_remove: bool,
+        pat: &hir::Pat<'_>,
+        opt_body: Option<&hir::Body<'_>>,
     ) {
         let first_hir_id = hir_ids_and_spans[0].0;
 
@@ -1664,6 +1685,9 @@ impl<'tcx> Liveness<'_, 'tcx> {
                             .collect::<Vec<_>>(),
                         |lint| {
                             let mut err = lint.build(&format!("unused variable: `{}`", name));
+                            if self.has_added_lit_match_name_span(&name, opt_body, &mut err) {
+                                err.span_label(pat.span, "unused variable");
+                            }
                             err.multipart_suggestion(
                                 "if this is intentional, prefix it with an underscore",
                                 non_shorthands,
@@ -1677,6 +1701,42 @@ impl<'tcx> Liveness<'_, 'tcx> {
         }
     }
 
+    fn has_added_lit_match_name_span(
+        &self,
+        name: &str,
+        opt_body: Option<&hir::Body<'_>>,
+        err: &mut rustc_errors::DiagnosticBuilder<'_, ()>,
+    ) -> bool {
+        let mut has_litstring = false;
+        let Some(opt_body) = opt_body else {return false;};
+        let mut visitor = CollectLitsVisitor { lit_exprs: vec![] };
+        intravisit::walk_body(&mut visitor, opt_body);
+        for lit_expr in visitor.lit_exprs {
+            let hir::ExprKind::Lit(litx) = &lit_expr.kind else { continue };
+            let rustc_ast::LitKind::Str(syb, _) = litx.node else{ continue; };
+            let name_str: &str = syb.as_str();
+            let mut name_pa = String::from("{");
+            name_pa.push_str(&name);
+            name_pa.push('}');
+            if name_str.contains(&name_pa) {
+                err.span_label(
+                    lit_expr.span,
+                    "you might have meant to use string interpolation in this string literal",
+                );
+                err.multipart_suggestion(
+                    "string interpolation only works in `format!` invocations",
+                    vec![
+                        (lit_expr.span.shrink_to_lo(), "format!(".to_string()),
+                        (lit_expr.span.shrink_to_hi(), ")".to_string()),
+                    ],
+                    Applicability::MachineApplicable,
+                );
+                has_litstring = true;
+            }
+        }
+        has_litstring
+    }
+
     fn warn_about_dead_assign(&self, spans: Vec<Span>, hir_id: HirId, ln: LiveNode, var: Variable) {
         if !self.live_on_exit(ln, var) {
             self.report_unused_assign(hir_id, spans, var, |name| {
diff --git a/compiler/rustc_trait_selection/src/traits/engine.rs b/compiler/rustc_trait_selection/src/traits/engine.rs
index 72533a42d80..dba4d4f69da 100644
--- a/compiler/rustc_trait_selection/src/traits/engine.rs
+++ b/compiler/rustc_trait_selection/src/traits/engine.rs
@@ -17,6 +17,7 @@ use rustc_span::Span;
 
 pub trait TraitEngineExt<'tcx> {
     fn new(tcx: TyCtxt<'tcx>) -> Box<Self>;
+    fn new_in_snapshot(tcx: TyCtxt<'tcx>) -> Box<Self>;
 }
 
 impl<'tcx> TraitEngineExt<'tcx> for dyn TraitEngine<'tcx> {
@@ -27,6 +28,14 @@ impl<'tcx> TraitEngineExt<'tcx> for dyn TraitEngine<'tcx> {
             Box::new(FulfillmentContext::new())
         }
     }
+
+    fn new_in_snapshot(tcx: TyCtxt<'tcx>) -> Box<Self> {
+        if tcx.sess.opts.unstable_opts.chalk {
+            Box::new(ChalkFulfillmentContext::new())
+        } else {
+            Box::new(FulfillmentContext::new_in_snapshot())
+        }
+    }
 }
 
 /// Used if you want to have pleasant experience when dealing
@@ -41,6 +50,10 @@ impl<'a, 'tcx> ObligationCtxt<'a, 'tcx> {
         Self { infcx, engine: RefCell::new(<dyn TraitEngine<'_>>::new(infcx.tcx)) }
     }
 
+    pub fn new_in_snapshot(infcx: &'a InferCtxt<'a, 'tcx>) -> Self {
+        Self { infcx, engine: RefCell::new(<dyn TraitEngine<'_>>::new_in_snapshot(infcx.tcx)) }
+    }
+
     pub fn register_obligation(&self, obligation: PredicateObligation<'tcx>) {
         self.engine.borrow_mut().register_predicate_obligation(self.infcx, obligation);
     }
diff --git a/compiler/rustc_trait_selection/src/traits/error_reporting/suggestions.rs b/compiler/rustc_trait_selection/src/traits/error_reporting/suggestions.rs
index 54f01577c5e..02adae5bde1 100644
--- a/compiler/rustc_trait_selection/src/traits/error_reporting/suggestions.rs
+++ b/compiler/rustc_trait_selection/src/traits/error_reporting/suggestions.rs
@@ -20,7 +20,7 @@ use rustc_hir::def_id::DefId;
 use rustc_hir::intravisit::Visitor;
 use rustc_hir::lang_items::LangItem;
 use rustc_hir::{AsyncGeneratorKind, GeneratorKind, Node};
-use rustc_infer::infer::TyCtxtInferExt;
+use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind};
 use rustc_middle::hir::map;
 use rustc_middle::ty::{
     self, suggest_arbitrary_trait_bound, suggest_constraining_type_param, AdtKind, DefIdTree,
@@ -1589,32 +1589,38 @@ impl<'a, 'tcx> InferCtxtExt<'tcx> for InferCtxt<'a, 'tcx> {
         expected: ty::PolyTraitRef<'tcx>,
     ) -> DiagnosticBuilder<'tcx, ErrorGuaranteed> {
         pub(crate) fn build_fn_sig_ty<'tcx>(
-            tcx: TyCtxt<'tcx>,
+            infcx: &InferCtxt<'_, 'tcx>,
             trait_ref: ty::PolyTraitRef<'tcx>,
         ) -> Ty<'tcx> {
             let inputs = trait_ref.skip_binder().substs.type_at(1);
             let sig = match inputs.kind() {
                 ty::Tuple(inputs)
-                    if tcx.fn_trait_kind_from_lang_item(trait_ref.def_id()).is_some() =>
+                    if infcx.tcx.fn_trait_kind_from_lang_item(trait_ref.def_id()).is_some() =>
                 {
-                    tcx.mk_fn_sig(
+                    infcx.tcx.mk_fn_sig(
                         inputs.iter(),
-                        tcx.mk_ty_infer(ty::TyVar(ty::TyVid::from_u32(0))),
+                        infcx.next_ty_var(TypeVariableOrigin {
+                            span: DUMMY_SP,
+                            kind: TypeVariableOriginKind::MiscVariable,
+                        }),
                         false,
                         hir::Unsafety::Normal,
                         abi::Abi::Rust,
                     )
                 }
-                _ => tcx.mk_fn_sig(
+                _ => infcx.tcx.mk_fn_sig(
                     std::iter::once(inputs),
-                    tcx.mk_ty_infer(ty::TyVar(ty::TyVid::from_u32(0))),
+                    infcx.next_ty_var(TypeVariableOrigin {
+                        span: DUMMY_SP,
+                        kind: TypeVariableOriginKind::MiscVariable,
+                    }),
                     false,
                     hir::Unsafety::Normal,
                     abi::Abi::Rust,
                 ),
             };
 
-            tcx.mk_fn_ptr(trait_ref.rebind(sig))
+            infcx.tcx.mk_fn_ptr(trait_ref.rebind(sig))
         }
 
         let argument_kind = match expected.skip_binder().self_ty().kind() {
@@ -1634,11 +1640,10 @@ impl<'a, 'tcx> InferCtxtExt<'tcx> for InferCtxt<'a, 'tcx> {
         let found_span = found_span.unwrap_or(span);
         err.span_label(found_span, "found signature defined here");
 
-        let expected = build_fn_sig_ty(self.tcx, expected);
-        let found = build_fn_sig_ty(self.tcx, found);
+        let expected = build_fn_sig_ty(self, expected);
+        let found = build_fn_sig_ty(self, found);
 
-        let (expected_str, found_str) =
-            self.tcx.infer_ctxt().enter(|infcx| infcx.cmp(expected, found));
+        let (expected_str, found_str) = self.cmp(expected, found);
 
         let signature_kind = format!("{argument_kind} signature");
         err.note_expected_found(&signature_kind, expected_str, &signature_kind, found_str);
diff --git a/compiler/rustc_typeck/src/check/inherited.rs b/compiler/rustc_typeck/src/check/inherited.rs
index f3115fc5c02..1439baf5440 100644
--- a/compiler/rustc_typeck/src/check/inherited.rs
+++ b/compiler/rustc_typeck/src/check/inherited.rs
@@ -1,6 +1,7 @@
 use super::callee::DeferredCallResolution;
 
 use rustc_data_structures::fx::FxHashSet;
+use rustc_data_structures::sync::Lrc;
 use rustc_hir as hir;
 use rustc_hir::def_id::LocalDefId;
 use rustc_hir::HirIdMap;
@@ -12,7 +13,9 @@ use rustc_middle::ty::{self, Ty, TyCtxt};
 use rustc_span::def_id::LocalDefIdMap;
 use rustc_span::{self, Span};
 use rustc_trait_selection::infer::InferCtxtExt as _;
-use rustc_trait_selection::traits::{self, ObligationCause, TraitEngine, TraitEngineExt};
+use rustc_trait_selection::traits::{
+    self, ObligationCause, ObligationCtxt, TraitEngine, TraitEngineExt as _,
+};
 
 use std::cell::RefCell;
 use std::ops::Deref;
@@ -84,7 +87,29 @@ impl<'tcx> Inherited<'_, 'tcx> {
             infcx: tcx
                 .infer_ctxt()
                 .ignoring_regions()
-                .with_fresh_in_progress_typeck_results(hir_owner),
+                .with_fresh_in_progress_typeck_results(hir_owner)
+                .with_normalize_fn_sig_for_diagnostic(Lrc::new(move |infcx, fn_sig| {
+                    if fn_sig.has_escaping_bound_vars() {
+                        return fn_sig;
+                    }
+                    infcx.probe(|_| {
+                        let ocx = ObligationCtxt::new_in_snapshot(infcx);
+                        let normalized_fn_sig = ocx.normalize(
+                            ObligationCause::dummy(),
+                            // FIXME(compiler-errors): This is probably not the right param-env...
+                            infcx.tcx.param_env(def_id),
+                            fn_sig,
+                        );
+                        if ocx.select_all_or_error().is_empty() {
+                            let normalized_fn_sig =
+                                infcx.resolve_vars_if_possible(normalized_fn_sig);
+                            if !normalized_fn_sig.needs_infer() {
+                                return normalized_fn_sig;
+                            }
+                        }
+                        fn_sig
+                    })
+                })),
             def_id,
         }
     }
diff --git a/src/test/ui/asm/type-check-1.stderr b/src/test/ui/asm/type-check-1.stderr
index 162ff1d32bc..1845139659d 100644
--- a/src/test/ui/asm/type-check-1.stderr
+++ b/src/test/ui/asm/type-check-1.stderr
@@ -106,7 +106,7 @@ error[E0308]: mismatched types
   --> $DIR/type-check-1.rs:60:26
    |
 LL |         asm!("{}", const 0 as *mut u8);
-   |                          ^^^^^^^^^^^^ expected integer, found *-ptr
+   |                          ^^^^^^^^^^^^ expected integer, found `*mut u8`
    |
    = note:     expected type `{integer}`
            found raw pointer `*mut u8`
@@ -133,7 +133,7 @@ error[E0308]: mismatched types
   --> $DIR/type-check-1.rs:78:25
    |
 LL | global_asm!("{}", const 0 as *mut u8);
-   |                         ^^^^^^^^^^^^ expected integer, found *-ptr
+   |                         ^^^^^^^^^^^^ expected integer, found `*mut u8`
    |
    = note:     expected type `{integer}`
            found raw pointer `*mut u8`
diff --git a/src/test/ui/dst/dst-bad-coercions.stderr b/src/test/ui/dst/dst-bad-coercions.stderr
index 01f862ed516..0d6f4d0209f 100644
--- a/src/test/ui/dst/dst-bad-coercions.stderr
+++ b/src/test/ui/dst/dst-bad-coercions.stderr
@@ -2,7 +2,7 @@ error[E0308]: mismatched types
   --> $DIR/dst-bad-coercions.rs:14:17
    |
 LL |     let y: &S = x;
-   |            --   ^ expected `&S`, found *-ptr
+   |            --   ^ expected `&S`, found `*const S`
    |            |
    |            expected due to this
    |
@@ -13,7 +13,7 @@ error[E0308]: mismatched types
   --> $DIR/dst-bad-coercions.rs:15:21
    |
 LL |     let y: &dyn T = x;
-   |            ------   ^ expected `&dyn T`, found *-ptr
+   |            ------   ^ expected `&dyn T`, found `*const S`
    |            |
    |            expected due to this
    |
@@ -24,7 +24,7 @@ error[E0308]: mismatched types
   --> $DIR/dst-bad-coercions.rs:19:17
    |
 LL |     let y: &S = x;
-   |            --   ^ expected `&S`, found *-ptr
+   |            --   ^ expected `&S`, found `*mut S`
    |            |
    |            expected due to this
    |
@@ -35,7 +35,7 @@ error[E0308]: mismatched types
   --> $DIR/dst-bad-coercions.rs:20:21
    |
 LL |     let y: &dyn T = x;
-   |            ------   ^ expected `&dyn T`, found *-ptr
+   |            ------   ^ expected `&dyn T`, found `*mut S`
    |            |
    |            expected due to this
    |
diff --git a/src/test/ui/impl-trait/issue-99914.rs b/src/test/ui/impl-trait/issue-99914.rs
new file mode 100644
index 00000000000..4324a0229a6
--- /dev/null
+++ b/src/test/ui/impl-trait/issue-99914.rs
@@ -0,0 +1,13 @@
+// edition:2021
+
+fn main() {}
+
+struct Error;
+struct Okay;
+
+fn foo(t: Result<Okay, Error>) {
+    t.and_then(|t| -> _ { bar(t) });
+    //~^ ERROR mismatched types
+}
+
+async fn bar(t: Okay) {}
diff --git a/src/test/ui/impl-trait/issue-99914.stderr b/src/test/ui/impl-trait/issue-99914.stderr
new file mode 100644
index 00000000000..074d5d58d9a
--- /dev/null
+++ b/src/test/ui/impl-trait/issue-99914.stderr
@@ -0,0 +1,21 @@
+error[E0308]: mismatched types
+  --> $DIR/issue-99914.rs:9:27
+   |
+LL |     t.and_then(|t| -> _ { bar(t) });
+   |                           ^^^^^^ expected enum `Result`, found opaque type
+   |
+note: while checking the return type of the `async fn`
+  --> $DIR/issue-99914.rs:13:23
+   |
+LL | async fn bar(t: Okay) {}
+   |                       ^ checked the `Output` of this `async fn`, found opaque type
+   = note:     expected enum `Result<_, Error>`
+           found opaque type `impl Future<Output = ()>`
+help: try wrapping the expression in `Ok`
+   |
+LL |     t.and_then(|t| -> _ { Ok(bar(t)) });
+   |                           +++      +
+
+error: aborting due to previous error
+
+For more information about this error, try `rustc --explain E0308`.
diff --git a/src/test/ui/mismatched_types/issue-19109.stderr b/src/test/ui/mismatched_types/issue-19109.stderr
index c25e2687b75..5cef64bb169 100644
--- a/src/test/ui/mismatched_types/issue-19109.stderr
+++ b/src/test/ui/mismatched_types/issue-19109.stderr
@@ -4,7 +4,7 @@ error[E0308]: mismatched types
 LL | fn function(t: &mut dyn Trait) {
    |                                - help: try adding a return type: `-> *mut dyn Trait`
 LL |     t as *mut dyn Trait
-   |     ^^^^^^^^^^^^^^^^^^^ expected `()`, found *-ptr
+   |     ^^^^^^^^^^^^^^^^^^^ expected `()`, found `*mut dyn Trait`
    |
    = note: expected unit type `()`
             found raw pointer `*mut dyn Trait`
diff --git a/src/test/ui/mismatched_types/normalize-fn-sig.rs b/src/test/ui/mismatched_types/normalize-fn-sig.rs
new file mode 100644
index 00000000000..1a2093c44f0
--- /dev/null
+++ b/src/test/ui/mismatched_types/normalize-fn-sig.rs
@@ -0,0 +1,16 @@
+trait Foo {
+    type Bar;
+}
+
+impl<T> Foo for T {
+    type Bar = i32;
+}
+
+fn foo<T>(_: <T as Foo>::Bar, _: &'static <T as Foo>::Bar) {}
+
+fn needs_i32_ref_fn(_: fn(&'static i32, i32)) {}
+
+fn main() {
+    needs_i32_ref_fn(foo::<()>);
+    //~^ ERROR mismatched types
+}
diff --git a/src/test/ui/mismatched_types/normalize-fn-sig.stderr b/src/test/ui/mismatched_types/normalize-fn-sig.stderr
new file mode 100644
index 00000000000..6c55f29c5d1
--- /dev/null
+++ b/src/test/ui/mismatched_types/normalize-fn-sig.stderr
@@ -0,0 +1,19 @@
+error[E0308]: mismatched types
+  --> $DIR/normalize-fn-sig.rs:14:22
+   |
+LL |     needs_i32_ref_fn(foo::<()>);
+   |     ---------------- ^^^^^^^^^ expected `&i32`, found `i32`
+   |     |
+   |     arguments to this function are incorrect
+   |
+   = note: expected fn pointer `fn(&'static i32, i32)`
+                 found fn item `fn(i32, &'static i32) {foo::<()>}`
+note: function defined here
+  --> $DIR/normalize-fn-sig.rs:11:4
+   |
+LL | fn needs_i32_ref_fn(_: fn(&'static i32, i32)) {}
+   |    ^^^^^^^^^^^^^^^^ ------------------------
+
+error: aborting due to previous error
+
+For more information about this error, try `rustc --explain E0308`.
diff --git a/src/test/ui/type/issue-100584.rs b/src/test/ui/type/issue-100584.rs
new file mode 100644
index 00000000000..10284656323
--- /dev/null
+++ b/src/test/ui/type/issue-100584.rs
@@ -0,0 +1,15 @@
+#![deny(unused)]
+fn foo(xyza: &str) {
+//~^ ERROR unused variable: `xyza`
+    let _ = "{xyza}";
+}
+
+fn foo3(xyza: &str) {
+//~^ ERROR unused variable: `xyza`
+    let _ = "aaa{xyza}bbb";
+}
+
+fn main() {
+  foo("x");
+  foo3("xx");
+}
diff --git a/src/test/ui/type/issue-100584.stderr b/src/test/ui/type/issue-100584.stderr
new file mode 100644
index 00000000000..e1db14d1f00
--- /dev/null
+++ b/src/test/ui/type/issue-100584.stderr
@@ -0,0 +1,44 @@
+error: unused variable: `xyza`
+  --> $DIR/issue-100584.rs:2:8
+   |
+LL | fn foo(xyza: &str) {
+   |        ^^^^ unused variable
+LL |
+LL |     let _ = "{xyza}";
+   |             -------- you might have meant to use string interpolation in this string literal
+   |
+note: the lint level is defined here
+  --> $DIR/issue-100584.rs:1:9
+   |
+LL | #![deny(unused)]
+   |         ^^^^^^
+   = note: `#[deny(unused_variables)]` implied by `#[deny(unused)]`
+help: string interpolation only works in `format!` invocations
+   |
+LL |     let _ = format!("{xyza}");
+   |             ++++++++        +
+help: if this is intentional, prefix it with an underscore
+   |
+LL | fn foo(_xyza: &str) {
+   |        ~~~~~
+
+error: unused variable: `xyza`
+  --> $DIR/issue-100584.rs:7:9
+   |
+LL | fn foo3(xyza: &str) {
+   |         ^^^^ unused variable
+LL |
+LL |     let _ = "aaa{xyza}bbb";
+   |             -------------- you might have meant to use string interpolation in this string literal
+   |
+help: string interpolation only works in `format!` invocations
+   |
+LL |     let _ = format!("aaa{xyza}bbb");
+   |             ++++++++              +
+help: if this is intentional, prefix it with an underscore
+   |
+LL | fn foo3(_xyza: &str) {
+   |         ~~~~~
+
+error: aborting due to 2 previous errors
+