diff --git a/RELEASES.md b/RELEASES.md
index 87768b2f74e76..4982b54f0f886 100644
--- a/RELEASES.md
+++ b/RELEASES.md
@@ -1,3 +1,96 @@
+Version 1.96.0 (2026-05-28)
+==========================
+
+
+
+Language
+--------
+- [Allow passing `expr` metavariable to `cfg`](https://github.com/rust-lang/rust/pull/146961)
+- [Always coerce never types in tuple expressions](https://github.com/rust-lang/rust/pull/147834)
+- [Avoid incorrect inference guidance of function arguments in rare cases](https://github.com/rust-lang/rust/pull/150316)
+- [Support s390x vector registers in inline assembly](https://github.com/rust-lang/rust/pull/154184)
+- [Allow using constants of type `ManuallyDrop` as patterns (fixing a regression introduced in 1.94.0)](https://github.com/rust-lang/rust/pull/154891)
+
+
+
+Compiler
+--------
+- [Enable link relaxation feature for LoongArch Linux targets](https://github.com/rust-lang/rust/pull/153427)
+- [Update `riscv64gc-unknown-fuchsia` baseline to RVA22 + vector](https://github.com/rust-lang/rust/pull/155072)
+
+
+
+Libraries
+---------
+- [Support iterating over ranges of `NonZero` integers](https://github.com/rust-lang/rust/pull/127534)
+- [refactor 'valid for read/write' definition: exclude null; add that as an exception on individual methods instead](https://github.com/rust-lang/rust/pull/152615)
+- [Fix SGX delayed host lookup via ToSocketAddr](https://github.com/rust-lang/rust/pull/152851)
+
+
+
+Stabilized APIs
+---------------
+
+- [`assert_matches!`](https://doc.rust-lang.org/stable/std/macro.assert_matches.html)
+- [`debug_assert_matches!`](https://doc.rust-lang.org/stable/std/macro.debug_assert_matches.html)
+- [`From for AssertUnwindSafe`](https://doc.rust-lang.org/stable/std/panic/struct.AssertUnwindSafe.html#impl-From%3CT%3E-for-AssertUnwindSafe%3CT%3E)
+- [`From for LazyCell`](https://doc.rust-lang.org/stable/std/cell/struct.LazyCell.html#impl-From%3CT%3E-for-LazyCell%3CT,+F%3E)
+- [`From for LazyLock`](https://doc.rust-lang.org/stable/std/sync/struct.LazyLock.html#impl-From%3CT%3E-for-LazyLock%3CT,+F%3E)
+- [`core::range::RangeToInclusive`](https://doc.rust-lang.org/stable/core/range/struct.RangeToInclusive.html)
+- [`core::range::RangeToInclusiveIter`](https://doc.rust-lang.org/stable/core/range/struct.RangeToInclusiveIter.html)
+- [`core::range::RangeFrom`](https://doc.rust-lang.org/stable/core/ops/struct.RangeFrom.html)
+- [`core::range::RangeFromIter`](https://doc.rust-lang.org/stable/core/ops/struct.RangeFromIter.html)
+- [`core::range::Range`](https://doc.rust-lang.org/stable/std/range/struct.Range.html)
+- [`core::range::RangeIter`](https://doc.rust-lang.org/stable/std/range/struct.RangeIter.html)
+
+
+
+Cargo
+-----
+- [Allow a dependency to specify both a git repository and an alternate registry.](https://github.com/rust-lang/cargo/pull/16810/) Just like with crates.io, the git repository will be used locally, but the registry version will be used when published.
+- [Added `target.'cfg(..)'.rustdocflags` support in configuration.](https://github.com/rust-lang/cargo/pull/16846)
+- Fixed [CVE-2026-5222](https://blog.rust-lang.org/2026/05/25/cve-2026-5222/) and [CVE-2026-5223](https://blog.rust-lang.org/2026/05/25/cve-2026-5223/).
+
+
+
+Rustdoc
+-----
+- [Deprecation notes are now rendered like any other documentation](https://github.com/rust-lang/rust/pull/149931). Previously they used the css `white-space: pre-wrap;` property and stripped any `
` elements from the rendered html, however this caused issues and unintuitive behavior. The new behavior should be more predictable, however some multi-line deprecation notes will now be rendered as as single lines. If this is undesirable, you can use the standard markdown method of forcing a linebreak, which is two spaces followed by a newline (`"\n"`).
+- [Don't emit rustdoc `missing_doc_code_examples` lint on impl items](https://github.com/rust-lang/rust/pull/154048)
+- [Seperate methods and associated functions in sidebar](https://github.com/rust-lang/rust/pull/154644)
+
+
+
+Compatibility Notes
+-------------------
+- [Fix layout of `#[repr(Int)]` enums in some edge cases involving fields of uninhabited zero-sized types](https://github.com/rust-lang/rust/pull/146989)
+- [Prevent unsize-coercing into `Pin` where `Foo` doesn't implement `Deref`. Some such coercions were previously allowed, but produce a type with no useful public API.](https://github.com/rust-lang/rust/pull/149218)
+- [rustc: Stop passing `--allow-undefined` on wasm targets](https://github.com/rust-lang/rust/pull/149868)
+- [Gate the accidentally stabilized `#![reexport_test_harness_main]` attribute](https://github.com/rust-lang/rust/pull/152210)
+- [Error on return-position-impl-trait-in-traits whose types are too private](https://github.com/rust-lang/rust/pull/152543)
+- [Report the `uninhabited_static` lint in dependencies and make it deny-by-default](https://github.com/rust-lang/rust/pull/152853)
+- [Distributed builds now contain non-split debuginfo for windows-gnu](https://github.com/rust-lang/rust/pull/152870)
+ This appears to improve the quality of backtraces. This change has no effect on the defaults for the output of rustc/cargo on these targets.
+- [Check const generic arguments are correctly typed in more positions](https://github.com/rust-lang/rust/pull/152931)
+- [Remove `-Csoft-float`](https://github.com/rust-lang/rust/pull/152973)
+- [Importing structs with `::{self [as name]}`, e.g., `struct S {}; use S::{self as Other};`, is now no longer permitted because `{self}` imports require a module parent.](https://github.com/rust-lang/rust/pull/152996)
+- [For `export_name`, `link_name`, and `link_section` attributes, if multiple of the same attribute is present, the first one now takes precedence.](https://github.com/rust-lang/rust/pull/153041)
+- [Update the minimum external LLVM to 21](https://github.com/rust-lang/rust/pull/153684)
+- On `avr` targets, C's `double` type is 32-bit by default, so [change `c_double` to `f32` on `avr` targets to match](https://github.com/rust-lang/rust/pull/154647). This is a breaking change, but necessary to make `c_double` match C's double.
+
+
+
+Internal Changes
+----------------
+
+These changes do not affect any public interfaces of Rust, but they represent
+significant improvements to the performance or internals of rustc and related
+tools.
+
+- [JSON targets: `aarch64` softfloat targets now have to have `rustc_abi` set to `"softfloat"`](https://github.com/rust-lang/rust/pull/152941)
+- [target specs: stricter checks for LLVM ABI values, and correlate that with `cfg(target_abi)`](https://github.com/rust-lang/rust/pull/153769)
+
+
Version 1.95.0 (2026-04-16)
===========================
diff --git a/compiler/rustc_ast/src/ast.rs b/compiler/rustc_ast/src/ast.rs
index af8ad425507c4..682ba78cddc5f 100644
--- a/compiler/rustc_ast/src/ast.rs
+++ b/compiler/rustc_ast/src/ast.rs
@@ -3497,6 +3497,7 @@ impl AttrItem {
|| self.path == sym::warn
|| self.path == sym::allow
|| self.path == sym::deny
+ || self.path == sym::expect
}
}
diff --git a/compiler/rustc_attr_parsing/src/errors.rs b/compiler/rustc_attr_parsing/src/errors.rs
index d2c9c1b1eb807..56f6d29778414 100644
--- a/compiler/rustc_attr_parsing/src/errors.rs
+++ b/compiler/rustc_attr_parsing/src/errors.rs
@@ -165,6 +165,9 @@ pub(crate) struct InvalidAttrStyle {
#[note("this attribute does not have an `!`, which means it is applied to this {$target}")]
pub target_span: Option,
pub target: &'static str,
+ pub crate_root_path: String,
+ #[help("the crate root is at `{$crate_root_path}`")]
+ pub show_crate_root_help: bool,
}
#[derive(Diagnostic)]
diff --git a/compiler/rustc_attr_parsing/src/parser.rs b/compiler/rustc_attr_parsing/src/parser.rs
index 48790f273adfc..15a9eb91a143d 100644
--- a/compiler/rustc_attr_parsing/src/parser.rs
+++ b/compiler/rustc_attr_parsing/src/parser.rs
@@ -12,7 +12,7 @@ use rustc_ast::{
AttrArgs, Expr, ExprKind, LitKind, MetaItemLit, Path, PathSegment, StmtKind, UnOp,
};
use rustc_ast_pretty::pprust;
-use rustc_errors::{Diag, PResult};
+use rustc_errors::{Applicability, Diag, PResult};
use rustc_hir::{self as hir, AttrPath};
use rustc_parse::exp;
use rustc_parse::parser::{ForceCollect, Parser, PathStyle, Recovery, token_descr};
@@ -410,7 +410,20 @@ fn expr_to_lit<'sess>(
// - `#[foo = include_str!("nonexistent-file.rs")]`:
// results in `ast::ExprKind::Err`.
let msg = "attribute value must be a literal";
- let err = psess.dcx().struct_span_err(span, msg);
+ let mut err = psess.dcx().struct_span_err(span, msg);
+
+ // Suggest adding quotation marks to turn an identifier into a string literal
+ if let ExprKind::Path(None, ref path) = expr.kind
+ && let [segment] = path.segments.as_slice()
+ {
+ err.span_suggestion(
+ expr.span,
+ "try adding quotation marks",
+ &format!("\"{}\"", segment.ident),
+ Applicability::MaybeIncorrect,
+ );
+ }
+
Err(err)
}
}
diff --git a/compiler/rustc_attr_parsing/src/target_checking.rs b/compiler/rustc_attr_parsing/src/target_checking.rs
index d05f1baf63dad..fe4d72b83282b 100644
--- a/compiler/rustc_attr_parsing/src/target_checking.rs
+++ b/compiler/rustc_attr_parsing/src/target_checking.rs
@@ -5,7 +5,7 @@ use rustc_errors::{DiagArgValue, Diagnostic, MultiSpan, StashKey};
use rustc_feature::Features;
use rustc_hir::attrs::AttributeKind;
use rustc_hir::{AttrItem, Attribute, MethodKind, Target};
-use rustc_span::{BytePos, Span, Symbol, sym};
+use rustc_span::{BytePos, FileName, RemapPathScopeComponents, Span, Symbol, sym};
use crate::AttributeParser;
use crate::context::AcceptContext;
@@ -186,6 +186,20 @@ impl<'sess> AttributeParser<'sess> {
let target_span = cx.target_span;
let attr_span = cx.attr_span;
+ let (show_crate_root_help, crate_root_path) = is_used_as_inner
+ .then(|| cx.cx.sess.local_crate_source_file())
+ .flatten()
+ .filter(|src| {
+ !matches!(
+ cx.cx.sess.source_map().span_to_filename(attr_span),
+ FileName::Real(ref name) if name == src
+ )
+ })
+ .map(|src| {
+ (true, src.path(RemapPathScopeComponents::DIAGNOSTICS).display().to_string())
+ })
+ .unwrap_or_default();
+
cx.emit_lint(
rustc_session::lint::builtin::UNUSED_ATTRIBUTES,
crate::errors::InvalidAttrStyle {
@@ -193,6 +207,8 @@ impl<'sess> AttributeParser<'sess> {
is_used_as_inner,
target_span: (!is_used_as_inner).then_some(target_span),
target: target.name(),
+ crate_root_path,
+ show_crate_root_help,
},
attr_span,
);
diff --git a/compiler/rustc_const_eval/src/const_eval/machine.rs b/compiler/rustc_const_eval/src/const_eval/machine.rs
index fb456d80e465f..1dee2f34371e8 100644
--- a/compiler/rustc_const_eval/src/const_eval/machine.rs
+++ b/compiler/rustc_const_eval/src/const_eval/machine.rs
@@ -2,7 +2,7 @@ use std::borrow::{Borrow, Cow};
use std::hash::Hash;
use std::{fmt, mem};
-use rustc_abi::{Align, FIRST_VARIANT, FieldIdx, Size};
+use rustc_abi::{Align, FIRST_VARIANT, FieldIdx, Size, VariantIdx};
use rustc_ast::Mutability;
use rustc_data_structures::fx::{FxHashMap, FxIndexMap, IndexEntry};
use rustc_hir::def_id::{DefId, LocalDefId};
@@ -622,6 +622,75 @@ impl<'tcx> interpret::Machine<'tcx> for CompileTimeMachine<'tcx> {
ecx.write_discriminant(variant_index, dest)?;
}
+ sym::type_id_fields => {
+ let ty = ecx.read_type_id(&args[0])?;
+ let variant_idx = ecx.read_target_usize(&args[1])? as usize;
+
+ let variants_num =
+ ty.ty_adt_def().map(|adt_def| adt_def.variants().len()).unwrap_or(1);
+ if variant_idx >= variants_num {
+ throw_ub!(BoundsCheckFailed {
+ len: variants_num as u64,
+ index: variant_idx as u64
+ });
+ }
+
+ let fields_num = match ty.kind() {
+ ty::Adt(adt_def, _) => {
+ let variant_def = &adt_def.variants()[VariantIdx::from_usize(variant_idx)];
+ variant_def.fields.len()
+ }
+ ty::Tuple(fields) => fields.len(),
+ _ => 0, // Other types have no fields
+ };
+
+ ecx.write_scalar(Scalar::from_target_usize(fields_num as u64, ecx), dest)?;
+ }
+
+ sym::type_id_field_representing_type => {
+ let ty = ecx.read_type_id(&args[0])?;
+ let variant_idx = ecx.read_target_usize(&args[1])? as usize;
+ let field_idx = ecx.read_target_usize(&args[2])? as usize;
+
+ let variants_num =
+ ty.ty_adt_def().map(|adt_def| adt_def.variants().len()).unwrap_or(1);
+ if variant_idx >= variants_num {
+ throw_ub!(BoundsCheckFailed {
+ len: variants_num as u64,
+ index: variant_idx as u64
+ });
+ }
+
+ let fields_num = match ty.kind() {
+ ty::Adt(adt_def, _) => {
+ let variant_def = &adt_def.variants()[VariantIdx::from_usize(variant_idx)];
+ variant_def.fields.len()
+ }
+ ty::Tuple(fields) => fields.len(),
+ _ => 0, // Other types have no fields
+ };
+ if field_idx >= fields_num {
+ throw_ub!(BoundsCheckFailed {
+ len: fields_num as u64,
+ index: field_idx as u64
+ });
+ }
+
+ let frt = Ty::new_field_representing_type(
+ *ecx.tcx,
+ ty,
+ VariantIdx::from_usize(variant_idx),
+ FieldIdx::from_usize(field_idx),
+ );
+ ecx.write_type_id(frt, dest)?;
+ }
+
+ sym::type_id_variants => {
+ let ty = ecx.read_type_id(&args[0])?;
+ let variants_num = ty.ty_adt_def().map(|def| def.variants().len()).unwrap_or(1);
+ ecx.write_scalar(Scalar::from_target_usize(variants_num as u64, ecx), dest)?;
+ }
+
sym::field_offset => {
let frt_ty = instance.args.type_at(0);
ensure_monomorphic_enough(ecx.tcx.tcx, frt_ty)?;
@@ -643,6 +712,20 @@ impl<'tcx> interpret::Machine<'tcx> for CompileTimeMachine<'tcx> {
ecx.write_scalar(Scalar::from_target_usize(offset, ecx), dest)?;
}
+ sym::field_representing_type_actual_type_id => {
+ let frt_ty = ecx.read_type_id(&args[0])?;
+
+ let field_ty = if let ty::Adt(def, args) = frt_ty.kind()
+ && let Some(FieldInfo { ty, .. }) =
+ def.field_representing_type_info(ecx.tcx.tcx, args)
+ {
+ ecx.tcx.erase_and_anonymize_regions(ty)
+ } else {
+ span_bug!(ecx.cur_span(), "expected field representing type, got {frt_ty}")
+ };
+ ecx.write_type_id(field_ty, dest)?;
+ }
+
_ => {
// We haven't handled the intrinsic, let's see if we can use a fallback body.
if ecx.tcx.intrinsic(instance.def_id()).unwrap().must_be_overridden {
diff --git a/compiler/rustc_hir_analysis/src/check/intrinsic.rs b/compiler/rustc_hir_analysis/src/check/intrinsic.rs
index 9059070157fac..c1edbb4fc520d 100644
--- a/compiler/rustc_hir_analysis/src/check/intrinsic.rs
+++ b/compiler/rustc_hir_analysis/src/check/intrinsic.rs
@@ -114,6 +114,7 @@ fn intrinsic_operation_unsafety(tcx: TyCtxt<'_>, intrinsic_id: LocalDefId) -> hi
| sym::fadd_algebraic
| sym::fdiv_algebraic
| sym::field_offset
+ | sym::field_representing_type_actual_type_id
| sym::floorf16
| sym::floorf32
| sym::floorf64
@@ -213,6 +214,9 @@ fn intrinsic_operation_unsafety(tcx: TyCtxt<'_>, intrinsic_id: LocalDefId) -> hi
| sym::truncf128
| sym::type_id
| sym::type_id_eq
+ | sym::type_id_field_representing_type
+ | sym::type_id_fields
+ | sym::type_id_variants
| sym::type_id_vtable
| sym::type_name
| sym::type_of
@@ -319,6 +323,11 @@ pub(crate) fn check_intrinsic_type(
sym::type_name => (1, 0, vec![], Ty::new_static_str(tcx)),
sym::type_id => (1, 0, vec![], type_id_ty()),
sym::type_id_eq => (0, 0, vec![type_id_ty(), type_id_ty()], tcx.types.bool),
+ sym::type_id_field_representing_type => {
+ (0, 0, vec![type_id_ty(), tcx.types.usize, tcx.types.usize], type_id_ty())
+ }
+ sym::type_id_fields => (0, 0, vec![type_id_ty(), tcx.types.usize], tcx.types.usize),
+ sym::type_id_variants => (0, 0, vec![type_id_ty()], tcx.types.usize),
sym::type_id_vtable => {
let dyn_metadata = tcx.require_lang_item(LangItem::DynMetadata, span);
let dyn_metadata_adt_ref = tcx.adt_def(dyn_metadata);
@@ -339,6 +348,7 @@ pub(crate) fn check_intrinsic_type(
vec![type_id_ty()],
tcx.type_of(tcx.lang_items().type_struct().unwrap()).no_bound_vars().unwrap(),
),
+ sym::field_representing_type_actual_type_id => (0, 0, vec![type_id_ty()], type_id_ty()),
sym::offload => (
3,
0,
diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs
index d220651fc9404..ba8ab39ede8cd 100644
--- a/compiler/rustc_span/src/symbol.rs
+++ b/compiler/rustc_span/src/symbol.rs
@@ -948,6 +948,7 @@ symbols! {
field_offset,
field_projections,
field_representing_type,
+ field_representing_type_actual_type_id,
field_representing_type_raw,
field_type,
fields,
@@ -2098,6 +2099,9 @@ symbols! {
type_changing_struct_update,
type_id,
type_id_eq,
+ type_id_field_representing_type,
+ type_id_fields,
+ type_id_variants,
type_id_vtable,
type_info,
type_ir,
diff --git a/library/core/src/any.rs b/library/core/src/any.rs
index 54a6408a06321..fc5f60e0d28b5 100644
--- a/library/core/src/any.rs
+++ b/library/core/src/any.rs
@@ -829,7 +829,7 @@ impl TypeId {
}
}
- fn as_u128(self) -> u128 {
+ pub(crate) fn as_u128(self) -> u128 {
let mut bytes = [0; 16];
// This is a provenance-stripping memcpy.
diff --git a/library/core/src/intrinsics/mod.rs b/library/core/src/intrinsics/mod.rs
index 9ef9c226f3cda..82a7e3be9228e 100644
--- a/library/core/src/intrinsics/mod.rs
+++ b/library/core/src/intrinsics/mod.rs
@@ -2941,11 +2941,57 @@ pub const fn type_id_eq(a: crate::any::TypeId, b: crate::any::TypeId) -> bool {
/// Gets the size of the type represented by this `TypeId`.
///
-/// The stabilized version of this intrinsic is [`core::any::TypeId::size`].
+/// The more user-friendly version of this intrinsic is [`core::any::TypeId::size`].
#[rustc_intrinsic]
#[unstable(feature = "core_intrinsics", issue = "none")]
pub const fn size_of_type_id(_id: crate::any::TypeId) -> Option {
- panic!("`Type::size` can only be called at compile-time")
+ panic!("`TypeId::size` can only be called at compile-time")
+}
+
+/// Gets the number of variants of the type represented by this `TypeId`.
+///
+/// The more user-friendly version of this intrinsic is [`core::any::TypeId::variants`].
+#[rustc_intrinsic]
+#[unstable(feature = "core_intrinsics", issue = "none")]
+pub const fn type_id_variants(_id: crate::any::TypeId) -> usize {
+ panic!("`TypeId::variants` can only be called at compile-time")
+}
+
+/// Gets the number of fields at the given `variant_index` represented by this `TypeId`.
+///
+/// The more user-friendly version of this intrinsic is [`core::any::TypeId::fields`].
+#[rustc_intrinsic]
+#[unstable(feature = "core_intrinsics", issue = "none")]
+pub const fn type_id_fields(_id: crate::any::TypeId, _variant_index: usize) -> usize {
+ panic!("`TypeId::fields` can only be called at compile-time")
+}
+
+/// Gets the [`FieldRepresentingType`]'s `TypeId` at the given index of the type represented by this `TypeId`.
+///
+/// The more user-friendly version of this intrinsic is [`core::any::TypeId::field`].
+///
+/// [`FieldRepresentingType`]: crate::field::FieldRepresentingType
+#[rustc_intrinsic]
+#[unstable(feature = "core_intrinsics", issue = "none")]
+pub const fn type_id_field_representing_type(
+ _id: crate::any::TypeId,
+ _variant_index: usize,
+ _field_index: usize,
+) -> crate::any::TypeId {
+ panic!("`TypeId::field` can only be called at compile-time")
+}
+
+/// Gets the actual field `TypeId` of the [`FieldRepresentingType`]'s `TypeId`.
+///
+/// The more user-friendly version of this intrinsic is [`core::mem::type_info::FieldId::type_id`].
+///
+/// [`FieldRepresentingType`]: crate::field::FieldRepresentingType
+#[rustc_intrinsic]
+#[unstable(feature = "core_intrinsics", issue = "none")]
+pub const fn field_representing_type_actual_type_id(
+ _frt_type_id: crate::any::TypeId,
+) -> crate::any::TypeId {
+ panic!("`FieldId::type_id` can only be called at compile-time")
}
/// Lowers in MIR to `Rvalue::Aggregate` with `AggregateKind::RawPtr`.
diff --git a/library/core/src/mem/type_info.rs b/library/core/src/mem/type_info.rs
index 17c51ccaad858..8e7e51caad1f1 100644
--- a/library/core/src/mem/type_info.rs
+++ b/library/core/src/mem/type_info.rs
@@ -2,6 +2,7 @@
//! runtime or const-eval processable way.
use crate::any::TypeId;
+use crate::fmt;
use crate::intrinsics::{self, type_id, type_of};
use crate::marker::PointeeSized;
use crate::ptr::DynMetadata;
@@ -376,4 +377,201 @@ impl TypeId {
pub const fn size(self) -> Option {
intrinsics::size_of_type_id(self)
}
+
+ /// Returns the number of variants of the type represented by this `TypeId`.
+ ///
+ /// For enums, this is the number of variants. For structs and unions, this is always 1.
+ ///
+ /// ```
+ /// #![feature(type_info)]
+ /// use std::any::TypeId;
+ ///
+ /// assert_eq!(const { TypeId::of::