From f123df5b073d1afa9dcbbe276332901bf6541980 Mon Sep 17 00:00:00 2001 From: Yara Date: Fri, 28 Aug 2026 00:52:19 +0200 Subject: [PATCH 01/16] reflection: adds `TypeId::points_to` and `TypeId::is_mutable_pointer` --- .../src/const_eval/machine.rs | 23 ++++++++++++ .../src/const_eval/type_info.rs | 1 - .../rustc_hir_analysis/src/check/intrinsic.rs | 4 ++ compiler/rustc_span/src/symbol.rs | 2 + library/core/src/intrinsics/mod.rs | 20 ++++++++++ library/core/src/mem/type_info.rs | 37 +++++++++++++++++++ library/coretests/tests/mem/type_info.rs | 18 +++++++++ 7 files changed, 104 insertions(+), 1 deletion(-) diff --git a/compiler/rustc_const_eval/src/const_eval/machine.rs b/compiler/rustc_const_eval/src/const_eval/machine.rs index 7c10dd04f39f3..562173e9ad1f5 100644 --- a/compiler/rustc_const_eval/src/const_eval/machine.rs +++ b/compiler/rustc_const_eval/src/const_eval/machine.rs @@ -612,6 +612,16 @@ impl<'tcx> interpret::Machine<'tcx> for CompileTimeMachine<'tcx> { ecx.write_scalar(Scalar::from_bool(ty.is_signed()), dest)?; } + sym::type_id_points_mutably => { + let ty = ecx.read_type_id(&args[0])?; + let ret = if let ty::RawPtr(_, mutability) = ty.kind() { + mutability.is_mut() + } else { + false + }; + ecx.write_scalar(Scalar::from_bool(ret), dest)?; + } + sym::size_of_type_id => { let ty = ecx.read_type_id(&args[0])?; let layout = ecx.layout_of(ty)?; @@ -692,6 +702,19 @@ impl<'tcx> interpret::Machine<'tcx> for CompileTimeMachine<'tcx> { ecx.write_type_id(frt, dest)?; } + sym::type_id_points_to => { + let ty = ecx.read_type_id(&args[0])?; + let variant_index = if let ty::RawPtr(pointee_ty, _) = ty.kind() { + let (variant, variant_place) = ecx.project_downcast_named(dest, sym::Some)?; + let field_place = ecx.project_field(&variant_place, FieldIdx::ZERO)?; + ecx.write_type_id(*pointee_ty, &field_place)?; + variant + } else { + ecx.project_downcast_named(dest, sym::None)?.0 + }; + ecx.write_discriminant(variant_index, 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); diff --git a/compiler/rustc_const_eval/src/const_eval/type_info.rs b/compiler/rustc_const_eval/src/const_eval/type_info.rs index f6e0208d98835..7f6f23ff6faf2 100644 --- a/compiler/rustc_const_eval/src/const_eval/type_info.rs +++ b/compiler/rustc_const_eval/src/const_eval/type_info.rs @@ -3,7 +3,6 @@ mod adt; use std::borrow::Cow; use rustc_abi::{ExternAbi, FieldIdx}; -use rustc_ast::Mutability; use rustc_hir::attrs::lang_items::LangItem; use rustc_middle::span_bug; use rustc_middle::ty::layout::TyAndLayout; diff --git a/compiler/rustc_hir_analysis/src/check/intrinsic.rs b/compiler/rustc_hir_analysis/src/check/intrinsic.rs index 30d7127ccd8fa..8e5242de82fea 100644 --- a/compiler/rustc_hir_analysis/src/check/intrinsic.rs +++ b/compiler/rustc_hir_analysis/src/check/intrinsic.rs @@ -203,7 +203,9 @@ fn intrinsic_operation_unsafety(tcx: TyCtxt<'_>, intrinsic_id: LocalDefId) -> hi | sym::type_id_field_representing_type | sym::type_id_fields | sym::type_id_generics + | sym::type_id_points_mutably | sym::type_id_is_signed + | sym::type_id_points_to | sym::type_id_variants | sym::type_id_vtable | sym::type_name @@ -318,6 +320,8 @@ pub(crate) fn check_intrinsic_type( } sym::type_id_fields => (0, 0, vec![type_id_ty(), tcx.types.usize], tcx.types.usize), sym::type_id_is_signed => (0, 0, vec![type_id_ty()], tcx.types.bool), + sym::type_id_points_mutably => (0, 0, vec![type_id_ty()], tcx.types.bool), + sym::type_id_points_to => (0, 0, vec![type_id_ty()], Ty::new_option(tcx, type_id_ty())), sym::type_id_variants => (0, 0, vec![type_id_ty()], tcx.types.usize), sym::variant_name => (0, 0, vec![type_id_ty(), tcx.types.usize], Ty::new_static_str(tcx)), sym::variant_non_exhaustive => (0, 0, vec![type_id_ty(), tcx.types.usize], tcx.types.bool), diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 768f1be1cd48d..5ee54cc39a795 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -2169,7 +2169,9 @@ symbols! { type_id_field_representing_type, type_id_fields, type_id_generics, + type_id_points_mutably, type_id_is_signed, + type_id_points_to, type_id_variants, type_id_vtable, type_info, diff --git a/library/core/src/intrinsics/mod.rs b/library/core/src/intrinsics/mod.rs index ca29cf2e19681..3ed3ff0518bf6 100644 --- a/library/core/src/intrinsics/mod.rs +++ b/library/core/src/intrinsics/mod.rs @@ -3111,6 +3111,26 @@ pub fn non_exhaustive(_id: crate::any::TypeId) -> bool; #[rustc_comptime] pub fn type_id_generics(_id: crate::any::TypeId) -> &'static [crate::mem::type_info::Generic]; +// FIXME(reflection): Pick a consistent naming scheme for the intrinsics. Right now we got +// type_id_, _type_id and intrinsics not mentioning type_id at all. +/// Given a `TypeId` that represents a pointer this returns the `TypeId` which that pointer +/// points to. When called on anything else this returns None. +/// +/// The more user-friendly version of this intrinsic is [`core::any::TypeId::points_to`]. +#[rustc_intrinsic] +#[unstable(feature = "core_intrinsics", issue = "none")] +#[rustc_comptime] +pub fn type_id_points_to(_id: crate::any::TypeId) -> Option; + +/// Given a `TypeId` that represents a pointer returns whether that pointer is mutable. +/// When called on anything else this returns `false`. +/// +/// The more user-friendly version of this intrinsic is [`core::any::TypeId::points_mutably`]. +#[rustc_intrinsic] +#[unstable(feature = "core_intrinsics", issue = "none")] +#[rustc_comptime] +pub fn type_id_points_mutably(_id: crate::any::TypeId) -> bool; + /// Lowers in MIR to `Rvalue::Aggregate` with `AggregateKind::RawPtr`. /// /// This is used to implement functions like `slice::from_raw_parts_mut` and diff --git a/library/core/src/mem/type_info.rs b/library/core/src/mem/type_info.rs index 111664775ca8d..ca4eef733a153 100644 --- a/library/core/src/mem/type_info.rs +++ b/library/core/src/mem/type_info.rs @@ -567,6 +567,43 @@ impl TypeId { pub fn generics(self) -> &'static [Generic] { intrinsics::type_id_generics(self) } + + /// Given a `TypeId` that represents a pointer this returns the `TypeId` + /// which that pointer points to. When called on anything else this returns + /// None. + /// + /// ``` + /// #![feature(type_info)] + /// use std::any::TypeId; + /// + /// assert_eq!( + /// const { TypeId::of::<*const i32>().points_to() }, + /// const { Some(TypeId::of::()) }, + /// ); + /// ``` + #[unstable(feature = "type_info", issue = "146922")] + #[rustc_const_unstable(feature = "type_info", issue = "146922")] + #[rustc_comptime] + pub fn points_to(self) -> Option { + intrinsics::type_id_points_to(self) + } + + /// Given a `TypeId` that represents a pointer returns whether that pointer is mutable. + /// When called on anything else this returns `false`. + /// + /// ``` + /// #![feature(type_info)] + /// use std::any::TypeId; + /// + /// assert!(!const { TypeId::of::<*const i32>().points_mutably() }); + /// assert!(const { TypeId::of::<*mut i32>().points_mutably() }); + /// ``` + #[unstable(feature = "type_info", issue = "146922")] + #[rustc_const_unstable(feature = "type_info", issue = "146922")] + #[rustc_comptime] + pub fn points_mutably(self) -> bool { + intrinsics::type_id_points_mutably(self) + } } /// Variant representing type ID. Representing a variant of an enum. diff --git a/library/coretests/tests/mem/type_info.rs b/library/coretests/tests/mem/type_info.rs index f3a69dd857aba..3f180014352d5 100644 --- a/library/coretests/tests/mem/type_info.rs +++ b/library/coretests/tests/mem/type_info.rs @@ -310,6 +310,12 @@ fn test_pointers() { _ => unreachable!(), } + const { + let ty = TypeId::of::<*const u8>(); + assert!(ty.points_to() == Some(TypeId::of::())); + assert!(!ty.points_mutably()); + } + // Mutable pointer. match const { Type::of::<*mut u64>() }.kind { TypeKind::Pointer(pointer) => { @@ -319,6 +325,12 @@ fn test_pointers() { _ => unreachable!(), } + const { + let ty = TypeId::of::<*mut u64>(); + assert!(ty.points_to() == Some(TypeId::of::())); + assert!(ty.points_mutably()); + } + // Wide pointer. match const { Type::of::<*const dyn Any>() }.kind { TypeKind::Pointer(pointer) => { @@ -327,6 +339,12 @@ fn test_pointers() { } _ => unreachable!(), } + + const { + let ty = TypeId::of::<*const dyn Any>(); + assert!(ty.points_to() == Some(TypeId::of::())); + assert!(!ty.points_mutably()); + } } #[test] From 9c36788af12390eb797a3da9748fca1c3ac33517 Mon Sep 17 00:00:00 2001 From: Yara Date: Tue, 1 Sep 2026 01:08:19 +0200 Subject: [PATCH 02/16] reflection: remove field from TypeKind::Pointer variant --- .../src/const_eval/type_info.rs | 35 ++----------------- library/core/src/mem/type_info.rs | 13 +------ library/coretests/tests/mem/type_info.rs | 29 +++------------ 3 files changed, 8 insertions(+), 69 deletions(-) diff --git a/compiler/rustc_const_eval/src/const_eval/type_info.rs b/compiler/rustc_const_eval/src/const_eval/type_info.rs index 7f6f23ff6faf2..fa5f501b0b4ab 100644 --- a/compiler/rustc_const_eval/src/const_eval/type_info.rs +++ b/compiler/rustc_const_eval/src/const_eval/type_info.rs @@ -142,14 +142,9 @@ impl<'tcx> InterpCx<'tcx, CompileTimeMachine<'tcx>> { variant } - ty::RawPtr(ty, mutability) => { - let (variant, variant_place) = + ty::RawPtr(_, _) => { + let (variant, _variant_place) = self.project_downcast_named(&field_dest, sym::Pointer)?; - let pointer_place = - self.project_field(&variant_place, FieldIdx::ZERO)?; - - self.write_pointer_type_info(pointer_place, *ty, *mutability)?; - variant } ty::Dynamic(predicates, region) => { @@ -443,30 +438,4 @@ impl<'tcx> InterpCx<'tcx, CompileTimeMachine<'tcx>> { interp_ok(()) } - - pub(crate) fn write_pointer_type_info( - &mut self, - place: impl Writeable<'tcx, CtfeProvenance>, - ty: Ty<'tcx>, - mutability: Mutability, - ) -> InterpResult<'tcx> { - // Iterate over all fields of `type_info::Pointer`. - for (field_idx, field) in - place.layout().ty.ty_adt_def().unwrap().non_enum_variant().fields.iter_enumerated() - { - let field_place = self.project_field(&place, field_idx)?; - - match field.name { - // Write the `TypeId` of the pointer's inner type to the `ty` field. - sym::pointee => self.write_type_id(ty, &field_place)?, - // Write the boolean representing the pointer's mutability to the `mutable` field. - sym::mutable => { - self.write_scalar(Scalar::from_bool(mutability.is_mut()), &field_place)? - } - other => span_bug!(self.tcx.def_span(field.did), "unimplemented field {other}"), - } - } - - interp_ok(()) - } } diff --git a/library/core/src/mem/type_info.rs b/library/core/src/mem/type_info.rs index ca4eef733a153..13bd3e99389f9 100644 --- a/library/core/src/mem/type_info.rs +++ b/library/core/src/mem/type_info.rs @@ -102,7 +102,7 @@ pub enum TypeKind { /// References. Reference(Reference), /// Pointers. - Pointer(Pointer), + Pointer, /// Function pointers. FnPtr(FnPtr), /// FIXME(#146922): add all the common types @@ -218,17 +218,6 @@ pub struct Reference { pub mutable: bool, } -/// Compile-time type information about pointers. -#[derive(Debug)] -#[non_exhaustive] -#[unstable(feature = "type_info", issue = "146922")] -pub struct Pointer { - /// The type of the value being pointed to. - pub pointee: TypeId, - /// Whether this pointer is mutable or not. - pub mutable: bool, -} - #[derive(Debug)] #[unstable(feature = "type_info", issue = "146922")] /// Function pointer, e.g. fn(u8), diff --git a/library/coretests/tests/mem/type_info.rs b/library/coretests/tests/mem/type_info.rs index 3f180014352d5..5a9e1a77d6564 100644 --- a/library/coretests/tests/mem/type_info.rs +++ b/library/coretests/tests/mem/type_info.rs @@ -301,15 +301,10 @@ fn test_references() { #[test] fn test_pointers() { - // Immutable pointer. - match const { Type::of::<*const u8>() }.kind { - TypeKind::Pointer(pointer) => { - assert_eq!(pointer.pointee, TypeId::of::()); - assert!(!pointer.mutable); - } - _ => unreachable!(), - } + use TypeKind::Pointer; + // Immutable pointer. + let Type { kind: Pointer, .. } = Type::of::<*const u8>() else { panic!() }; const { let ty = TypeId::of::<*const u8>(); assert!(ty.points_to() == Some(TypeId::of::())); @@ -317,14 +312,7 @@ fn test_pointers() { } // Mutable pointer. - match const { Type::of::<*mut u64>() }.kind { - TypeKind::Pointer(pointer) => { - assert_eq!(pointer.pointee, TypeId::of::()); - assert!(pointer.mutable); - } - _ => unreachable!(), - } - + let Type { kind: Pointer, .. } = Type::of::<*mut u64>() else { panic!() }; const { let ty = TypeId::of::<*mut u64>(); assert!(ty.points_to() == Some(TypeId::of::())); @@ -332,14 +320,7 @@ fn test_pointers() { } // Wide pointer. - match const { Type::of::<*const dyn Any>() }.kind { - TypeKind::Pointer(pointer) => { - assert_eq!(pointer.pointee, TypeId::of::()); - assert!(!pointer.mutable); - } - _ => unreachable!(), - } - + let Type { kind: Pointer, .. } = Type::of::<*const dyn Any>() else { panic!() }; const { let ty = TypeId::of::<*const dyn Any>(); assert!(ty.points_to() == Some(TypeId::of::())); From e46de86eee8bf03d2b6e93a6ad56736de17453de Mon Sep 17 00:00:00 2001 From: Yara Date: Tue, 1 Sep 2026 15:02:13 +0200 Subject: [PATCH 03/16] reflection: make `points_to` and `points_mutably` support references --- .../rustc_const_eval/src/const_eval/machine.rs | 15 ++++++++------- library/core/src/mem/type_info.rs | 8 ++++++++ 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/compiler/rustc_const_eval/src/const_eval/machine.rs b/compiler/rustc_const_eval/src/const_eval/machine.rs index 562173e9ad1f5..8e3a94a9c8bea 100644 --- a/compiler/rustc_const_eval/src/const_eval/machine.rs +++ b/compiler/rustc_const_eval/src/const_eval/machine.rs @@ -614,12 +614,11 @@ impl<'tcx> interpret::Machine<'tcx> for CompileTimeMachine<'tcx> { sym::type_id_points_mutably => { let ty = ecx.read_type_id(&args[0])?; - let ret = if let ty::RawPtr(_, mutability) = ty.kind() { - mutability.is_mut() - } else { - false - }; - ecx.write_scalar(Scalar::from_bool(ret), dest)?; + let is_mutable = matches!( + ty.kind(), + ty::RawPtr(_, Mutability::Mut) | &ty::Ref(_, _, Mutability::Mut) + ); + ecx.write_scalar(Scalar::from_bool(is_mutable), dest)?; } sym::size_of_type_id => { @@ -704,7 +703,9 @@ impl<'tcx> interpret::Machine<'tcx> for CompileTimeMachine<'tcx> { sym::type_id_points_to => { let ty = ecx.read_type_id(&args[0])?; - let variant_index = if let ty::RawPtr(pointee_ty, _) = ty.kind() { + let variant_index = if let ty::RawPtr(pointee_ty, _) | ty::Ref(_, pointee_ty, _) = + ty.kind() + { let (variant, variant_place) = ecx.project_downcast_named(dest, sym::Some)?; let field_place = ecx.project_field(&variant_place, FieldIdx::ZERO)?; ecx.write_type_id(*pointee_ty, &field_place)?; diff --git a/library/core/src/mem/type_info.rs b/library/core/src/mem/type_info.rs index 13bd3e99389f9..a68d6711849d5 100644 --- a/library/core/src/mem/type_info.rs +++ b/library/core/src/mem/type_info.rs @@ -566,6 +566,11 @@ impl TypeId { /// use std::any::TypeId; /// /// assert_eq!( + /// const { TypeId::of::<&i32>().points_to() }, + /// const { Some(TypeId::of::()) }, + /// ); + /// + /// assert_eq!( /// const { TypeId::of::<*const i32>().points_to() }, /// const { Some(TypeId::of::()) }, /// ); @@ -584,6 +589,9 @@ impl TypeId { /// #![feature(type_info)] /// use std::any::TypeId; /// + /// assert!(const { TypeId::of::<&mut i32>().points_mutably() }); + /// assert!(const { !TypeId::of::<&i32>().points_mutably() }); + /// /// assert!(!const { TypeId::of::<*const i32>().points_mutably() }); /// assert!(const { TypeId::of::<*mut i32>().points_mutably() }); /// ``` From f4cdf28df504687591d4d38e4960f12397bb5993 Mon Sep 17 00:00:00 2001 From: Yara Date: Tue, 1 Sep 2026 15:02:13 +0200 Subject: [PATCH 04/16] reflection: remove field from TypeKind::Reference --- .../src/const_eval/type_info.rs | 33 ++--------------- library/core/src/mem/type_info.rs | 13 +------ library/coretests/tests/mem/fn_ptr.rs | 8 ++--- library/coretests/tests/mem/type_info.rs | 35 +++++++++---------- 4 files changed, 24 insertions(+), 65 deletions(-) diff --git a/compiler/rustc_const_eval/src/const_eval/type_info.rs b/compiler/rustc_const_eval/src/const_eval/type_info.rs index fa5f501b0b4ab..2ac5abdd78578 100644 --- a/compiler/rustc_const_eval/src/const_eval/type_info.rs +++ b/compiler/rustc_const_eval/src/const_eval/type_info.rs @@ -133,13 +133,9 @@ impl<'tcx> InterpCx<'tcx, CompileTimeMachine<'tcx>> { self.project_downcast_named(&field_dest, sym::Str)?; variant } - ty::Ref(_, ty, mutability) => { - let (variant, variant_place) = + ty::Ref(_, _, _) => { + let (variant, _) = self.project_downcast_named(&field_dest, sym::Reference)?; - let reference_place = - self.project_field(&variant_place, FieldIdx::ZERO)?; - self.write_reference_type_info(reference_place, *ty, *mutability)?; - variant } ty::RawPtr(_, _) => { @@ -295,31 +291,6 @@ impl<'tcx> InterpCx<'tcx, CompileTimeMachine<'tcx>> { interp_ok(()) } - pub(crate) fn write_reference_type_info( - &mut self, - place: impl Writeable<'tcx, CtfeProvenance>, - ty: Ty<'tcx>, - mutability: Mutability, - ) -> InterpResult<'tcx> { - // Iterate over all fields of `type_info::Reference`. - for (field_idx, field) in - place.layout().ty.ty_adt_def().unwrap().non_enum_variant().fields.iter_enumerated() - { - let field_place = self.project_field(&place, field_idx)?; - - match field.name { - // Write the `TypeId` of the reference's inner type to the `ty` field. - sym::pointee => self.write_type_id(ty, &field_place)?, - // Write the boolean representing the reference's mutability to the `mutable` field. - sym::mutable => { - self.write_scalar(Scalar::from_bool(mutability.is_mut()), &field_place)? - } - other => span_bug!(self.tcx.def_span(field.did), "unimplemented field {other}"), - } - } - interp_ok(()) - } - pub(crate) fn write_type_id_generics( &mut self, place: &impl Writeable<'tcx, CtfeProvenance>, diff --git a/library/core/src/mem/type_info.rs b/library/core/src/mem/type_info.rs index a68d6711849d5..1a19f60210509 100644 --- a/library/core/src/mem/type_info.rs +++ b/library/core/src/mem/type_info.rs @@ -100,7 +100,7 @@ pub enum TypeKind { /// String slice type. Str(Str), /// References. - Reference(Reference), + Reference, /// Pointers. Pointer, /// Function pointers. @@ -207,17 +207,6 @@ pub struct Str { // No additional information to provide for now. } -/// Compile-time type information about references. -#[derive(Debug)] -#[non_exhaustive] -#[unstable(feature = "type_info", issue = "146922")] -pub struct Reference { - /// The type of the value being referred to. - pub pointee: TypeId, - /// Whether this reference is mutable or not. - pub mutable: bool, -} - #[derive(Debug)] #[unstable(feature = "type_info", issue = "146922")] /// Function pointer, e.g. fn(u8), diff --git a/library/coretests/tests/mem/fn_ptr.rs b/library/coretests/tests/mem/fn_ptr.rs index 192054bcaf66b..b69478db000b7 100644 --- a/library/coretests/tests/mem/fn_ptr.rs +++ b/library/coretests/tests/mem/fn_ptr.rs @@ -43,16 +43,16 @@ fn test_ref() { if output != UNIT_TY { panic!(); } - let TypeKind::Reference(reference) = ty1.info().kind else { + let TypeKind::Reference = ty1.info().kind else { panic!(); }; - if reference.pointee != U8_TY { + if ty1.points_to().unwrap() != U8_TY { panic!(); } - let TypeKind::Reference(reference) = ty2.info().kind else { + let TypeKind::Reference = ty2.info().kind else { panic!(); }; - if reference.pointee != U8_TY { + if ty1.points_to().unwrap() != U8_TY { panic!(); } } diff --git a/library/coretests/tests/mem/type_info.rs b/library/coretests/tests/mem/type_info.rs index 5a9e1a77d6564..7fe592496f1a7 100644 --- a/library/coretests/tests/mem/type_info.rs +++ b/library/coretests/tests/mem/type_info.rs @@ -271,31 +271,30 @@ fn test_primitives() { #[test] fn test_references() { + use TypeKind::Reference; + // Immutable reference. - match const { Type::of::<&u8>() }.kind { - TypeKind::Reference(reference) => { - assert_eq!(reference.pointee, TypeId::of::()); - assert!(!reference.mutable); - } - _ => unreachable!(), + let Type { kind: Reference, .. } = Type::of::<&u8>() else { panic!() }; + const { + let ty = TypeId::of::<&u8>(); + assert!(ty.points_to() == Some(TypeId::of::())); + assert!(!ty.points_mutably()); } // Mutable references. - match const { Type::of::<&mut u64>() }.kind { - TypeKind::Reference(reference) => { - assert_eq!(reference.pointee, TypeId::of::()); - assert!(reference.mutable); - } - _ => unreachable!(), + let Type { kind: Reference, .. } = Type::of::<&mut u64>() else { panic!() }; + const { + let ty = TypeId::of::<&mut u64>(); + assert!(ty.points_to() == Some(TypeId::of::())); + assert!(ty.points_mutably()); } // Wide references. - match const { Type::of::<&dyn Any>() }.kind { - TypeKind::Reference(reference) => { - assert_eq!(reference.pointee, TypeId::of::()); - assert!(!reference.mutable); - } - _ => unreachable!(), + let Type { kind: Reference, .. } = Type::of::<&dyn Any>() else { panic!() }; + const { + let ty = TypeId::of::<&dyn Any>(); + assert!(ty.points_to() == Some(TypeId::of::())); + assert!(!ty.points_mutably()); } } From 98d68aa8ce1df53a07e4f68c22c9a31a56483664 Mon Sep 17 00:00:00 2001 From: Yara Date: Tue, 1 Sep 2026 23:58:45 +0200 Subject: [PATCH 05/16] reflection: adds `TypeId::function_ptr` returning FnPtr --- compiler/rustc_attr_ir/src/lang_items.rs | 3 + .../src/const_eval/machine.rs | 14 +++- .../rustc_hir_analysis/src/check/intrinsic.rs | 12 ++++ compiler/rustc_span/src/symbol.rs | 1 + library/core/src/intrinsics/mod.rs | 9 +++ library/core/src/mem/type_info.rs | 64 ++++++++++++++++++- 6 files changed, 101 insertions(+), 2 deletions(-) diff --git a/compiler/rustc_attr_ir/src/lang_items.rs b/compiler/rustc_attr_ir/src/lang_items.rs index f2ad7abba755d..c1ad05dc8e4a8 100644 --- a/compiler/rustc_attr_ir/src/lang_items.rs +++ b/compiler/rustc_attr_ir/src/lang_items.rs @@ -465,6 +465,9 @@ language_item_table! { // Used to fallback `{float}` to `f32` when `f32: From<{float}>` From, sym::From, from_trait, Target::Trait, GenericRequirement::Exact(1); FromFn, sym::from, from_fn, Target::Method(MethodKind::Trait { body: false }), GenericRequirement::None; + + // Experimental lang item for `Reflection and comptime`(https://goals.rust-lang.org/2025h2/reflection-and-comptime.html) + FnPtr, sym::FnPtr, fn_ptr, Target::Struct, GenericRequirement::None; } /// The requirement imposed on the generics of a lang item diff --git a/compiler/rustc_const_eval/src/const_eval/machine.rs b/compiler/rustc_const_eval/src/const_eval/machine.rs index 8e3a94a9c8bea..a26a6261251b3 100644 --- a/compiler/rustc_const_eval/src/const_eval/machine.rs +++ b/compiler/rustc_const_eval/src/const_eval/machine.rs @@ -700,7 +700,19 @@ impl<'tcx> interpret::Machine<'tcx> for CompileTimeMachine<'tcx> { ); ecx.write_type_id(frt, dest)?; } - + sym::type_id_function_ptr => { + let ty = ecx.read_type_id(&args[0])?; + let variant_index = if let ty::FnPtr(sig, fn_header) = ty.kind() { + let (variant, variant_place) = ecx.project_downcast_named(dest, sym::Some)?; + let field_place = ecx.project_field(&variant_place, FieldIdx::ZERO)?; + let sig = sig.skip_binder(); + ecx.write_fn_ptr_type_info(field_place, &sig, fn_header)?; + variant + } else { + ecx.project_downcast_named(dest, sym::None)?.0 + }; + ecx.write_discriminant(variant_index, dest)?; + } sym::type_id_points_to => { let ty = ecx.read_type_id(&args[0])?; let variant_index = if let ty::RawPtr(pointee_ty, _) | ty::Ref(_, pointee_ty, _) = diff --git a/compiler/rustc_hir_analysis/src/check/intrinsic.rs b/compiler/rustc_hir_analysis/src/check/intrinsic.rs index 8e5242de82fea..93544c383f699 100644 --- a/compiler/rustc_hir_analysis/src/check/intrinsic.rs +++ b/compiler/rustc_hir_analysis/src/check/intrinsic.rs @@ -202,6 +202,7 @@ fn intrinsic_operation_unsafety(tcx: TyCtxt<'_>, intrinsic_id: LocalDefId) -> hi | sym::type_id_eq | sym::type_id_field_representing_type | sym::type_id_fields + | sym::type_id_function_ptr | sym::type_id_generics | sym::type_id_points_mutably | sym::type_id_is_signed @@ -319,6 +320,17 @@ pub(crate) fn check_intrinsic_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_function_ptr => { + let fn_ptr = tcx.require_lang_item(LangItem::FnPtr, span); + let fn_ptr_adt_ref = tcx.adt_def(fn_ptr); + let fn_ptr_ty = Ty::new_adt(tcx, fn_ptr_adt_ref, ty::List::empty()); + + let option = tcx.require_lang_item(LangItem::Option, span); + let option_adt_ref = tcx.adt_def(option); + let option_args = tcx.mk_args(&[fn_ptr_ty.into()]); + let option_fn_ptr_ty = Ty::new_adt(tcx, option_adt_ref, option_args); + (0, 0, vec![type_id_ty()], option_fn_ptr_ty) + } sym::type_id_is_signed => (0, 0, vec![type_id_ty()], tcx.types.bool), sym::type_id_points_mutably => (0, 0, vec![type_id_ty()], tcx.types.bool), sym::type_id_points_to => (0, 0, vec![type_id_ty()], Ty::new_option(tcx, type_id_ty())), diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 5ee54cc39a795..b2e2cd46a4291 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -2168,6 +2168,7 @@ symbols! { type_id_eq, type_id_field_representing_type, type_id_fields, + type_id_function_ptr, type_id_generics, type_id_points_mutably, type_id_is_signed, diff --git a/library/core/src/intrinsics/mod.rs b/library/core/src/intrinsics/mod.rs index 3ed3ff0518bf6..52dd5cc024cb8 100644 --- a/library/core/src/intrinsics/mod.rs +++ b/library/core/src/intrinsics/mod.rs @@ -3098,6 +3098,15 @@ pub fn field_representing_type_name(_frt_type_id: crate::any::TypeId) -> &'stati #[rustc_comptime] pub fn field_representing_type_offset(_frt_type_id: crate::any::TypeId) -> usize; +/// Given a `TypeId` that represents a function pointer returns an [`core::mem::type_info::FnPtr`]. +/// When called on something else this returns `None`. +/// +/// The more user-friendly version of this intrinsic is [`core::any::TypeId::function_ptr`]. +#[rustc_intrinsic] +#[unstable(feature = "core_intrinsics", issue = "none")] +#[rustc_comptime] +pub fn type_id_function_ptr(_type_id: crate::any::TypeId) -> Option; + /// Checks whether this type is non-exhaustive. #[rustc_intrinsic] #[unstable(feature = "core_intrinsics", issue = "none")] diff --git a/library/core/src/mem/type_info.rs b/library/core/src/mem/type_info.rs index 1a19f60210509..abf42f5c2c802 100644 --- a/library/core/src/mem/type_info.rs +++ b/library/core/src/mem/type_info.rs @@ -106,6 +106,8 @@ pub enum TypeKind { /// Function pointers. FnPtr(FnPtr), /// FIXME(#146922): add all the common types + /// non exhaustive list: + /// - Never Other, } @@ -208,6 +210,7 @@ pub struct Str { } #[derive(Debug)] +#[lang = "FnPtr"] #[unstable(feature = "type_info", issue = "146922")] /// Function pointer, e.g. fn(u8), pub struct FnPtr { @@ -238,12 +241,36 @@ pub struct FnPtr { impl FnPtr { /// Returns the splatted function argument index, or `None` if no argument is splatted. + /// + /// e.g. in `fn overload(a: u8, #[rustc_splat] b: (f32, usize))` the index is 1, + /// and it can be called as `overload(a, 1.0, 2)`. pub const fn splatted(&self) -> Option { if self.is_splatted { Some(self.splatted_index) } else { None } } + /// Whether this function is variadic, e.g. extern "C" fn add(n: usize, mut args: ...); + pub const fn is_variadic(&self) -> Option { + if self.is_splatted { Some(self.splatted_index) } else { None } + } + /// whether this refers to an unsafe function. + pub const fn is_unsafe(&self) -> bool { + self.unsafety + } + /// Returns the application binary interface. For example extern "C". + pub const fn abi(&self) -> Abi { + self.abi + } + /// The types of the functions parameters + pub const fn inputs(&self) -> &'static [TypeId] { + self.inputs + } + /// List of the types returned by the function. For a function with no output + /// specified this returns `TypeId::of<()>`. + pub const fn output(&self) -> TypeId { + self.output + } } -#[derive(Debug, Default)] +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq)] #[non_exhaustive] #[unstable(feature = "type_info", issue = "146922")] /// Abi of [FnPtr] @@ -590,6 +617,41 @@ impl TypeId { pub fn points_mutably(self) -> bool { intrinsics::type_id_points_mutably(self) } + + /// Given a `TypeId` that represents a function pointer returns an + /// [`FnPtr`]. When called on something else this returns `None`. + /// ``` + /// #![feature(type_info)] + /// use std::any::TypeId; + /// use std::mem::type_info::{Abi, FnPtr}; + /// + /// const F: FnPtr = TypeId::of:: usize>() + /// .function_ptr() + /// .expect("TypeId of a function ptr"); + /// + /// assert!(F.inputs() == [TypeId::of::(), TypeId::of::()]); + /// assert!(F.output() == TypeId::of::()); + /// assert!(F.abi() == Abi::default()); + /// ``` + /// ``` + /// #![feature(type_info)] + /// # use std::any::TypeId; + /// # use std::mem::type_info::{Abi, FnPtr}; + /// # + /// const F: FnPtr = TypeId::of::() + /// .function_ptr() + /// .expect("TypeId of a function ptr"); + /// + /// assert!(F.inputs() == []); + /// assert!(F.output() == TypeId::of::<()>()); + /// assert!(F.abi() == Abi::default()); + /// ``` + #[unstable(feature = "type_info", issue = "146922")] + #[rustc_const_unstable(feature = "type_info", issue = "146922")] + #[rustc_comptime] + pub fn function_ptr(self) -> Option { + intrinsics::type_id_function_ptr(self) + } } /// Variant representing type ID. Representing a variant of an enum. From 06fa642f235127eb3287d887c6f9740f55da0af3 Mon Sep 17 00:00:00 2001 From: Yara Date: Fri, 4 Sep 2026 12:09:29 +0200 Subject: [PATCH 06/16] reflection: remove field from TypeKind::FnPtr --- .../src/const_eval/machine.rs | 2 +- .../src/const_eval/type_info.rs | 13 +- .../rustc_hir_analysis/src/check/intrinsic.rs | 2 +- compiler/rustc_span/src/symbol.rs | 4 +- library/core/src/mem/type_info.rs | 35 +-- library/coretests/tests/mem/fn_ptr.rs | 250 +++++------------- 6 files changed, 77 insertions(+), 229 deletions(-) diff --git a/compiler/rustc_const_eval/src/const_eval/machine.rs b/compiler/rustc_const_eval/src/const_eval/machine.rs index a26a6261251b3..ce4c8497463c8 100644 --- a/compiler/rustc_const_eval/src/const_eval/machine.rs +++ b/compiler/rustc_const_eval/src/const_eval/machine.rs @@ -705,7 +705,7 @@ impl<'tcx> interpret::Machine<'tcx> for CompileTimeMachine<'tcx> { let variant_index = if let ty::FnPtr(sig, fn_header) = ty.kind() { let (variant, variant_place) = ecx.project_downcast_named(dest, sym::Some)?; let field_place = ecx.project_field(&variant_place, FieldIdx::ZERO)?; - let sig = sig.skip_binder(); + let sig = sig.skip_binder(); // FIXME: handle lifetime bounds ecx.write_fn_ptr_type_info(field_place, &sig, fn_header)?; variant } else { diff --git a/compiler/rustc_const_eval/src/const_eval/type_info.rs b/compiler/rustc_const_eval/src/const_eval/type_info.rs index 2ac5abdd78578..8d93aee57a518 100644 --- a/compiler/rustc_const_eval/src/const_eval/type_info.rs +++ b/compiler/rustc_const_eval/src/const_eval/type_info.rs @@ -150,16 +150,9 @@ impl<'tcx> InterpCx<'tcx, CompileTimeMachine<'tcx>> { self.write_dyn_trait_type_info(dyn_place, *predicates, *region)?; variant } - ty::FnPtr(sig, fn_header) => { - let (variant, variant_place) = + ty::FnPtr(_, _) => { + let (variant, _) = self.project_downcast_named(&field_dest, sym::FnPtr)?; - let fn_ptr_place = - self.project_field(&variant_place, FieldIdx::ZERO)?; - - // FIXME: handle lifetime bounds - let sig = sig.skip_binder(); - - self.write_fn_ptr_type_info(fn_ptr_place, &sig, fn_header)?; variant } ty::Foreign(_) @@ -348,7 +341,7 @@ impl<'tcx> InterpCx<'tcx, CompileTimeMachine<'tcx>> { let field_place = self.project_field(&place, field_idx)?; match field.name { - sym::unsafety => { + sym::is_unsafe => { self.write_scalar(Scalar::from_bool(!fn_sig_kind.is_safe()), &field_place)?; } sym::abi => match fn_sig_kind.abi() { diff --git a/compiler/rustc_hir_analysis/src/check/intrinsic.rs b/compiler/rustc_hir_analysis/src/check/intrinsic.rs index 93544c383f699..61f72ee4b5de1 100644 --- a/compiler/rustc_hir_analysis/src/check/intrinsic.rs +++ b/compiler/rustc_hir_analysis/src/check/intrinsic.rs @@ -204,8 +204,8 @@ fn intrinsic_operation_unsafety(tcx: TyCtxt<'_>, intrinsic_id: LocalDefId) -> hi | sym::type_id_fields | sym::type_id_function_ptr | sym::type_id_generics - | sym::type_id_points_mutably | sym::type_id_is_signed + | sym::type_id_points_mutably | sym::type_id_points_to | sym::type_id_variants | sym::type_id_vtable diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index b2e2cd46a4291..87ff02d9aa58b 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -1152,6 +1152,7 @@ symbols! { is, is_auto, is_splatted, + is_unsafe, is_val_statically_known, isa_attribute, isize, @@ -2170,8 +2171,8 @@ symbols! { type_id_fields, type_id_function_ptr, type_id_generics, - type_id_points_mutably, type_id_is_signed, + type_id_points_mutably, type_id_points_to, type_id_variants, type_id_vtable, @@ -2264,7 +2265,6 @@ symbols! { unsafe_no_drop_flag, unsafe_pinned, unsafe_unpin, - unsafety, unsize, unsized_const_param_ty, unsized_const_params, diff --git a/library/core/src/mem/type_info.rs b/library/core/src/mem/type_info.rs index abf42f5c2c802..1f38339a7421b 100644 --- a/library/core/src/mem/type_info.rs +++ b/library/core/src/mem/type_info.rs @@ -104,7 +104,7 @@ pub enum TypeKind { /// Pointers. Pointer, /// Function pointers. - FnPtr(FnPtr), + FnPtr, /// FIXME(#146922): add all the common types /// non exhaustive list: /// - Never @@ -214,29 +214,16 @@ pub struct Str { #[unstable(feature = "type_info", issue = "146922")] /// Function pointer, e.g. fn(u8), pub struct FnPtr { - /// Unsafety, true is unsafe - pub unsafety: bool, - - /// Abi, e.g. extern "C" - pub abi: Abi, - - /// Function inputs - pub inputs: &'static [TypeId], - - /// Function return type, default is TypeId::of::<()> - pub output: TypeId, - - /// Vardiadic function, e.g. extern "C" fn add(n: usize, mut args: ...); - pub variadic: bool, - + is_unsafe: bool, + abi: Abi, + inputs: &'static [TypeId], + output: TypeId, + variadic: bool, // FIXME(splat): should these fields be private, or merged into an Option? /// Is any function argument splatted? - pub is_splatted: bool, + is_splatted: bool, - /// The index of the splatted function argument in `inputs`, only valid if `is_splatted` is true. - /// e.g. in `fn overload(a: u8, #[rustc_splat] b: (f32, usize))` the index is 1, and it can be called - /// as `overload(a, 1.0, 2)`. - pub splatted_index: u8, + splatted_index: u8, } impl FnPtr { @@ -248,12 +235,12 @@ impl FnPtr { if self.is_splatted { Some(self.splatted_index) } else { None } } /// Whether this function is variadic, e.g. extern "C" fn add(n: usize, mut args: ...); - pub const fn is_variadic(&self) -> Option { - if self.is_splatted { Some(self.splatted_index) } else { None } + pub const fn is_variadic(&self) -> bool { + self.variadic } /// whether this refers to an unsafe function. pub const fn is_unsafe(&self) -> bool { - self.unsafety + self.is_unsafe } /// Returns the application binary interface. For example extern "C". pub const fn abi(&self) -> Abi { diff --git a/library/coretests/tests/mem/fn_ptr.rs b/library/coretests/tests/mem/fn_ptr.rs index b69478db000b7..862048e4090d4 100644 --- a/library/coretests/tests/mem/fn_ptr.rs +++ b/library/coretests/tests/mem/fn_ptr.rs @@ -3,233 +3,101 @@ use std::mem::type_info::{Abi, FnPtr, Type, TypeKind}; const STRING_TY: TypeId = const { TypeId::of::() }; const U8_TY: TypeId = const { TypeId::of::() }; -const _U8_REF_TY: TypeId = const { TypeId::of::<&u8>() }; const UNIT_TY: TypeId = const { TypeId::of::<()>() }; const TUPLE_STRING_U8_TY: TypeId = const { TypeId::of::<(String, u8)>() }; #[test] fn test_fn_ptrs() { - let TypeKind::FnPtr(FnPtr { - unsafety: false, - abi: Abi::ExternRust, - inputs: &[], - output, - variadic: false, - is_splatted: false, - splatted_index: _, - }) = (const { Type::of::().kind }) - else { - panic!(); - }; - assert_eq!(output, UNIT_TY); + let f = const { TypeId::of::().function_ptr().unwrap() }; + assert_eq!(f.is_unsafe(), false); + assert_eq!(f.abi(), Abi::ExternRust); + assert_eq!(f.inputs(), &[]); + assert_eq!(f.output(), UNIT_TY); + assert_eq!(f.is_variadic(), false); + assert_eq!(f.splatted(), None); } + +#[test] +fn test_typekind() { + assert!(matches!(const { Type::of::().kind }, TypeKind::FnPtr)); + assert!(matches!(const { Type::of::().kind }, TypeKind::FnPtr)); + assert!(matches!(const { Type::of::().kind }, TypeKind::FnPtr)); + assert!(matches!(const { Type::of::().kind }, TypeKind::FnPtr)); + assert!(matches!( + const { Type::of::().kind }, + TypeKind::FnPtr + )); +} + #[test] fn test_ref() { - const { - // references are tricky because the lifetimes give the references different type ids - // so we check the pointees instead - let TypeKind::FnPtr(FnPtr { - unsafety: false, - abi: Abi::ExternRust, - inputs: &[ty1, ty2], - output, - variadic: false, - is_splatted: false, - splatted_index: _, - }) = (const { Type::of::().kind }) - else { - panic!(); - }; - if output != UNIT_TY { - panic!(); - } - let TypeKind::Reference = ty1.info().kind else { - panic!(); - }; - if ty1.points_to().unwrap() != U8_TY { - panic!(); - } - let TypeKind::Reference = ty2.info().kind else { - panic!(); - }; - if ty1.points_to().unwrap() != U8_TY { - panic!(); - } - } + // references are tricky because the lifetimes give the references different type ids + // so we check the pointees instead + const F: FnPtr = TypeId::of::().function_ptr().unwrap(); + assert_eq!(const { F.inputs()[0].points_to() }, Some(U8_TY)); + assert_eq!(const { F.inputs()[1].points_to() }, Some(U8_TY)); } #[test] fn test_unsafe() { - let TypeKind::FnPtr(FnPtr { - unsafety: true, - abi: Abi::ExternRust, - inputs: &[], - output, - variadic: false, - is_splatted: false, - splatted_index: _, - }) = (const { Type::of::().kind }) - else { - panic!(); - }; - assert_eq!(output, UNIT_TY); + assert_eq!(const { TypeId::of::().function_ptr() }.unwrap().is_unsafe(), true); } + #[test] fn test_abi() { - let TypeKind::FnPtr(FnPtr { - unsafety: false, - abi: Abi::ExternRust, - inputs: &[], - output, - variadic: false, - is_splatted: false, - splatted_index: _, - }) = (const { Type::of::().kind }) - else { - panic!(); - }; - assert_eq!(output, UNIT_TY); + assert_eq!( + const { TypeId::of::().function_ptr() }.unwrap().abi(), + Abi::ExternRust + ); - let TypeKind::FnPtr(FnPtr { - unsafety: false, - abi: Abi::ExternC, - inputs: &[], - output, - variadic: false, - is_splatted: false, - splatted_index: _, - }) = (const { Type::of::().kind }) - else { - panic!(); - }; - assert_eq!(output, UNIT_TY); + assert_eq!( + const { TypeId::of::().function_ptr() }.unwrap().abi(), + Abi::ExternC + ); - let TypeKind::FnPtr(FnPtr { - unsafety: true, - abi: Abi::Named("system"), - inputs: &[], - output, - variadic: false, - is_splatted: false, - splatted_index: _, - }) = (const { Type::of::().kind }) - else { - panic!(); - }; - assert_eq!(output, UNIT_TY); + assert_eq!( + const { TypeId::of::().function_ptr() }.unwrap().abi(), + Abi::Named("system") + ); } #[test] fn test_inputs() { - let TypeKind::FnPtr(FnPtr { - unsafety: false, - abi: Abi::ExternRust, - inputs: &[ty1, ty2], - output, - variadic: false, - is_splatted: false, - splatted_index: _, - }) = (const { Type::of::().kind }) - else { - panic!(); - }; - assert_eq!(output, UNIT_TY); - assert_eq!(ty1, STRING_TY); - assert_eq!(ty2, U8_TY); + assert_eq!( + const { TypeId::of::().function_ptr() }.unwrap().inputs(), + [STRING_TY, U8_TY] + ); - let TypeKind::FnPtr(FnPtr { - unsafety: false, - abi: Abi::ExternRust, - inputs: &[ty1, ty2], - output, - variadic: false, - is_splatted: false, - splatted_index: _, - }) = (const { Type::of::().kind }) - else { - panic!(); - }; - assert_eq!(output, UNIT_TY); - assert_eq!(ty1, STRING_TY); - assert_eq!(ty2, U8_TY); + assert_eq!( + const { TypeId::of::().function_ptr() }.unwrap().inputs(), + [STRING_TY, U8_TY] + ); } #[test] fn test_output() { - let TypeKind::FnPtr(FnPtr { - unsafety: false, - abi: Abi::ExternRust, - inputs: &[], - output, - variadic: false, - is_splatted: false, - splatted_index: _, - }) = (const { Type::of:: u8>().kind }) - else { - panic!(); - }; - assert_eq!(output, U8_TY); + let f = const { TypeId::of:: u8>().function_ptr() }.unwrap(); + assert_eq!(f.output(), U8_TY); } #[test] fn test_variadic() { - let TypeKind::FnPtr(FnPtr { - unsafety: false, - abi: Abi::ExternC, - inputs: [ty1], - output, - variadic: true, - is_splatted: false, - splatted_index: _, - }) = &(const { Type::of::().kind }) - else { - panic!(); - }; - assert_eq!(output, &UNIT_TY); - assert_eq!(*ty1, U8_TY); + let f = const { TypeId::of::().function_ptr() }.unwrap(); + assert_eq!(f.abi(), Abi::ExternC); + assert_eq!(f.inputs(), [U8_TY]); + assert_eq!(f.is_variadic(), true); } #[test] fn test_splat() { - #[rustfmt::skip] - let TypeKind::FnPtr(fn_ptr_ty) = &(const { Type::of::().kind }) else { - panic!(); - }; - let FnPtr { - unsafety: false, - abi: Abi::ExternRust, - inputs: [ty1], - output, - variadic: false, - is_splatted: true, - splatted_index: 0, - } = fn_ptr_ty - else { - panic!(); - }; - assert_eq!(output, &UNIT_TY); - assert_eq!(*ty1, TUPLE_STRING_U8_TY); - assert_eq!(fn_ptr_ty.splatted(), Some(0)); + let f = const { TypeId::of::().function_ptr() }.unwrap(); + assert_eq!(f.inputs(), [TUPLE_STRING_U8_TY]); + assert_eq!(f.splatted(), Some(0)); } #[test] fn test_not_splat() { - let TypeKind::FnPtr(fn_ptr_ty) = &(const { Type::of::().kind }) else { - panic!(); - }; - let FnPtr { - unsafety: false, - abi: Abi::ExternRust, - inputs: [ty1], - output, - variadic: false, - is_splatted: false, - splatted_index: _, - } = fn_ptr_ty - else { - panic!(); - }; - assert_eq!(output, &UNIT_TY); - assert_eq!(*ty1, TUPLE_STRING_U8_TY); - assert_eq!(fn_ptr_ty.splatted(), None); + let f = const { TypeId::of::().function_ptr() }.unwrap(); + assert_eq!(f.inputs(), [TUPLE_STRING_U8_TY]); + assert_eq!(f.splatted(), None); } From 0238ef92794018efe672439a5f6ed22bd58bbc75 Mon Sep 17 00:00:00 2001 From: Matyas Susits Date: Thu, 3 Sep 2026 16:42:07 +0200 Subject: [PATCH 07/16] change compile-flags for asm ui tests to not embed bitcode or use LTO --- tests/ui/asm/aarch64/srcloc.rs | 2 +- tests/ui/asm/inline-syntax.arm.stderr | 11 +---------- tests/ui/asm/inline-syntax.rs | 5 ++--- tests/ui/asm/riscv/riscv32e-registers.rs | 2 +- tests/ui/asm/x86_64/srcloc.rs | 2 +- 5 files changed, 6 insertions(+), 16 deletions(-) diff --git a/tests/ui/asm/aarch64/srcloc.rs b/tests/ui/asm/aarch64/srcloc.rs index 91a2ef3514aee..b5e77c4023970 100644 --- a/tests/ui/asm/aarch64/srcloc.rs +++ b/tests/ui/asm/aarch64/srcloc.rs @@ -1,7 +1,7 @@ //@ add-minicore //@ build-fail //@ needs-asm-support -//@ compile-flags: --target aarch64-unknown-linux-gnu -Ccodegen-units=1 +//@ compile-flags: --target aarch64-unknown-linux-gnu -Ccodegen-units=1 -Cembed-bitcode=false -Clto=no //@ needs-llvm-components: aarch64 //@ ignore-backends: gcc #![crate_type = "lib"] diff --git a/tests/ui/asm/inline-syntax.arm.stderr b/tests/ui/asm/inline-syntax.arm.stderr index 5b193d26c8776..315f97bb09de9 100644 --- a/tests/ui/asm/inline-syntax.arm.stderr +++ b/tests/ui/asm/inline-syntax.arm.stderr @@ -6,15 +6,6 @@ note: instantiated into assembly here LL | .intel_syntax noprefix | ^ -error: unknown directive - | -note: instantiated into assembly here - --> :1:1 - | -LL | .intel_syntax noprefix - | ^ - = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` - error: unknown directive --> $DIR/inline-syntax.rs:21:15 | @@ -87,5 +78,5 @@ note: instantiated into assembly here LL | .intel_syntax noprefix | ^ -error: aborting due to 8 previous errors +error: aborting due to 7 previous errors diff --git a/tests/ui/asm/inline-syntax.rs b/tests/ui/asm/inline-syntax.rs index 63395c1096c09..d7c9fc8972cb7 100644 --- a/tests/ui/asm/inline-syntax.rs +++ b/tests/ui/asm/inline-syntax.rs @@ -1,9 +1,9 @@ //@ add-minicore //@ revisions: x86_64 arm -//@[x86_64] compile-flags: --target x86_64-unknown-linux-gnu +//@[x86_64] compile-flags: --target x86_64-unknown-linux-gnu -Cembed-bitcode=false -Clto=no //@[x86_64] check-pass //@[x86_64] needs-llvm-components: x86 -//@[arm] compile-flags: --target armv7-unknown-linux-gnueabihf +//@[arm] compile-flags: --target armv7-unknown-linux-gnueabihf -Cembed-bitcode=false -Clto=no //@[arm] build-fail //@[arm] needs-llvm-components: arm //@[arm] min-llvm-version: 23 @@ -49,4 +49,3 @@ global_asm!(".intel_syntax noprefix", "nop"); // Global assembly errors don't have line numbers, so no error on ARM. //[arm]~? ERROR unknown directive -//[arm]~? ERROR unknown directive diff --git a/tests/ui/asm/riscv/riscv32e-registers.rs b/tests/ui/asm/riscv/riscv32e-registers.rs index a5f4151b2c80a..77a2d92c3736b 100644 --- a/tests/ui/asm/riscv/riscv32e-registers.rs +++ b/tests/ui/asm/riscv/riscv32e-registers.rs @@ -4,7 +4,7 @@ //@ build-fail //@ revisions: riscv32e_llvm23 riscv32em_llvm23 riscv32emc_llvm23 //@ revisions: riscv32e_llvm24 riscv32em_llvm24 riscv32emc_llvm24 -//@ compile-flags: --crate-type=rlib +//@ compile-flags: --crate-type=rlib -Cembed-bitcode=false -Clto=no //@ [riscv32e_llvm23] needs-llvm-components: riscv //@ [riscv32e_llvm23] compile-flags: --target=riscv32e-unknown-none-elf //@ [riscv32e_llvm23] max-llvm-major-version: 23 diff --git a/tests/ui/asm/x86_64/srcloc.rs b/tests/ui/asm/x86_64/srcloc.rs index e73854acf1522..9bbd20340e3ab 100644 --- a/tests/ui/asm/x86_64/srcloc.rs +++ b/tests/ui/asm/x86_64/srcloc.rs @@ -1,6 +1,6 @@ //@ add-minicore //@ build-fail -//@ compile-flags: --target x86_64-unknown-linux-gnu -Ccodegen-units=1 +//@ compile-flags: --target x86_64-unknown-linux-gnu -Ccodegen-units=1 -Cembed-bitcode=false -Clto=no //@ needs-llvm-components: x86 //@ ignore-backends: gcc #![crate_type = "lib"] From fa5601dd03bd9dfabe322e2c78af832f0128960d Mon Sep 17 00:00:00 2001 From: Matyas Susits Date: Thu, 3 Sep 2026 16:05:14 +0200 Subject: [PATCH 08/16] add test for inline asm cookie reproducibility --- .../inline-asm-cookie-issue-150451.rs | 9 ++++ .../rmake.rs | 42 +++++++++++++++++++ 2 files changed, 51 insertions(+) create mode 100644 tests/run-make/parallel-reproducible-inline-asm-cookie/inline-asm-cookie-issue-150451.rs create mode 100644 tests/run-make/parallel-reproducible-inline-asm-cookie/rmake.rs diff --git a/tests/run-make/parallel-reproducible-inline-asm-cookie/inline-asm-cookie-issue-150451.rs b/tests/run-make/parallel-reproducible-inline-asm-cookie/inline-asm-cookie-issue-150451.rs new file mode 100644 index 0000000000000..be49be470091e --- /dev/null +++ b/tests/run-make/parallel-reproducible-inline-asm-cookie/inline-asm-cookie-issue-150451.rs @@ -0,0 +1,9 @@ +use std::thread; + +fn _main() { + let _t1 = thread::spawn(|| { + for _ in 0..100 { + println!("test"); + } + }); +} diff --git a/tests/run-make/parallel-reproducible-inline-asm-cookie/rmake.rs b/tests/run-make/parallel-reproducible-inline-asm-cookie/rmake.rs new file mode 100644 index 0000000000000..62ad5a46af860 --- /dev/null +++ b/tests/run-make/parallel-reproducible-inline-asm-cookie/rmake.rs @@ -0,0 +1,42 @@ +//@ needs-target-std +//@ ignore-cross-compile +//@ ignore-windows-gnu +// GNU Linker for Windows is non-deterministic. (from `reproducible-build-2` test in this suite) + +use std::rc::Rc; + +use run_make_support::{bin_name, is_windows_msvc, rfs, run_in_tmpdir, rustc}; + +/// Test that parallel compiler produces identical binaries. +fn main() { + const FILE_NAME: &str = "inline-asm-cookie-issue-150451"; + let bin_name = bin_name(FILE_NAME); + + let mut reference = None; + + for _ in 0..10 { + // Tmp dir as previous runs affect output binary on windows. + run_in_tmpdir(|| { + let mut rustc = rustc(); + rustc + .input(format!("{FILE_NAME}.rs")) + .arg("--crate-type=lib") + .arg("-Zthreads=3") + .arg("-Clink-dead-code=true") + .arg("-Copt-level=0") + .arg("-Cembed-bitcode=true") + .output(&bin_name); + + if is_windows_msvc() { + rustc.arg("-Clink-arg=/Brepro"); + } + + rustc.run(); + + let current = Rc::new(rfs::read(&bin_name)); + reference.get_or_insert(Rc::clone(¤t)); + + assert_eq!(Some(current), reference); + }); + } +} From 970cb98f0ea3472c8bbb9152f74025ec73e03ef7 Mon Sep 17 00:00:00 2001 From: Matyas Susits Date: Thu, 30 Jul 2026 08:11:34 +0200 Subject: [PATCH 09/16] Disable inline asm line info cookies when llvm bitcode is saved or LTO is enabled The parallel frontend makes the cookies nondeterministic in their current form, resulting in nondeterministic outputs when bitcode is emitted or LTO is used. Causes minor diagnostic regression for inline asm in release builds. --- .../rustc_codegen_cranelift/src/driver/aot.rs | 1 + compiler/rustc_codegen_gcc/src/lib.rs | 1 + compiler/rustc_codegen_llvm/src/asm.rs | 55 ++++++++++++------- compiler/rustc_codegen_llvm/src/base.rs | 11 +++- compiler/rustc_codegen_llvm/src/context.rs | 3 + compiler/rustc_codegen_llvm/src/lib.rs | 3 +- compiler/rustc_codegen_ssa/src/back/write.rs | 14 ++--- compiler/rustc_codegen_ssa/src/base.rs | 22 ++++++-- .../rustc_codegen_ssa/src/traits/backend.rs | 1 + 9 files changed, 74 insertions(+), 37 deletions(-) diff --git a/compiler/rustc_codegen_cranelift/src/driver/aot.rs b/compiler/rustc_codegen_cranelift/src/driver/aot.rs index d6c25cf524a5c..e06549090226e 100644 --- a/compiler/rustc_codegen_cranelift/src/driver/aot.rs +++ b/compiler/rustc_codegen_cranelift/src/driver/aot.rs @@ -284,6 +284,7 @@ impl ExtraBackendMethods for AotDriver { &self, tcx: TyCtxt<'_>, cgu_name: Symbol, + _bitcode_needed: bool, ) -> (ModuleCodegen, u64) { let start_time = Instant::now(); diff --git a/compiler/rustc_codegen_gcc/src/lib.rs b/compiler/rustc_codegen_gcc/src/lib.rs index cbc7db8e9e23f..2fb5459a20283 100644 --- a/compiler/rustc_codegen_gcc/src/lib.rs +++ b/compiler/rustc_codegen_gcc/src/lib.rs @@ -365,6 +365,7 @@ impl ExtraBackendMethods for GccCodegenBackend { &self, tcx: TyCtxt<'_>, cgu_name: Symbol, + _bitcode_needed: bool, ) -> (ModuleCodegen, u64) { base::compile_codegen_unit( tcx, diff --git a/compiler/rustc_codegen_llvm/src/asm.rs b/compiler/rustc_codegen_llvm/src/asm.rs index 0f74f5e81d684..9960ee2ec64d0 100644 --- a/compiler/rustc_codegen_llvm/src/asm.rs +++ b/compiler/rustc_codegen_llvm/src/asm.rs @@ -10,6 +10,8 @@ use rustc_middle::mir::interpret::{PointerArithmetic, Scalar as ConstScalar}; use rustc_middle::ty::Instance; use rustc_middle::ty::layout::TyAndLayout; use rustc_middle::{bug, span_bug}; +use rustc_session::Session; +use rustc_session::config::Lto; use rustc_span::{Pos, Span, Symbol, sym}; use rustc_target::asm::*; use rustc_target::spec::HasTargetSpec; @@ -594,30 +596,45 @@ pub(crate) fn inline_asm_call<'ll>( let key = "srcloc"; let kind = bx.get_md_kind_id(key); - // `srcloc` contains one 64-bit integer for each line of assembly code, - // where the lower 32 bits hold the lo byte position and the upper 32 bits - // hold the hi byte position. - let mut srcloc = vec![]; - if dia == llvm::AsmDialect::Intel && line_spans.len() > 1 { - // LLVM inserts an extra line to add the ".intel_syntax", so add - // a dummy srcloc entry for it. - // - // Don't do this if we only have 1 line span since that may be - // due to the asm template string coming from a macro. LLVM will - // default to the first srcloc for lines that don't have an - // associated srcloc. - srcloc.push(llvm::LLVMValueAsMetadata(bx.const_u64(0))); + if allow_raw_span_inline_asm_srcloc(bx.tcx.sess, bx.bitcode_needed) { + // `srcloc` contains one 64-bit integer for each line of assembly code, + // where the lower 32 bits hold the lo byte position and the upper 32 bits + // hold the hi byte position. + let mut srcloc = vec![]; + if dia == llvm::AsmDialect::Intel && line_spans.len() > 1 { + // LLVM inserts an extra line to add the ".intel_syntax", so add + // a dummy srcloc entry for it. + // + // Don't do this if we only have 1 line span since that may be + // due to the asm template string coming from a macro. LLVM will + // default to the first srcloc for lines that don't have an + // associated srcloc. + srcloc.push(llvm::LLVMValueAsMetadata(bx.const_u64(0))); + } + srcloc.extend(line_spans.iter().map(|span| { + llvm::LLVMValueAsMetadata( + bx.const_u64(u64::from(span.lo().to_u32()) | (u64::from(span.hi().to_u32()) << 32)), + ) + })); + bx.cx.set_metadata_node(call, kind, &srcloc); } - srcloc.extend(line_spans.iter().map(|span| { - llvm::LLVMValueAsMetadata( - bx.const_u64(u64::from(span.lo().to_u32()) | (u64::from(span.hi().to_u32()) << 32)), - ) - })); - bx.cx.set_metadata_node(call, kind, &srcloc); Some(call) } +/// Whenever inline assembly bitcode is built, its `srcloc` contains the raw span numbers +/// as location cookies. This is problematic since that is nondeterministic when using +/// the parallel frontend. Even without parallelism, the cookies are meaningless in another +/// rustc session. +/// +/// Discussion about replacing the cookies with something stable: rust-lang/rust#150451 +fn allow_raw_span_inline_asm_srcloc(sess: &Session, bitcode_needed: bool) -> bool { + // even for Lto::ThinLocal, where the bitcode isn't serialized into files, the changes in + // raw span positions would reflect in the LTO module hashes, which could lead to + // nondeterminism + sess.lto() == Lto::No && !bitcode_needed +} + /// If the register is an xmm/ymm/zmm register then return its index. fn xmm_reg_index(reg: InlineAsmReg) -> Option { use X86InlineAsmReg::*; diff --git a/compiler/rustc_codegen_llvm/src/base.rs b/compiler/rustc_codegen_llvm/src/base.rs index 14700266412dd..401b52318539b 100644 --- a/compiler/rustc_codegen_llvm/src/base.rs +++ b/compiler/rustc_codegen_llvm/src/base.rs @@ -64,6 +64,7 @@ pub(crate) fn iter_global_aliases(llmod: &llvm::Module) -> ValueIter<'_> { pub(crate) fn compile_codegen_unit( tcx: TyCtxt<'_>, cgu_name: Symbol, + bitcode_needed: bool, ) -> (ModuleCodegen, u64) { let start_time = Instant::now(); @@ -71,7 +72,7 @@ pub(crate) fn compile_codegen_unit( let (module, _) = tcx.dep_graph.with_task( dep_node, tcx, - || module_codegen(tcx, cgu_name), + || module_codegen(tcx, cgu_name, bitcode_needed), Some(dep_graph::hash_result), ); let time_to_codegen = start_time.elapsed(); @@ -80,7 +81,11 @@ pub(crate) fn compile_codegen_unit( // the time we needed for codegenning it. let cost = time_to_codegen.as_nanos() as u64; - fn module_codegen(tcx: TyCtxt<'_>, cgu_name: Symbol) -> ModuleCodegen { + fn module_codegen( + tcx: TyCtxt<'_>, + cgu_name: Symbol, + needs_bitcode: bool, + ) -> ModuleCodegen { let cgu = tcx.codegen_unit(cgu_name); let _prof_timer = tcx.prof.generic_activity_with_arg_recorder("codegen_module", |recorder| { @@ -90,7 +95,7 @@ pub(crate) fn compile_codegen_unit( // Instantiate monomorphizations without filling out definitions yet... let llvm_module = ModuleLlvm::new(tcx, cgu_name.as_str()); { - let mut cx = CodegenCx::new(tcx, cgu, &llvm_module); + let mut cx = CodegenCx::new(tcx, cgu, &llvm_module, needs_bitcode); // Declare and store globals shared by all offload kernels // diff --git a/compiler/rustc_codegen_llvm/src/context.rs b/compiler/rustc_codegen_llvm/src/context.rs index 853c4bfc9ca3f..66886fc080e88 100644 --- a/compiler/rustc_codegen_llvm/src/context.rs +++ b/compiler/rustc_codegen_llvm/src/context.rs @@ -92,6 +92,7 @@ pub(crate) type CodegenCx<'ll, 'tcx> = GenericCx<'ll, FullCx<'ll, 'tcx>>; pub(crate) struct FullCx<'ll, 'tcx> { pub tcx: TyCtxt<'tcx>, + pub bitcode_needed: bool, pub scx: SimpleCx<'ll>, pub use_dll_storage_attrs: bool, pub tls_model: llvm::ThreadLocalMode, @@ -606,6 +607,7 @@ impl<'ll, 'tcx> CodegenCx<'ll, 'tcx> { tcx: TyCtxt<'tcx>, codegen_unit: &'tcx CodegenUnit<'tcx>, llvm_module: &'ll crate::ModuleLlvm, + bitcode_needed: bool, ) -> Self { // An interesting part of Windows which MSVC forces our hand on (and // apparently MinGW didn't) is the usage of `dllimport` and `dllexport` @@ -683,6 +685,7 @@ impl<'ll, 'tcx> CodegenCx<'ll, 'tcx> { GenericCx( FullCx { tcx, + bitcode_needed, scx: SimpleCx::new(llmod, llcx, tcx.data_layout.pointer_size()), use_dll_storage_attrs, tls_model, diff --git a/compiler/rustc_codegen_llvm/src/lib.rs b/compiler/rustc_codegen_llvm/src/lib.rs index 775e1dcf2ffea..ca16d33b90256 100644 --- a/compiler/rustc_codegen_llvm/src/lib.rs +++ b/compiler/rustc_codegen_llvm/src/lib.rs @@ -112,8 +112,9 @@ impl ExtraBackendMethods for LlvmCodegenBackend { &self, tcx: TyCtxt<'_>, cgu_name: Symbol, + bitcode_needed: bool, ) -> (ModuleCodegen, u64) { - base::compile_codegen_unit(tcx, cgu_name) + base::compile_codegen_unit(tcx, cgu_name, bitcode_needed) } } diff --git a/compiler/rustc_codegen_ssa/src/back/write.rs b/compiler/rustc_codegen_ssa/src/back/write.rs index 78cdd3e38f68c..e8b25eb4359d0 100644 --- a/compiler/rustc_codegen_ssa/src/back/write.rs +++ b/compiler/rustc_codegen_ssa/src/back/write.rs @@ -15,7 +15,6 @@ use rustc_errors::{ Level, MultiSpan, Style, Suggestions, catch_fatal_errors, }; use rustc_fs_util::link_or_copy; -use rustc_hir::find_attr; use rustc_incremental::{copy_cgu_workproduct_to_incr_comp_cache_dir, in_incr_comp_dir_sess}; use rustc_macros::{Decodable, Encodable}; use rustc_metadata::fs::copy_to_stdout; @@ -114,7 +113,7 @@ pub struct ModuleConfig { } impl ModuleConfig { - fn new(kind: ModuleKind, tcx: TyCtxt<'_>, no_builtins: bool) -> ModuleConfig { + pub(crate) fn new(kind: ModuleKind, tcx: TyCtxt<'_>, no_builtins: bool) -> ModuleConfig { // If it's a regular module, use `$regular`, otherwise use `$other`. // `$regular` and `$other` are evaluated lazily. macro_rules! if_regular { @@ -426,15 +425,12 @@ fn need_pre_lto_bitcode_for_incr_comp(sess: &Session) -> bool { pub(crate) fn start_async_codegen( backend: B, tcx: TyCtxt<'_>, + regular_config: Arc, + allocator_config: Arc, allocator_module: Option>, ) -> OngoingCodegen { let (coordinator_send, coordinator_receive) = channel(); - let no_builtins = find_attr!(tcx, crate, NoBuiltins); - - let regular_config = ModuleConfig::new(ModuleKind::Regular, tcx, no_builtins); - let allocator_config = ModuleConfig::new(ModuleKind::Allocator, tcx, no_builtins); - let (shared_emitter, shared_emitter_main) = SharedEmitter::new(); let (codegen_worker_send, codegen_worker_receive) = channel(); @@ -444,8 +440,8 @@ pub(crate) fn start_async_codegen( shared_emitter, codegen_worker_send, coordinator_receive, - Arc::new(regular_config), - Arc::new(allocator_config), + regular_config, + allocator_config, allocator_module, coordinator_send.clone(), ); diff --git a/compiler/rustc_codegen_ssa/src/base.rs b/compiler/rustc_codegen_ssa/src/base.rs index c870d1694d068..8dd129f45cc5a 100644 --- a/compiler/rustc_codegen_ssa/src/base.rs +++ b/compiler/rustc_codegen_ssa/src/base.rs @@ -42,7 +42,7 @@ use tracing::{debug, info}; use crate::assert_module_sources::CguReuse; use crate::back::link::are_upstream_rust_objects_already_included; use crate::back::write::{ - ComputedLtoType, OngoingCodegen, compute_per_cgu_lto_type, start_async_codegen, + ComputedLtoType, ModuleConfig, OngoingCodegen, compute_per_cgu_lto_type, start_async_codegen, submit_codegened_module_to_llvm, submit_post_lto_module_to_llvm, submit_pre_lto_module_to_llvm, }; use crate::common::{self, IntPredicate, RealPredicate, TypeKind}; @@ -52,7 +52,7 @@ use crate::mir::place::PlaceRef; use crate::traits::*; use crate::{ CachedModuleCodegen, CodegenLintLevelSpecs, CrateInfo, EiiLinkageImplInfo, EiiLinkageInfo, - ModuleCodegen, diagnostics, meth, mir, + ModuleCodegen, ModuleKind, diagnostics, meth, mir, }; pub(crate) fn bin_op_to_icmp_predicate(op: BinOp, signed: bool) -> IntPredicate { @@ -762,7 +762,18 @@ pub fn codegen_crate< None }; - let ongoing_codegen = start_async_codegen(backend.clone(), tcx, allocator_module); + let no_builtins = find_attr!(tcx, crate, NoBuiltins); + let regular_module_config = ModuleConfig::new(ModuleKind::Regular, tcx, no_builtins); + let bitcode_needed = regular_module_config.bitcode_needed(); + let allocator_module_config = ModuleConfig::new(ModuleKind::Allocator, tcx, no_builtins); + + let ongoing_codegen = start_async_codegen( + backend.clone(), + tcx, + Arc::new(regular_module_config), + Arc::new(allocator_module_config), + allocator_module, + ); // For better throughput during parallel processing by LLVM, we used to sort // CGUs largest to smallest. This would lead to better thread utilization @@ -822,7 +833,8 @@ pub fn codegen_crate< let start_time = Instant::now(); let pre_compiled_cgus = par_map(cgus, |(i, _)| { - let module = backend.compile_codegen_unit(tcx, codegen_units[i].name()); + let module = + backend.compile_codegen_unit(tcx, codegen_units[i].name(), bitcode_needed); (i, IntoDynSyncSend(module)) }); @@ -846,7 +858,7 @@ pub fn codegen_crate< cgu.0 } else { let start_time = Instant::now(); - let module = backend.compile_codegen_unit(tcx, cgu.name()); + let module = backend.compile_codegen_unit(tcx, cgu.name(), bitcode_needed); total_codegen_time += start_time.elapsed(); module }; diff --git a/compiler/rustc_codegen_ssa/src/traits/backend.rs b/compiler/rustc_codegen_ssa/src/traits/backend.rs index 11878c1f5165d..2435cca50a0f3 100644 --- a/compiler/rustc_codegen_ssa/src/traits/backend.rs +++ b/compiler/rustc_codegen_ssa/src/traits/backend.rs @@ -177,5 +177,6 @@ pub trait ExtraBackendMethods: Send + Sync + DynSend + DynSync { &self, tcx: TyCtxt<'_>, cgu_name: Symbol, + bitcode_needed: bool, ) -> (ModuleCodegen, u64); } From 714b06b962ffb2a6fbdf01b9622374d505acc96d Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sun, 6 Sep 2026 19:31:09 +0200 Subject: [PATCH 10/16] add test ensuring we refuse to const-eval the body of a rustc_do_not_const_check function --- .../consts/const-eval/do_not_const_check.rs | 25 +++++++++++++++++++ .../const-eval/do_not_const_check.stderr | 15 +++++++++++ 2 files changed, 40 insertions(+) create mode 100644 tests/ui/consts/const-eval/do_not_const_check.rs create mode 100644 tests/ui/consts/const-eval/do_not_const_check.stderr diff --git a/tests/ui/consts/const-eval/do_not_const_check.rs b/tests/ui/consts/const-eval/do_not_const_check.rs new file mode 100644 index 0000000000000..ced2557bffd19 --- /dev/null +++ b/tests/ui/consts/const-eval/do_not_const_check.rs @@ -0,0 +1,25 @@ +//! Ensure that we refuse to run a do_not_const_check function, even if the body *would* const-check +//! at the moment. +#![feature(rustc_attrs, intrinsics)] + +#[rustc_do_not_const_check] +const fn mostly_harmless() {} + +const _: () = { + mostly_harmless(); //~ERROR: calling non-const function +}; + +// Also ensure the same happens with intrinsics. +// Here we need some intrinsic that the interpreter does *not* have a native implementation for. +// Let's hope nobody adds one... +#[rustc_intrinsic] +#[rustc_do_not_const_check] +pub const fn integer_min(a: T, b: T) -> T { + a +} + +const _: () = { + integer_min(0, 1); //~ERROR: calling non-const function +}; + +fn main() {} diff --git a/tests/ui/consts/const-eval/do_not_const_check.stderr b/tests/ui/consts/const-eval/do_not_const_check.stderr new file mode 100644 index 0000000000000..507999df218d1 --- /dev/null +++ b/tests/ui/consts/const-eval/do_not_const_check.stderr @@ -0,0 +1,15 @@ +error[E0080]: calling non-const function `mostly_harmless` + --> $DIR/do_not_const_check.rs:9:5 + | +LL | mostly_harmless(); + | ^^^^^^^^^^^^^^^^^ evaluation of `_` failed here + +error[E0080]: calling non-const function `integer_min::` + --> $DIR/do_not_const_check.rs:22:5 + | +LL | integer_min(0, 1); + | ^^^^^^^^^^^^^^^^^ evaluation of `_` failed here + +error: aborting due to 2 previous errors + +For more information about this error, try `rustc --explain E0080`. From eb2326af7c23acb3758d4fb7d5df68e029c3c413 Mon Sep 17 00:00:00 2001 From: pbkx <93405617+pbkx@users.noreply.github.com> Date: Fri, 19 Jun 2026 13:28:09 -0700 Subject: [PATCH 11/16] avoid spurious lifetime diagnostic in async generic case --- .../rustc_borrowck/src/region_infer/mod.rs | 35 ++++++++++++++++++- .../spurious-static-bound-issue-115376.rs | 8 +++++ .../spurious-static-bound-issue-115376.stderr | 17 +++++++++ .../unconstrained-closure-lifetime-generic.rs | 1 - ...onstrained-closure-lifetime-generic.stderr | 17 ++------- 5 files changed, 61 insertions(+), 17 deletions(-) create mode 100644 tests/ui/async-await/spurious-static-bound-issue-115376.rs create mode 100644 tests/ui/async-await/spurious-static-bound-issue-115376.stderr diff --git a/compiler/rustc_borrowck/src/region_infer/mod.rs b/compiler/rustc_borrowck/src/region_infer/mod.rs index 534cd1327bbe5..2f5793d5b3672 100644 --- a/compiler/rustc_borrowck/src/region_infer/mod.rs +++ b/compiler/rustc_borrowck/src/region_infer/mod.rs @@ -589,6 +589,7 @@ impl<'tcx> RegionInferenceContext<'tcx> { // result in basically the exact same error being reported to // the user. Avoid that. let mut deduplicate_errors = FxIndexSet::default(); + let mut failed_type_tests = Vec::new(); for type_test in &self.type_tests { debug!("check_type_test: {:?}", type_test); @@ -609,8 +610,40 @@ impl<'tcx> RegionInferenceContext<'tcx> { continue; } - // Type-test failed. Report the error. + // Type-test failed. Collect it so we can suppress redundant errors below. let erased_generic_kind = infcx.tcx.erase_and_anonymize_regions(type_test.generic_kind); + failed_type_tests.push((erased_generic_kind, type_test)); + } + + // An async body can produce both `G: 'static` and `G: 'a` type-test failures at + // the same span, as in `tests/ui/async-await/spurious-static-bound-issue-115376.rs`. + // Reporting the weaker bound adds a redundant diagnostic and suggests a lifetime + // bound that cannot fix the missing `G: 'static` requirement. Keep the `'static` + // error and suppress weaker failures for the same erased generic kind and span. + // This is a diagnostic heuristic, using the same erasure as deduplication below. + // + // Collect all failed `'static` bounds before reporting errors so suppression does + // not depend on the order of the type tests. Compare SCCs because a lower-bound + // region can be equivalent to `'static` without being `fr_static` itself. + let static_scc = self.constraint_sccs.scc(self.universal_regions().fr_static); + let static_bound_errors: FxIndexSet<_> = failed_type_tests + .iter() + .filter_map(|&(erased_generic_kind, type_test)| { + if self.constraint_sccs.scc(type_test.lower_bound) == static_scc { + Some((erased_generic_kind, type_test.span)) + } else { + None + } + }) + .collect(); + + // If `G: 'static` failed at this span, then same-span `G: 'a` failures are weaker. + for (erased_generic_kind, type_test) in failed_type_tests { + if self.constraint_sccs.scc(type_test.lower_bound) != static_scc + && static_bound_errors.contains(&(erased_generic_kind, type_test.span)) + { + continue; + } // Skip duplicate-ish errors. if deduplicate_errors.insert(( diff --git a/tests/ui/async-await/spurious-static-bound-issue-115376.rs b/tests/ui/async-await/spurious-static-bound-issue-115376.rs new file mode 100644 index 0000000000000..42cd388ab7eec --- /dev/null +++ b/tests/ui/async-await/spurious-static-bound-issue-115376.rs @@ -0,0 +1,8 @@ +//@ edition: 2021 + +async fn test(_: &u8) { + let _: &'static T; + //~^ ERROR the parameter type `T` may not live long enough +} + +fn main() {} diff --git a/tests/ui/async-await/spurious-static-bound-issue-115376.stderr b/tests/ui/async-await/spurious-static-bound-issue-115376.stderr new file mode 100644 index 0000000000000..6292823029de1 --- /dev/null +++ b/tests/ui/async-await/spurious-static-bound-issue-115376.stderr @@ -0,0 +1,17 @@ +error[E0310]: the parameter type `T` may not live long enough + --> $DIR/spurious-static-bound-issue-115376.rs:4:12 + | +LL | let _: &'static T; + | ^^^^^^^^^^ + | | + | the parameter type `T` must be valid for the static lifetime... + | ...so that the type `T` will meet its required lifetime bounds + | +help: consider adding an explicit lifetime bound + | +LL | async fn test(_: &u8) { + | +++++++++ + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0310`. diff --git a/tests/ui/borrowck/unconstrained-closure-lifetime-generic.rs b/tests/ui/borrowck/unconstrained-closure-lifetime-generic.rs index 4fdf5470feac6..0edabe009acdb 100644 --- a/tests/ui/borrowck/unconstrained-closure-lifetime-generic.rs +++ b/tests/ui/borrowck/unconstrained-closure-lifetime-generic.rs @@ -14,7 +14,6 @@ impl Foo { //~| ERROR the parameter type `impl for<'a> Fn(&'a usize) -> Box` may not live long enough //~| ERROR the parameter type `I` may not live long enough //~| ERROR the parameter type `I` may not live long enough - //~| ERROR the parameter type `I` may not live long enough //~| ERROR `f` does not live long enough } } diff --git a/tests/ui/borrowck/unconstrained-closure-lifetime-generic.stderr b/tests/ui/borrowck/unconstrained-closure-lifetime-generic.stderr index df86ce79f09c7..cbeb8fba8d226 100644 --- a/tests/ui/borrowck/unconstrained-closure-lifetime-generic.stderr +++ b/tests/ui/borrowck/unconstrained-closure-lifetime-generic.stderr @@ -84,19 +84,6 @@ help: consider adding an explicit lifetime bound LL | pub fn ack(&mut self, f: impl for<'a> Fn(&'a usize) -> Box) { | +++++++++ -error[E0311]: the parameter type `I` may not live long enough - --> $DIR/unconstrained-closure-lifetime-generic.rs:10:35 - | -LL | pub fn ack(&mut self, f: impl for<'a> Fn(&'a usize) -> Box) { - | --------- the parameter type `I` must be valid for the anonymous lifetime defined here... -LL | self.bar = Box::new(|baz| Box::new(f(baz))); - | ^^^^^^^^^^^^^^^^ ...so that the type `I` will meet its required lifetime bounds - | -help: consider adding an explicit lifetime bound - | -LL | pub fn ack<'a, I: 'a>(&'a mut self, f: impl for<'a> Fn(&'a usize) -> Box) { - | +++ ++++ ++ - error[E0597]: `f` does not live long enough --> $DIR/unconstrained-closure-lifetime-generic.rs:10:44 | @@ -113,7 +100,7 @@ LL | } | = note: due to object lifetime defaults, `Box Fn(&'a usize) -> Box<(dyn Any + 'a)>>` actually means `Box<(dyn for<'a> Fn(&'a usize) -> Box<(dyn Any + 'a)> + 'static)>` -error: aborting due to 8 previous errors +error: aborting due to 7 previous errors -Some errors have detailed explanations: E0310, E0311, E0597. +Some errors have detailed explanations: E0310, E0597. For more information about an error, try `rustc --explain E0310`. From 018b9ad740ebde4014322770101e1c0b6dde29e3 Mon Sep 17 00:00:00 2001 From: HigherOrderLogic <73709188+HigherOrderLogic@users.noreply.github.com> Date: Mon, 7 Sep 2026 19:37:51 +1000 Subject: [PATCH 12/16] std: remove exceed whitespace in docs --- library/std/src/thread/join_handle.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/std/src/thread/join_handle.rs b/library/std/src/thread/join_handle.rs index 93dcc634d2dfa..955fd524e736b 100644 --- a/library/std/src/thread/join_handle.rs +++ b/library/std/src/thread/join_handle.rs @@ -104,7 +104,7 @@ impl JoinHandle { /// Otherwise, it fully waits for the thread to finish, including all destructors /// for thread-local variables that might be running after the main function of the thread. /// - /// In terms of [atomic memory orderings], the completion of the associated + /// In terms of [atomic memory orderings], the completion of the associated /// thread synchronizes with this function returning. In other words, all /// operations performed by that thread [happen /// before](https://doc.rust-lang.org/nomicon/atomics.html#data-accesses) all From d3cba0b9b57acfc80b5f054b20aeb3f99186d267 Mon Sep 17 00:00:00 2001 From: Zalathar Date: Mon, 7 Sep 2026 19:55:35 +1000 Subject: [PATCH 13/16] Temporarily add a crashtest for instrumenting comptime functions This test demonstrates the existing crash, and will be migrated to a successful coverage test in a subsequent commit. Co-Authored-By: Rachel Barker --- tests/crashes/comptime.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 tests/crashes/comptime.rs diff --git a/tests/crashes/comptime.rs b/tests/crashes/comptime.rs new file mode 100644 index 0000000000000..77fec9f509222 --- /dev/null +++ b/tests/crashes/comptime.rs @@ -0,0 +1,13 @@ +#![feature(rustc_attrs)] +//@ edition: 2024 +//@ compile-flags: -Cinstrument-coverage +//@ needs-profiler-runtime + +// Check that instrumenting a crate with a comptime function doesn't ICE. +// (The function itself doesn't need to be instrumented, and probably shouldn't be.) +// Regression test for . + +#[rustc_comptime] +fn comptime_fn() {} + +fn main() {} From 0027ee161223389c9490ffef6d07f1cc00a75385 Mon Sep 17 00:00:00 2001 From: Zalathar Date: Sun, 6 Sep 2026 16:47:24 +1000 Subject: [PATCH 14/16] Make comptime functions ineligible for coverage Compile-time-only functions don't generate code, so instrumenting them for coverage is useless. This also avoids an ICE when trying to get the function's symbol name for an unused-function record, which can occur when instrumenting `core`. Co-Authored-By: Rachel Barker --- .../rustc_mir_transform/src/coverage/query.rs | 17 +++++++++++++++-- tests/coverage/comptime.cov-map | 10 ++++++++++ tests/coverage/comptime.coverage | 12 ++++++++++++ tests/{crashes => coverage}/comptime.rs | 2 -- 4 files changed, 37 insertions(+), 4 deletions(-) create mode 100644 tests/coverage/comptime.cov-map create mode 100644 tests/coverage/comptime.coverage rename tests/{crashes => coverage}/comptime.rs (82%) diff --git a/compiler/rustc_mir_transform/src/coverage/query.rs b/compiler/rustc_mir_transform/src/coverage/query.rs index 6ffb85d7b90a8..a4d39f09b724d 100644 --- a/compiler/rustc_mir_transform/src/coverage/query.rs +++ b/compiler/rustc_mir_transform/src/coverage/query.rs @@ -1,5 +1,6 @@ use rustc_hir::attrs::CoverageAttrKind; -use rustc_hir::find_attr; +use rustc_hir::def::DefKind; +use rustc_hir::{self as hir, find_attr}; use rustc_index::bit_set::DenseBitSet; use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrFlags; use rustc_middle::mir::coverage::{ @@ -30,11 +31,23 @@ fn is_eligible_for_coverage(tcx: TyCtxt<'_>, def_id: LocalDefId) -> bool { // expressions from coverage spans in enclosing MIR's, like we do for closures. (That might // be tricky if const expressions have no corresponding statements in the enclosing MIR. // Closures are carved out by their initial `Assign` statement.) - if !tcx.def_kind(def_id).is_fn_like() { + let def_kind = tcx.def_kind(def_id); + if !def_kind.is_fn_like() { trace!("InstrumentCoverage skipped for {def_id:?} (not an fn-like)"); return false; } + // Comptime functions can't exist at runtime, so instrumenting them is useless. + // This also avoids an ICE when getting the symbol name for an unused-function record + // (due to ). + // We check `def_kind` first to avoid any unexpected panics from merely asking for constness. + if matches!(def_kind, DefKind::Fn | DefKind::AssocFn) + && matches!(tcx.constness(def_id), hir::Constness::Const { always: true }) + { + trace!("InstrumentCoverage skipped for {def_id:?} (comptime)"); + return false; + } + if tcx.codegen_fn_attrs(def_id).flags.contains(CodegenFnAttrFlags::NAKED) { trace!("InstrumentCoverage skipped for {def_id:?} (`#[naked]`)"); return false; diff --git a/tests/coverage/comptime.cov-map b/tests/coverage/comptime.cov-map new file mode 100644 index 0000000000000..30f91da050f6f --- /dev/null +++ b/tests/coverage/comptime.cov-map @@ -0,0 +1,10 @@ +Function name: comptime::main +Raw bytes (14): 0x[01, 01, 00, 02, 01, 0b, 01, 00, 0a, 01, 00, 0c, 00, 0d] +Number of files: 1 +- file 0 => $DIR/comptime.rs +Number of expressions: 0 +Number of file 0 mappings: 2 +- Code(Counter(0)) at (prev + 11, 1) to (start + 0, 10) +- Code(Counter(0)) at (prev + 0, 12) to (start + 0, 13) +Highest counter ID seen: c0 + diff --git a/tests/coverage/comptime.coverage b/tests/coverage/comptime.coverage new file mode 100644 index 0000000000000..1ff44169babb2 --- /dev/null +++ b/tests/coverage/comptime.coverage @@ -0,0 +1,12 @@ + LL| |#![feature(rustc_attrs)] + LL| |//@ edition: 2024 + LL| | + LL| |// Check that instrumenting a crate with a comptime function doesn't ICE. + LL| |// (The function itself doesn't need to be instrumented, and probably shouldn't be.) + LL| |// Regression test for . + LL| | + LL| |#[rustc_comptime] + LL| |fn comptime_fn() {} + LL| | + LL| 1|fn main() {} + diff --git a/tests/crashes/comptime.rs b/tests/coverage/comptime.rs similarity index 82% rename from tests/crashes/comptime.rs rename to tests/coverage/comptime.rs index 77fec9f509222..4891051b0076d 100644 --- a/tests/crashes/comptime.rs +++ b/tests/coverage/comptime.rs @@ -1,7 +1,5 @@ #![feature(rustc_attrs)] //@ edition: 2024 -//@ compile-flags: -Cinstrument-coverage -//@ needs-profiler-runtime // Check that instrumenting a crate with a comptime function doesn't ICE. // (The function itself doesn't need to be instrumented, and probably shouldn't be.) From eec967a01ce098a13666d766674de0a8c085355f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Ber=C3=A1nek?= Date: Mon, 7 Sep 2026 12:03:27 +0200 Subject: [PATCH 15/16] Also invalidate library when checking it if rustc has changed --- src/bootstrap/src/core/builder/cargo.rs | 4 ++-- src/bootstrap/src/utils/build_stamp.rs | 8 +++++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/bootstrap/src/core/builder/cargo.rs b/src/bootstrap/src/core/builder/cargo.rs index 288e147a26615..754f4a547bd74 100644 --- a/src/bootstrap/src/core/builder/cargo.rs +++ b/src/bootstrap/src/core/builder/cargo.rs @@ -1018,9 +1018,9 @@ impl Builder<'_> { // Avoid doing this during dry run as that usually means the relevant // compiler is not yet linked/copied properly. // - // Only clear out the directory if we're compiling std; otherwise, we + // Only clear out the directory if we're running Cargo on std; otherwise, we // should let Cargo take care of things for us (via depdep info) - if !self.config.dry_run() && mode == Mode::Std && cmd_kind == Kind::Build { + if !self.config.dry_run() && mode == Mode::Std { build_stamp::clear_if_dirty(self, &out_dir, &self.rustc(compiler)); } diff --git a/src/bootstrap/src/utils/build_stamp.rs b/src/bootstrap/src/utils/build_stamp.rs index 36a3d0772e5ad..f70c92667a5cf 100644 --- a/src/bootstrap/src/utils/build_stamp.rs +++ b/src/bootstrap/src/utils/build_stamp.rs @@ -114,7 +114,13 @@ pub fn clear_if_dirty(builder: &Builder<'_>, dir: &Path, input: &Path) -> bool { let stamp = BuildStamp::new(dir); let mut cleared = false; if mtime(stamp.path()) < mtime(input) { - builder.do_if_verbose(|| println!("Dirty - {}", dir.display())); + builder.do_if_verbose(|| { + println!( + "Removing dirty directory `{}` because `{}` changed", + dir.display(), + input.display(), + ) + }); let _ = fs::remove_dir_all(dir); cleared = true; } else if stamp.path().exists() { From 22506376bd6987f89d2518b8370eff4e0504d632 Mon Sep 17 00:00:00 2001 From: Zalathar Date: Mon, 7 Sep 2026 21:26:48 +1000 Subject: [PATCH 16/16] Don't pass a redundant `scrutinee_span` to some MIR-build methods In all cases, the caller was passing `&self.thir[scrutinee_id].span`, which can just as easily be done by the callee. There's no need to add confusion by passing a separate span. --- .../rustc_mir_build/src/builder/expr/into.rs | 16 +++++----------- .../rustc_mir_build/src/builder/matches/mod.rs | 14 +++++--------- 2 files changed, 10 insertions(+), 20 deletions(-) diff --git a/compiler/rustc_mir_build/src/builder/expr/into.rs b/compiler/rustc_mir_build/src/builder/expr/into.rs index 13fbb3a2c0c3c..db9dbc1c3f482 100644 --- a/compiler/rustc_mir_build/src/builder/expr/into.rs +++ b/compiler/rustc_mir_build/src/builder/expr/into.rs @@ -55,14 +55,9 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { ExprKind::Block { block: ast_block } => { this.ast_block(destination, block, ast_block, source_info) } - ExprKind::Match { scrutinee, ref arms, .. } => this.match_expr( - destination, - block, - scrutinee, - arms, - expr_span, - this.thir[scrutinee].span, - ), + ExprKind::Match { scrutinee, ref arms, .. } => { + this.match_expr(destination, block, scrutinee, arms, expr_span) + } ExprKind::If { cond, then, else_opt, if_then_scope } => { let then_span = this.thir[then].span; let then_source_info = this.source_info(then_span); @@ -303,9 +298,8 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { // Logic for `match`. let scrutinee_span = this.thir.exprs[scrutinee].span; - let scrutinee_place_builder = unpack!( - body_block = this.lower_scrutinee(body_block, scrutinee, scrutinee_span) - ); + let scrutinee_place_builder = + unpack!(body_block = this.lower_scrutinee(body_block, scrutinee)); let match_start_span = match_span.shrink_to_lo().to(scrutinee_span); diff --git a/compiler/rustc_mir_build/src/builder/matches/mod.rs b/compiler/rustc_mir_build/src/builder/matches/mod.rs index a520acda5e6c8..01505fb9ec8ae 100644 --- a/compiler/rustc_mir_build/src/builder/matches/mod.rs +++ b/compiler/rustc_mir_build/src/builder/matches/mod.rs @@ -339,10 +339,9 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { scrutinee_id: ExprId, arms: &[ArmId], span: Span, - scrutinee_span: Span, ) -> BlockAnd<()> { - let scrutinee_place = - unpack!(block = self.lower_scrutinee(block, scrutinee_id, scrutinee_span)); + let scrutinee_span = self.thir[scrutinee_id].span; + let scrutinee_place = unpack!(block = self.lower_scrutinee(block, scrutinee_id)); let match_start_span = span.shrink_to_lo().to(scrutinee_span); let patterns = arms @@ -378,11 +377,10 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { &mut self, mut block: BasicBlock, scrutinee_id: ExprId, - scrutinee_span: Span, ) -> BlockAnd> { let scrutinee_place_builder = unpack!(block = self.as_place_builder(block, scrutinee_id)); if let Some(scrutinee_place) = scrutinee_place_builder.try_to_place(self) { - let source_info = self.source_info(scrutinee_span); + let source_info = self.source_info(self.thir[scrutinee_id].span); self.cfg.push_place_mention(block, source_info, scrutinee_place); } @@ -612,9 +610,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { } _ => { - let initializer = &self.thir[initializer_id]; - let place_builder = - unpack!(block = self.lower_scrutinee(block, initializer_id, initializer.span)); + let place_builder = unpack!(block = self.lower_scrutinee(block, initializer_id)); self.place_into_pattern(block, irrefutable_pat, place_builder, true) } } @@ -2334,7 +2330,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { declare_let_bindings: DeclareLetBindings, ) -> BlockAnd<()> { let expr_span = self.thir[expr_id].span; - let scrutinee = unpack!(block = self.lower_scrutinee(block, expr_id, expr_span)); + let scrutinee = unpack!(block = self.lower_scrutinee(block, expr_id)); let built_tree = self.lower_match_tree( block, expr_span,