Skip to content

Conversation

oli-obk
Copy link
Contributor

@oli-obk oli-obk commented Sep 23, 2025

I am opening this PR for discussion about the general design we should start out with, as there are various options (that are not too hard to transition between each other, so we should totally just pick one and go with it and reiterate later)

r? @scottmcm and @joshtriplett

project goal issue: rust-lang/rust-project-goals#406
tracking issue: #146922

The design currently implemented by this PR is

  • TypeId::info (method, usually used as id.info() returns a Type struct
  • the Type struct has fields that contain information about the type
  • the most notable field is kind, which is a non-exhaustive enum over all possible type kinds and their specific information. So it has a Tuple(Tuple) variant, where the only field is a Tuple struct type that contains more information (The list of type ids that make up the tuple).
  • To get nested type information (like the type of fields) you need to call TypeId::info again.
  • There is only one language intrinsic to go from TypeId to Type, and it does all the work

An alternative design could be

  • Lots of small methods (each backed by an intrinsic) on TypeId that return all the individual information pieces (size, align, number of fields, number of variants, ...)
  • This is how C++ does it
  • Advantage: you only get the information you ask for, so it's probably cheaper if you get just one piece of information for lots of types (e.g. reimplementing size_of in terms of TypeId::info is likely expensive and wasteful)
  • Disadvantage: lots of method calling (and Option return types, or "general" methods like num_fields returning 0 for primitives) instead of matching and field accesses
  • a crates.io crate could implement TypeId::info in terms of this design

The backing implementation is modular enough that switching from one to the other is probably not an issue, and the alternative design could be easier for the CTFE engine's implementation, just not as nice to use for end users (without crates wrapping the logic)

One wart of this design that I'm fixing in separate branches is that TypeId::info will panic if used at runtime, while it should be uncallable

@rustbot
Copy link
Collaborator

rustbot commented Sep 23, 2025

Some changes occurred to the CTFE / Miri interpreter

cc @rust-lang/miri

Some changes occurred to the CTFE machinery

cc @RalfJung, @oli-obk, @lcnr

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-libs Relevant to the library team, which will review and decide on the PR/issue. WG-trait-system-refactor The Rustc Trait System Refactor Initiative (-Znext-solver) labels Sep 23, 2025
@oli-obk oli-obk force-pushed the comptime-reflect branch 2 times, most recently from aab0141 to 4234855 Compare September 23, 2025 08:12
@rustbot

This comment has been minimized.

@rust-log-analyzer

This comment has been minimized.

@rust-log-analyzer

This comment has been minimized.

let op = ecx.project_field(&args[0], FieldIdx::ZERO)?;
let op = ecx.project_index(&op, 0)?;
let id = ecx.read_scalar(&op)?.to_pointer(ecx)?;
let (ty, _offset) = ecx.get_ptr_type_id(id)?;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you use read_type_id instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yea, this code predates that

/// Compute the type information of a concrete type.
/// It can only be called at compile time, the backends do
/// not implement it.
#[rustc_intrinsic]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typically we put all intrinsics into the intrinsics module. It's nice to have all these operations in one place since they are language primitives and thus quite different from the rest of the standard library.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, for the single intrinsic scheme I agree, but it does get a bit boilerplaty to keep intrinsics separate from their one single caller if we have a lot of those

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Happy to move them if my sentiment is not shared

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OTOH it gets tricky to figure out what the language surface provided by the compiler is if it's spread all across libcore...

@rust-log-analyzer

This comment has been minimized.

/// It can only be called at compile time.
#[unstable(feature = "type_info", issue = "146922")]
#[rustc_const_unstable(feature = "type_info", issue = "146922")]
pub const fn info(self) -> Type {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This currently has zero regards for semver, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, but it also only supports tuples, which was an explicit choice so we can handle semver related things when we support Adts

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unless you mean the fact that it allows inspecting a generic param and now knowing it's a tuple. This can look through opaque types and stuff

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh right it also breaks parametricity (though specialization also does that).

Not super relevant right now, but I hope "discuss semver considerations" is a major item somewhere on the agenda for this feature. ;) (The tracking issue is still rather barebones at the moment.)

/// Tuples.
Tuple(Tuple),
/// Primitives
Leaf,
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume, at this early stage, Leaf is mostly a placeholder and will be renamed to something more fitting like Primitive, if not wholly replaced by variants for (kinds of) primitives?

I apologize if, in my ignorance, i'm missing something or jumping way too far ahead

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yea, you're absolutely spot on. I'll add docs for this

#[unstable(feature = "type_info", issue = "146922")]
#[rustc_const_unstable(feature = "type_info", issue = "146922")]
pub const fn of<T: 'static>() -> Self {
TypeId::of::<T>().info()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
TypeId::of::<T>().info()
const { TypeId::of::<T>().info() }

This makes it so that the function is callable at run time.

@theemathas
Copy link
Contributor

It seems like... trying to obtain a Type of a struct causes an ICE?

@oli-obk
Copy link
Contributor Author

oli-obk commented Sep 23, 2025

It seems like... trying to obtain a Type of a struct causes an ICE?

Oh whoops, that shouldn't happen. I'll add some more tests

/// Primitives
Leaf,
/// FIXME(#146922): add all the common types
Unimplemented,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@addiesh

This comment has been minimized.

@theemathas

This comment has been minimized.

@RalfJung
Copy link
Member

RalfJung commented Sep 24, 2025

This is an MVP. Please do not flood this PR with all your wildest reflection dreams. Anything that suggests to extend the scope of this PR is off-topic.

@theemathas
Copy link
Contributor

Currently, this implementation says that, in the type (u8, dyn Send), the offset of the dyn Send field is 1. Is this correct? I believe that there will be padding bytes before the dyn Send field to make the data inside aligned.

Comment on lines 140 to 149
let mut ptr = self.mplace_to_ref(&fields_place)?;
ptr.layout = self.layout_of(Ty::new_imm_ref(
self.tcx.tcx,
self.tcx.lifetimes.re_static,
fields_layout.ty,
))?;

let slice_type = Ty::new_imm_ref(
self.tcx.tcx,
self.tcx.lifetimes.re_static,
Ty::new_slice(self.tcx.tcx, field_type),
);
let slice_type = self.layout_of(slice_type)?;
self.unsize_into(&ptr.into(), slice_type, &fields_slice_place)?;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All this here does is construct the wide pointer for the slice, right?

Immediate::new_slice and ImmTy::from_immediate should be an easier to do that.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah oops, good call

Copy link
Contributor

@onkoe onkoe left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

newer contributor here. basically, I just got some nits you can ignore :)

thank you so much for working on this!!! :D

View changes since this review

Comment on lines 33 to +35

#[unstable(feature = "type_info", issue = "146922")]
pub mod type_info;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This might be on purpose, but did you intend to use the same re-export pattern as above?

Otherwise, wouldn't this make a guarantee that everything in this module would become unstable/stable at the same time? (and prevent making changes to those internals..?)

if other MVPs tend to ignore this stuff, please ignore this comment :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if I make the module stable, its internals would not become stable. The module may also stay unstable forever. For easier usage while entirely unstable I'm just making the module publicly available so all internals can be used and played with

Comment on lines 41 to 73
ty::Tuple(fields) => {
let (variant, variant_place) = downcast(sym::Tuple)?;
// `Tuple` struct
let tuple_place = self.project_field(&variant_place, FieldIdx::ZERO)?;
assert_eq!(
1,
tuple_place
.layout()
.ty
.ty_adt_def()
.unwrap()
.non_enum_variant()
.fields
.len()
);
self.write_tuple_fields(tuple_place, fields, ty)?;
variant
}
ty::Uint(_) | ty::Int(_) | ty::Float(_) | ty::Char | ty::Bool => {
downcast(sym::Leaf)?.0
}
ty::Adt(_, _)
| ty::Foreign(_)
| ty::Str
| ty::Array(_, _)
| ty::Pat(_, _)
| ty::Slice(_)
| ty::RawPtr(..)
| ty::Ref(..)
| ty::FnDef(..)
| ty::FnPtr(..)
| ty::UnsafeBinder(..)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding a few comments in this function makes it much easier to edit, imv. Took me a sec to orient myself in here lol

(ignore if you want; mvp)

Suggested change
ty::Tuple(fields) => {
let (variant, variant_place) = downcast(sym::Tuple)?;
// `Tuple` struct
let tuple_place = self.project_field(&variant_place, FieldIdx::ZERO)?;
assert_eq!(
1,
tuple_place
.layout()
.ty
.ty_adt_def()
.unwrap()
.non_enum_variant()
.fields
.len()
);
self.write_tuple_fields(tuple_place, fields, ty)?;
variant
}
ty::Uint(_) | ty::Int(_) | ty::Float(_) | ty::Char | ty::Bool => {
downcast(sym::Leaf)?.0
}
ty::Adt(_, _)
| ty::Foreign(_)
| ty::Str
| ty::Array(_, _)
| ty::Pat(_, _)
| ty::Slice(_)
| ty::RawPtr(..)
| ty::Ref(..)
| ty::FnDef(..)
| ty::FnPtr(..)
| ty::UnsafeBinder(..)
ty::Tuple(fields) => {
let (variant, variant_place) = downcast(sym::Tuple)?;
// create the inner `core::mem::type_info::Tuple` struct
let tuple_place = self.project_field(&variant_place, FieldIdx::ZERO)?;
assert_eq!(
1,
tuple_place
.layout()
.ty
.ty_adt_def()
.unwrap()
.non_enum_variant()
.fields
.len()
);
self.write_tuple_fields(tuple_place, fields, ty)?;
variant
}
// write primitives directly (not much info to get)
ty::Uint(_) | ty::Int(_) | ty::Float(_) | ty::Char | ty::Bool => {
downcast(sym::Leaf)?.0
}
// other types are currently unimplemented.
//
// they'll result in `TypeKind::Unimplemented`
ty::Adt(_, _)
| ty::Foreign(_)
| ty::Str
| ty::Array(_, _)
| ty::Pat(_, _)
| ty::Slice(_)
| ty::RawPtr(..)
| ty::Ref(..)
| ty::FnDef(..)
| ty::FnPtr(..)
| ty::UnsafeBinder(..)

Comment on lines 13 to 28
pub(crate) fn write_type_info(
&mut self,
ty: Ty<'tcx>,
dest: &impl Writeable<'tcx, CtfeProvenance>,
) -> InterpResult<'tcx> {
let ty_struct = self.tcx.require_lang_item(LangItem::Type, self.tcx.span);
let ty_struct = self.tcx.type_of(ty_struct).instantiate_identity();
assert_eq!(ty_struct, dest.layout().ty);
let ty_struct = ty_struct.ty_adt_def().unwrap().non_enum_variant();
for (idx, field) in ty_struct.fields.iter_enumerated() {
let field_dest = self.project_field(dest, idx)?;
let downcast = |name: Symbol| {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a couple comments to help direct contributors (ignore if you want; MVP)

Suggested change
pub(crate) fn write_type_info(
&mut self,
ty: Ty<'tcx>,
dest: &impl Writeable<'tcx, CtfeProvenance>,
) -> InterpResult<'tcx> {
let ty_struct = self.tcx.require_lang_item(LangItem::Type, self.tcx.span);
let ty_struct = self.tcx.type_of(ty_struct).instantiate_identity();
assert_eq!(ty_struct, dest.layout().ty);
let ty_struct = ty_struct.ty_adt_def().unwrap().non_enum_variant();
for (idx, field) in ty_struct.fields.iter_enumerated() {
let field_dest = self.project_field(dest, idx)?;
let downcast = |name: Symbol| {
/// Creates a `core::mem::type_info::TypeInfo` for a given type, `ty`.
pub(crate) fn write_type_info(
&mut self,
ty: Ty<'tcx>,
dest: &impl Writeable<'tcx, CtfeProvenance>,
) -> InterpResult<'tcx> {
let ty_struct = self.tcx.require_lang_item(LangItem::Type, self.tcx.span);
let ty_struct = self.tcx.type_of(ty_struct).instantiate_identity();
assert_eq!(ty_struct, dest.layout().ty);
let ty_struct = ty_struct.ty_adt_def().unwrap().non_enum_variant();
// fill each field of `TypeInfo`
for (idx, field) in ty_struct.fields.iter_enumerated() {
let field_dest = self.project_field(dest, idx)?;
let downcast = |name: Symbol| {

@bors
Copy link
Collaborator

bors commented Sep 28, 2025

☔ The latest upstream changes (presumably #147128) made this pull request unmergeable. Please resolve the merge conflicts.

@james7132
Copy link

One wart of this design that I'm fixing in separate branches is that TypeId::info will panic if used at runtime, while it should be uncallable

TypeId::info would not be callable/panic at runtime, but would a const Type value produced from const evaluation be valid at runtime? or would users need to extract that information into a runtime-friendly mirror?

@rustbot
Copy link
Collaborator

rustbot commented Sep 30, 2025

This PR was rebased onto a different master commit. Here's a range-diff highlighting what actually changed.

Rebasing is a normal part of keeping PRs up to date, so no action is needed—this note is just to help reviewers.

@oli-obk
Copy link
Contributor Author

oli-obk commented Sep 30, 2025

Id::info would not be callable/panic at runtime, but would a const Type value produced from const evaluation be valid at runtime?

yea, just like any other value until we get runtime-sized types. You just can't look through further TypeIds that are contained within them

@rust-log-analyzer

This comment has been minimized.

@rustbot
Copy link
Collaborator

rustbot commented Oct 1, 2025

Some changes occurred in src/tools/clippy

cc @rust-lang/clippy

@rustbot rustbot added the T-clippy Relevant to the Clippy team. label Oct 1, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-clippy Relevant to the Clippy team. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-libs Relevant to the library team, which will review and decide on the PR/issue. WG-trait-system-refactor The Rustc Trait System Refactor Initiative (-Znext-solver)
Projects
None yet
Development

Successfully merging this pull request may close these issues.