From b23d30885308fadf851af7fc3134d129731b6417 Mon Sep 17 00:00:00 2001 From: Asuna Date: Wed, 14 Jan 2026 22:19:12 +0100 Subject: [PATCH] Support structs in type info reflection --- .../src/const_eval/type_info.rs | 177 ++++++++++++++---- compiler/rustc_span/src/symbol.rs | 2 + library/core/src/mem/type_info.rs | 15 ++ library/coretests/tests/mem/type_info.rs | 51 +++++ .../missing-trait-bounds/issue-69725.stderr | 8 +- tests/ui/reflection/dump.bit32.run.stdout | 55 +++++- tests/ui/reflection/dump.bit64.run.stdout | 55 +++++- tests/ui/reflection/dump.rs | 9 +- 8 files changed, 333 insertions(+), 39 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 f8881f0968bb..5b96856b253f 100644 --- a/compiler/rustc_const_eval/src/const_eval/type_info.rs +++ b/compiler/rustc_const_eval/src/const_eval/type_info.rs @@ -1,18 +1,36 @@ -use rustc_abi::FieldIdx; +use std::borrow::Cow; + +use rustc_abi::{FieldIdx, VariantIdx}; use rustc_ast::Mutability; use rustc_hir::LangItem; use rustc_middle::span_bug; use rustc_middle::ty::layout::TyAndLayout; -use rustc_middle::ty::{self, Const, ScalarInt, Ty}; +use rustc_middle::ty::{self, AdtDef, AdtKind, Const, GenericArgs, ScalarInt, Ty, VariantDef}; use rustc_span::{Symbol, sym}; use crate::const_eval::CompileTimeMachine; use crate::interpret::{ - CtfeProvenance, Immediate, InterpCx, InterpResult, MPlaceTy, MemoryKind, Scalar, Writeable, - interp_ok, + CtfeProvenance, Immediate, InterpCx, InterpResult, MPlaceTy, MemoryKind, Projectable, Scalar, + Writeable, interp_ok, }; impl<'tcx> InterpCx<'tcx, CompileTimeMachine<'tcx>> { + /// Equivalent to `project_downcast`, but identifies the variant by name instead of index. + fn downcast<'a>( + &self, + place: &(impl Writeable<'tcx, CtfeProvenance> + 'a), + name: Symbol, + ) -> InterpResult<'tcx, (VariantIdx, impl Writeable<'tcx, CtfeProvenance> + 'a)> { + let variants = place.layout().ty.ty_adt_def().unwrap().variants(); + let variant_idx = variants + .iter_enumerated() + .find(|(_idx, var)| var.name == name) + .unwrap_or_else(|| panic!("got {name} but expected one of {variants:#?}")) + .0; + + interp_ok((variant_idx, self.project_downcast(place, variant_idx)?)) + } + /// Writes a `core::mem::type_info::TypeInfo` for a given type, `ty` to the given place. pub(crate) fn write_type_info( &mut self, @@ -26,22 +44,13 @@ impl<'tcx> InterpCx<'tcx, CompileTimeMachine<'tcx>> { // Fill all fields of the `TypeInfo` struct. for (idx, field) in ty_struct.fields.iter_enumerated() { let field_dest = self.project_field(dest, idx)?; - let downcast = |name: Symbol| { - let variants = field_dest.layout().ty.ty_adt_def().unwrap().variants(); - let variant_id = variants - .iter_enumerated() - .find(|(_idx, var)| var.name == name) - .unwrap_or_else(|| panic!("got {name} but expected one of {variants:#?}")) - .0; - - interp_ok((variant_id, self.project_downcast(&field_dest, variant_id)?)) - }; let ptr_bit_width = || self.tcx.data_layout.pointer_size().bits(); match field.name { sym::kind => { let variant_index = match ty.kind() { ty::Tuple(fields) => { - let (variant, variant_place) = downcast(sym::Tuple)?; + let (variant, variant_place) = + self.downcast(&field_dest, sym::Tuple)?; // project to the single tuple variant field of `type_info::Tuple` struct type let tuple_place = self.project_field(&variant_place, FieldIdx::ZERO)?; assert_eq!( @@ -59,7 +68,8 @@ impl<'tcx> InterpCx<'tcx, CompileTimeMachine<'tcx>> { variant } ty::Array(ty, len) => { - let (variant, variant_place) = downcast(sym::Array)?; + let (variant, variant_place) = + self.downcast(&field_dest, sym::Array)?; let array_place = self.project_field(&variant_place, FieldIdx::ZERO)?; self.write_array_type_info(array_place, *ty, *len)?; @@ -67,23 +77,38 @@ impl<'tcx> InterpCx<'tcx, CompileTimeMachine<'tcx>> { variant } ty::Slice(ty) => { - let (variant, variant_place) = downcast(sym::Slice)?; + let (variant, variant_place) = + self.downcast(&field_dest, sym::Slice)?; let slice_place = self.project_field(&variant_place, FieldIdx::ZERO)?; self.write_slice_type_info(slice_place, *ty)?; variant } + ty::Adt(adt_def, generics) => { + // TODO(type_info): Handle enum and union + if !adt_def.is_struct() { + self.downcast(&field_dest, sym::Other)?.0 + } else { + let (variant, variant_place) = + self.downcast(&field_dest, sym::Struct)?; + let place = self.project_field(&variant_place, FieldIdx::ZERO)?; + self.write_adt_type_info(place, (ty, *adt_def), generics)?; + variant + } + } ty::Bool => { - let (variant, _variant_place) = downcast(sym::Bool)?; + let (variant, _variant_place) = + self.downcast(&field_dest, sym::Bool)?; variant } ty::Char => { - let (variant, _variant_place) = downcast(sym::Char)?; + let (variant, _variant_place) = + self.downcast(&field_dest, sym::Char)?; variant } ty::Int(int_ty) => { - let (variant, variant_place) = downcast(sym::Int)?; + let (variant, variant_place) = self.downcast(&field_dest, sym::Int)?; let place = self.project_field(&variant_place, FieldIdx::ZERO)?; self.write_int_type_info( place, @@ -93,7 +118,7 @@ impl<'tcx> InterpCx<'tcx, CompileTimeMachine<'tcx>> { variant } ty::Uint(uint_ty) => { - let (variant, variant_place) = downcast(sym::Int)?; + let (variant, variant_place) = self.downcast(&field_dest, sym::Int)?; let place = self.project_field(&variant_place, FieldIdx::ZERO)?; self.write_int_type_info( place, @@ -103,17 +128,19 @@ impl<'tcx> InterpCx<'tcx, CompileTimeMachine<'tcx>> { variant } ty::Float(float_ty) => { - let (variant, variant_place) = downcast(sym::Float)?; + let (variant, variant_place) = + self.downcast(&field_dest, sym::Float)?; let place = self.project_field(&variant_place, FieldIdx::ZERO)?; self.write_float_type_info(place, float_ty.bit_width())?; variant } ty::Str => { - let (variant, _variant_place) = downcast(sym::Str)?; + let (variant, _variant_place) = self.downcast(&field_dest, sym::Str)?; variant } ty::Ref(_, ty, mutability) => { - let (variant, variant_place) = downcast(sym::Reference)?; + let (variant, variant_place) = + self.downcast(&field_dest, sym::Reference)?; let reference_place = self.project_field(&variant_place, FieldIdx::ZERO)?; self.write_reference_type_info(reference_place, *ty, *mutability)?; @@ -121,7 +148,8 @@ impl<'tcx> InterpCx<'tcx, CompileTimeMachine<'tcx>> { variant } ty::RawPtr(ty, mutability) => { - let (variant, variant_place) = downcast(sym::Pointer)?; + let (variant, variant_place) = + self.downcast(&field_dest, sym::Pointer)?; let pointer_place = self.project_field(&variant_place, FieldIdx::ZERO)?; @@ -130,13 +158,13 @@ impl<'tcx> InterpCx<'tcx, CompileTimeMachine<'tcx>> { variant } ty::Dynamic(predicates, region) => { - let (variant, variant_place) = downcast(sym::DynTrait)?; + let (variant, variant_place) = + self.downcast(&field_dest, sym::DynTrait)?; let dyn_place = self.project_field(&variant_place, FieldIdx::ZERO)?; self.write_dyn_trait_type_info(dyn_place, *predicates, *region)?; variant } - ty::Adt(_, _) - | ty::Foreign(_) + ty::Foreign(_) | ty::Pat(_, _) | ty::FnDef(..) | ty::FnPtr(..) @@ -151,14 +179,14 @@ impl<'tcx> InterpCx<'tcx, CompileTimeMachine<'tcx>> { | ty::Bound(..) | ty::Placeholder(_) | ty::Infer(..) - | ty::Error(_) => downcast(sym::Other)?.0, + | ty::Error(_) => self.downcast(&field_dest, sym::Other)?.0, }; self.write_discriminant(variant_index, &field_dest)? } sym::size => { let layout = self.layout_of(ty)?; let variant_index = if layout.is_sized() { - let (variant, variant_place) = downcast(sym::Some)?; + let (variant, variant_place) = self.downcast(&field_dest, sym::Some)?; let size_field_place = self.project_field(&variant_place, FieldIdx::ZERO)?; self.write_scalar( @@ -168,7 +196,7 @@ impl<'tcx> InterpCx<'tcx, CompileTimeMachine<'tcx>> { )?; variant } else { - downcast(sym::None)?.0 + self.downcast(&field_dest, sym::None)?.0 }; self.write_discriminant(variant_index, &field_dest)?; } @@ -204,7 +232,7 @@ impl<'tcx> InterpCx<'tcx, CompileTimeMachine<'tcx>> { while let Some((i, place)) = fields_places.next(self)? { let field_ty = fields[i as usize]; - self.write_field(field_ty, place, tuple_layout, i)?; + self.write_field(field_ty, place, tuple_layout, None, i)?; } let fields_place = fields_place.map_provenance(CtfeProvenance::as_immutable); @@ -219,6 +247,7 @@ impl<'tcx> InterpCx<'tcx, CompileTimeMachine<'tcx>> { field_ty: Ty<'tcx>, place: MPlaceTy<'tcx>, layout: TyAndLayout<'tcx>, + name: Option, idx: u64, ) -> InterpResult<'tcx> { for (field_idx, field_ty_field) in @@ -226,6 +255,15 @@ impl<'tcx> InterpCx<'tcx, CompileTimeMachine<'tcx>> { { let field_place = self.project_field(&place, field_idx)?; match field_ty_field.name { + sym::name => { + let name = match name.as_ref() { + Some(name) => Cow::Borrowed(name.as_str()), + None => Cow::Owned(idx.to_string()), // For tuples + }; + let name_place = self.allocate_str_dedup(&name)?; + let ptr = self.mplace_to_ref(&name_place)?; + self.write_immediate(*ptr, &field_place)? + } sym::ty => self.write_type_id(field_ty, &field_place)?, sym::offset => { let offset = layout.fields.offset(idx as usize); @@ -287,6 +325,81 @@ impl<'tcx> InterpCx<'tcx, CompileTimeMachine<'tcx>> { interp_ok(()) } + // FIXME(type_info): No semver considerations for now + pub(crate) fn write_adt_type_info( + &mut self, + place: impl Writeable<'tcx, CtfeProvenance>, + adt: (Ty<'tcx>, AdtDef<'tcx>), + generics: &'tcx GenericArgs<'tcx>, + ) -> InterpResult<'tcx> { + let (adt_ty, adt_def) = adt; + match adt_def.adt_kind() { + AdtKind::Struct => self.write_struct_type_info( + place, + (adt_ty, adt_def.variant(VariantIdx::ZERO)), + generics, + ), + AdtKind::Union => todo!(), + AdtKind::Enum => todo!(), + } + } + + pub(crate) fn write_struct_type_info( + &mut self, + place: impl Writeable<'tcx, CtfeProvenance>, + struct_: (Ty<'tcx>, &'tcx VariantDef), + generics: &'tcx GenericArgs<'tcx>, + ) -> InterpResult<'tcx> { + let (struct_ty, struct_def) = struct_; + let struct_layout = self.layout_of(struct_ty)?; + + 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 { + sym::fields => { + let fields_slice_place = field_place; + let field_type = fields_slice_place + .layout() + .ty + .builtin_deref(false) + .unwrap() + .sequence_element_type(self.tcx.tcx); + let fields_layout = self.layout_of(Ty::new_array( + self.tcx.tcx, + field_type, + struct_def.fields.len() as u64, + ))?; + let fields_place = self.allocate(fields_layout, MemoryKind::Stack)?; + let mut fields_places = self.project_array_fields(&fields_place)?; + + for field_def in &struct_def.fields { + let (i, place) = fields_places.next(self)?.unwrap(); + let field_ty = field_def.ty(*self.tcx, generics); + self.write_field(field_ty, place, struct_layout, Some(field_def.name), i)?; + } + + let fields_place = fields_place.map_provenance(CtfeProvenance::as_immutable); + let ptr = Immediate::new_slice( + fields_place.ptr(), + struct_def.fields.len() as u64, + self, + ); + self.write_immediate(ptr, &fields_slice_place)? + } + sym::non_exhaustive => { + let is_non_exhaustive = struct_def.is_field_list_non_exhaustive(); + self.write_scalar(Scalar::from_bool(is_non_exhaustive), &field_place)? + } + other => span_bug!(self.tcx.def_span(field.did), "unimplemented field {other}"), + } + } + + interp_ok(()) + } + fn write_int_type_info( &mut self, place: impl Writeable<'tcx, CtfeProvenance>, diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 6aa2eae556e2..271124a4c372 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -372,6 +372,7 @@ symbols! { Stdin, Str, String, + Struct, StructuralPartialEq, SubdiagMessage, Subdiagnostic, @@ -1086,6 +1087,7 @@ symbols! { ffi_returns_twice, field, field_init_shorthand, + fields, file, file_options, flags, diff --git a/library/core/src/mem/type_info.rs b/library/core/src/mem/type_info.rs index 8b30803c97c9..0e87b9d9cbb7 100644 --- a/library/core/src/mem/type_info.rs +++ b/library/core/src/mem/type_info.rs @@ -49,6 +49,8 @@ pub enum TypeKind { Slice(Slice), /// Dynamic Traits. DynTrait(DynTrait), + /// Structs. + Struct(Struct), /// Primitive boolean type. Bool(Bool), /// Primitive character type. @@ -81,6 +83,8 @@ pub struct Tuple { #[non_exhaustive] #[unstable(feature = "type_info", issue = "146922")] pub struct Field { + /// The name of the field. + pub name: &'static str, /// The field's type. pub ty: TypeId, /// Offset in bytes from the parent type @@ -137,6 +141,17 @@ pub struct Trait { pub is_auto: bool, } +/// Compile-time type information about arrays. +#[derive(Debug)] +#[non_exhaustive] +#[unstable(feature = "type_info", issue = "146922")] +pub struct Struct { + /// All fields of the struct. + pub fields: &'static [Field], + /// Whether the struct field list is non-exhaustive. + pub non_exhaustive: bool, +} + /// Compile-time type information about `bool`. #[derive(Debug)] #[non_exhaustive] diff --git a/library/coretests/tests/mem/type_info.rs b/library/coretests/tests/mem/type_info.rs index 87f2d5dd8289..03ff5f55c4f7 100644 --- a/library/coretests/tests/mem/type_info.rs +++ b/library/coretests/tests/mem/type_info.rs @@ -1,4 +1,7 @@ +#![allow(dead_code)] + use std::any::{Any, TypeId}; +use std::mem::offset_of; use std::mem::type_info::{Type, TypeKind}; #[test] @@ -66,6 +69,54 @@ fn test_tuples() { } } +#[test] +fn test_structs() { + use TypeKind::*; + + const { + struct TestStruct { + first: u8, + second: u16, + reference: &'static u16, + } + + let Type { kind: Struct(ty), size, .. } = Type::of::() else { panic!() }; + assert!(size == Some(size_of::())); + assert!(!ty.non_exhaustive); + assert!(ty.fields.len() == 3); + assert!(ty.fields[0].name == "first"); + assert!(ty.fields[0].ty == TypeId::of::()); + assert!(ty.fields[0].offset == offset_of!(TestStruct, first)); + assert!(ty.fields[1].name == "second"); + assert!(ty.fields[1].ty == TypeId::of::()); + assert!(ty.fields[1].offset == offset_of!(TestStruct, second)); + assert!(ty.fields[2].name == "reference"); + assert!(ty.fields[2].ty != TypeId::of::<&'static u16>()); // FIXME(type_info): should be == + assert!(ty.fields[2].offset == offset_of!(TestStruct, reference)); + } + + const { + #[non_exhaustive] + struct NonExhaustive { + a: u8, + } + + let Type { kind: Struct(ty), .. } = Type::of::() else { panic!() }; + assert!(ty.non_exhaustive); + } + + const { + struct TupleStruct(u8, u16); + + let Type { kind: Struct(ty), .. } = Type::of::() else { panic!() }; + assert!(ty.fields.len() == 2); + assert!(ty.fields[0].name == "0"); + assert!(ty.fields[0].ty == TypeId::of::()); + assert!(ty.fields[1].name == "1"); + assert!(ty.fields[1].ty == TypeId::of::()); + } +} + #[test] fn test_primitives() { use TypeKind::*; diff --git a/tests/ui/missing-trait-bounds/issue-69725.stderr b/tests/ui/missing-trait-bounds/issue-69725.stderr index f483ea849b0e..20d2213f4f5f 100644 --- a/tests/ui/missing-trait-bounds/issue-69725.stderr +++ b/tests/ui/missing-trait-bounds/issue-69725.stderr @@ -1,17 +1,17 @@ -error[E0599]: the method `clone` exists for struct `Struct`, but its trait bounds were not satisfied +error[E0599]: the method `clone` exists for struct `issue_69725::Struct`, but its trait bounds were not satisfied --> $DIR/issue-69725.rs:9:32 | LL | let _ = Struct::::new().clone(); - | ^^^^^ method cannot be called on `Struct` due to unsatisfied trait bounds + | ^^^^^ method cannot be called on `issue_69725::Struct` due to unsatisfied trait bounds | ::: $DIR/auxiliary/issue-69725.rs:2:1 | LL | pub struct Struct(A); - | -------------------- doesn't satisfy `Struct: Clone` + | -------------------- doesn't satisfy `issue_69725::Struct: Clone` | = note: the following trait bounds were not satisfied: `A: Clone` - which is required by `Struct: Clone` + which is required by `issue_69725::Struct: Clone` help: consider restricting the type parameter to satisfy the trait bound | LL | fn crash() where A: Clone { diff --git a/tests/ui/reflection/dump.bit32.run.stdout b/tests/ui/reflection/dump.bit32.run.stdout index d15b46509ff2..feda3401e462 100644 --- a/tests/ui/reflection/dump.bit32.run.stdout +++ b/tests/ui/reflection/dump.bit32.run.stdout @@ -3,14 +3,17 @@ Type { Tuple { fields: [ Field { + name: "0", ty: TypeId(0x0596b48cc04376e64d5c788c2aa46bdb), offset: 0, }, Field { + name: "1", ty: TypeId(0x0596b48cc04376e64d5c788c2aa46bdb), offset: 1, }, Field { + name: "2", ty: TypeId(0x41223169ff28813ba79b7268a2a968d9), offset: 2, }, @@ -143,7 +146,18 @@ Type { ), } Type { - kind: Other, + kind: Struct( + Struct { + fields: [ + Field { + name: "a", + ty: TypeId(0x1378bb1c0a0202683eb65e7c11f2e4d7), + offset: 0, + }, + ], + non_exhaustive: false, + }, + ), size: Some( 4, ), @@ -154,6 +168,45 @@ Type { 12, ), } +Type { + kind: Struct( + Struct { + fields: [ + Field { + name: "a", + ty: TypeId(0x1378bb1c0a0202683eb65e7c11f2e4d7), + offset: 0, + }, + ], + non_exhaustive: true, + }, + ), + size: Some( + 4, + ), +} +Type { + kind: Struct( + Struct { + fields: [ + Field { + name: "0", + ty: TypeId(0x1378bb1c0a0202683eb65e7c11f2e4d7), + offset: 8, + }, + Field { + name: "1", + ty: TypeId(0x9ed91be891e304132cb86891e578f4a5), + offset: 0, + }, + ], + non_exhaustive: false, + }, + ), + size: Some( + 12, + ), +} Type { kind: Reference( Reference { diff --git a/tests/ui/reflection/dump.bit64.run.stdout b/tests/ui/reflection/dump.bit64.run.stdout index efae22653951..d9fef71f619c 100644 --- a/tests/ui/reflection/dump.bit64.run.stdout +++ b/tests/ui/reflection/dump.bit64.run.stdout @@ -3,14 +3,17 @@ Type { Tuple { fields: [ Field { + name: "0", ty: TypeId(0x0596b48cc04376e64d5c788c2aa46bdb), offset: 0, }, Field { + name: "1", ty: TypeId(0x0596b48cc04376e64d5c788c2aa46bdb), offset: 1, }, Field { + name: "2", ty: TypeId(0x41223169ff28813ba79b7268a2a968d9), offset: 2, }, @@ -143,7 +146,18 @@ Type { ), } Type { - kind: Other, + kind: Struct( + Struct { + fields: [ + Field { + name: "a", + ty: TypeId(0x1378bb1c0a0202683eb65e7c11f2e4d7), + offset: 0, + }, + ], + non_exhaustive: false, + }, + ), size: Some( 4, ), @@ -154,6 +168,45 @@ Type { 24, ), } +Type { + kind: Struct( + Struct { + fields: [ + Field { + name: "a", + ty: TypeId(0x1378bb1c0a0202683eb65e7c11f2e4d7), + offset: 0, + }, + ], + non_exhaustive: true, + }, + ), + size: Some( + 4, + ), +} +Type { + kind: Struct( + Struct { + fields: [ + Field { + name: "0", + ty: TypeId(0x1378bb1c0a0202683eb65e7c11f2e4d7), + offset: 8, + }, + Field { + name: "1", + ty: TypeId(0x9ed91be891e304132cb86891e578f4a5), + offset: 0, + }, + ], + non_exhaustive: false, + }, + ), + size: Some( + 16, + ), +} Type { kind: Reference( Reference { diff --git a/tests/ui/reflection/dump.rs b/tests/ui/reflection/dump.rs index d42216a62fdc..fefaad325aea 100644 --- a/tests/ui/reflection/dump.rs +++ b/tests/ui/reflection/dump.rs @@ -14,6 +14,13 @@ struct Foo { a: u32, } +#[non_exhaustive] +struct NonExhaustiveStruct { + a: u32, +} + +struct TupleStruct(u32, u64); + enum Bar { Some(u32), None, @@ -37,7 +44,7 @@ fn main() { [u8; 2], i8, i32, i64, i128, isize, u8, u32, u64, u128, usize, - Foo, Bar, + Foo, Bar, NonExhaustiveStruct, TupleStruct, &Unsized, &str, &[u8], str, [u8], &u8, &mut u8,