Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
66051e0
parse field representing types
BennoLossin Jan 1, 2026
f4df71a
lower field representing types to HIR
BennoLossin Jan 1, 2026
00e75af
lower field representing types to MIR
BennoLossin Jan 1, 2026
9b2f000
add field representing types to rustc_public
BennoLossin Jan 1, 2026
dde145a
add `Field` trait, lang items and `field_offset` intrinsic
BennoLossin Jan 1, 2026
66cc751
add builtin impl of `Field` for field representing types
BennoLossin Jan 1, 2026
6054dad
add feature gate test
BennoLossin Jan 1, 2026
29bf51f
add run-pass tests
BennoLossin Jan 1, 2026
8a68e63
test incoherent impls
BennoLossin Jan 1, 2026
51242ce
deny accessing FRTs of private fields
BennoLossin Jan 1, 2026
84f2d28
deny manual impls of the `Field` trait
BennoLossin Jan 1, 2026
9dbc512
deny auto trait and drop impls for FRTs
BennoLossin Jan 1, 2026
4a6b88b
do not impl `Field` for FRTs of `repr(packed)` types
BennoLossin Jan 1, 2026
93757a9
fixup! lower field representing types to HIR
BennoLossin Jan 16, 2026
6ab22f9
fixup! lower field representing types to MIR
BennoLossin Jan 16, 2026
5d0b02d
tests: fix ui output
BennoLossin Jan 16, 2026
6512db1
tests: check auto traits and `Copy` for FRTs
BennoLossin Jan 16, 2026
a520352
consolidate tests
BennoLossin Jan 16, 2026
37be83f
fixup! lower field representing types to MIR
BennoLossin Jan 16, 2026
1faa03d
tests: more nonexistent
BennoLossin Jan 16, 2026
31a061c
tests: add invalid syntax
BennoLossin Jan 16, 2026
f8c84a3
tests: fix symbol generation
BennoLossin Jan 16, 2026
306b84e
tests: add nonexistent tuple fields
BennoLossin Jan 16, 2026
945cfb4
tests: expect(incomplete_features) instead of allow
BennoLossin Jan 16, 2026
72022ee
tests: add offset function
BennoLossin Jan 16, 2026
66149ce
tests: bad normalization case for next-solver
BennoLossin Jan 16, 2026
bf27d90
tests: fix debuginfo
BennoLossin Jan 16, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Cargo.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3427,6 +3427,7 @@ dependencies = [
"rustc_macros",
"rustc_serialize",
"rustc_span",
"serde",
"tracing",
]

Expand Down Expand Up @@ -4291,6 +4292,7 @@ dependencies = [
"rustc_target",
"rustc_thread_pool",
"rustc_type_ir",
"serde",
"smallvec",
"thin-vec",
"tracing",
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_abi/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ rustc_index = { path = "../rustc_index", default-features = false }
rustc_macros = { path = "../rustc_macros", optional = true }
rustc_serialize = { path = "../rustc_serialize", optional = true }
rustc_span = { path = "../rustc_span", optional = true }
serde = { version = "1.0.125", features = ["derive"] }
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not sure if this is okay, but I need this in order for FieldId (which contains FieldIdx and VariantIdx) to implement serde::Serialize. This is because FieldId is needed in RigidTy, which derives serde::Serialize.

tracing = "0.1"
# tidy-alphabetical-end

Expand Down
4 changes: 2 additions & 2 deletions compiler/rustc_abi/src/layout/ty.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ rustc_index::newtype_index! {
/// `b` is `FieldIdx(1)` in `VariantIdx(0)`,
/// `d` is `FieldIdx(1)` in `VariantIdx(1)`, and
/// `f` is `FieldIdx(1)` in `VariantIdx(0)`.
#[derive(HashStable_Generic)]
#[derive(HashStable_Generic, serde::Serialize)]
#[encodable]
#[orderable]
pub struct FieldIdx {}
Expand All @@ -57,7 +57,7 @@ rustc_index::newtype_index! {
///
/// `struct`s, `tuples`, and `unions`s are considered to have a single variant
/// with variant index zero, aka [`FIRST_VARIANT`].
#[derive(HashStable_Generic)]
#[derive(HashStable_Generic, serde::Serialize)]
#[encodable]
#[orderable]
pub struct VariantIdx {
Expand Down
5 changes: 5 additions & 0 deletions compiler/rustc_ast/src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2557,6 +2557,11 @@ pub enum TyKind {
/// Pattern types like `pattern_type!(u32 is 1..=)`, which is the same as `NonZero<u32>`,
/// just as part of the type system.
Pat(Box<Ty>, Box<TyPat>),
/// A `field_of` expression (e.g., `builtin # field_of(Struct, field)`).
///
/// Usually not written directly in user code but indirectly via the macro
/// `core::field::field_of!(...)`.
FieldOf(Box<Ty>, Vec<Ident>),
/// Sometimes we need a dummy value when no error has occurred.
Dummy,
/// Placeholder for a kind that has failed to be defined.
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_ast/src/util/classify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,7 @@ fn type_trailing_braced_mac_call(mut ty: &ast::Ty) -> Option<&ast::MacCall> {
| ast::TyKind::ImplicitSelf
| ast::TyKind::CVarArgs
| ast::TyKind::Pat(..)
| ast::TyKind::FieldOf(..)
| ast::TyKind::Dummy
| ast::TyKind::Err(..) => break None,
}
Expand Down
4 changes: 4 additions & 0 deletions compiler/rustc_ast_lowering/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1495,6 +1495,10 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
TyKind::Pat(ty, pat) => {
hir::TyKind::Pat(self.lower_ty_alloc(ty, itctx), self.lower_ty_pat(pat, ty.span))
}
TyKind::FieldOf(ty, fields) => match self.lower_ty_field_path(fields, ty.span) {
Ok(field_path) => hir::TyKind::FieldOf(self.lower_ty_alloc(ty, itctx), field_path),
Err(err) => hir::TyKind::Err(err),
},
TyKind::MacCall(_) => {
span_bug!(t.span, "`TyKind::MacCall` should have been expanded by now")
}
Expand Down
29 changes: 28 additions & 1 deletion compiler/rustc_ast_lowering/src/pat.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use rustc_hir::definitions::DefPathData;
use rustc_hir::{self as hir, LangItem, Target};
use rustc_middle::span_bug;
use rustc_span::source_map::{Spanned, respan};
use rustc_span::{DesugaringKind, Ident, Span};
use rustc_span::{DesugaringKind, ErrorGuaranteed, Ident, Span};

use super::errors::{
ArbitraryExpressionInPattern, ExtraDoubleDot, MisplacedDoubleDot, SubTupleBinding,
Expand Down Expand Up @@ -478,6 +478,33 @@ impl<'a, 'hir> LoweringContext<'a, 'hir> {
hir::TyPat { hir_id: pat_hir_id, kind: node, span: self.lower_span(pattern.span) }
}

pub(crate) fn lower_ty_field_path(
&mut self,
fields: &[Ident],
ty_span: Span,
) -> Result<&'hir hir::TyFieldPath, ErrorGuaranteed> {
Ok(self.arena.alloc(self.lower_ty_field_path_mut(fields, ty_span)?))
}

fn lower_ty_field_path_mut(
&mut self,
fields: &[Ident],
ty_span: Span,
) -> Result<hir::TyFieldPath, ErrorGuaranteed> {
match fields.len() {
0 => span_bug!(ty_span, "expected at least one field ident parsed in `field_of!`"),
1 => Ok(hir::TyFieldPath { variant: None, field: self.lower_ident(fields[0]) }),
2 => Ok(hir::TyFieldPath {
variant: Some(self.lower_ident(fields[0])),
field: self.lower_ident(fields[1]),
}),
_ => Err(self.dcx().span_err(
fields.iter().map(|f| f.span).collect::<Vec<_>>(),
"`field_of!` only supports a single field or a variant with a field",
)),
}
}

/// Lowers the range end of an exclusive range (`2..5`) to an inclusive range 2..=(5 - 1).
/// This way the type system doesn't have to handle the distinction between inclusive/exclusive ranges.
fn lower_excluded_range_end(&mut self, e: &AnonConst) -> &'hir hir::ConstArg<'hir> {
Expand Down
19 changes: 19 additions & 0 deletions compiler/rustc_ast_pretty/src/pprust/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1377,6 +1377,25 @@ impl<'a> State<'a> {
self.word(" is ");
self.print_ty_pat(pat);
}
ast::TyKind::FieldOf(ty, fields) => {
self.word("builtin # field_of");
self.popen();
let ib = self.ibox(0);
self.print_type(ty);
self.word(",");
self.space();

if let Some((&first, rest)) = fields.split_first() {
self.print_ident(first);

for &field in rest {
self.word(".");
self.print_ident(field);
}
}
self.end(ib);
self.pclose();
}
}
self.end(ib);
}
Expand Down
2 changes: 2 additions & 0 deletions compiler/rustc_borrowck/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1875,6 +1875,7 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, '_, 'tcx> {
| ty::Str
| ty::Array(_, _)
| ty::Pat(_, _)
| ty::FRT(..)
| ty::Slice(_)
| ty::FnDef(_, _)
| ty::FnPtr(..)
Expand Down Expand Up @@ -1919,6 +1920,7 @@ impl<'a, 'tcx> MirBorrowckCtxt<'a, '_, 'tcx> {
| ty::Str
| ty::Array(_, _)
| ty::Pat(_, _)
| ty::FRT(..)
| ty::Slice(_)
| ty::RawPtr(_, _)
| ty::Ref(_, _, _)
Expand Down
2 changes: 2 additions & 0 deletions compiler/rustc_codegen_ssa/src/debuginfo/type_names.rs
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,8 @@ fn push_debuginfo_type_name<'tcx>(
write!(output, "{:?}", t).unwrap();
}
}
// FIXME(FRTs): implement debuginfo for field representing types
ty::FRT(..) => todo!(),
ty::Slice(inner_type) => {
if cpp_like_debuginfo {
output.push_str("slice2$<");
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_const_eval/src/const_eval/type_info.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ impl<'tcx> InterpCx<'tcx, CompileTimeMachine<'tcx>> {
downcast(sym::Leaf)?.0
}
ty::Adt(_, _)
| ty::FRT(_, _)
| ty::Foreign(_)
| ty::Str
| ty::Pat(_, _)
Expand Down
4 changes: 2 additions & 2 deletions compiler/rustc_const_eval/src/const_eval/valtrees.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ fn const_to_valtree_inner<'tcx>(
}

match ty.kind() {
ty::FnDef(..) => {
ty::FnDef(..) | ty::FRT(..) => {
*num_nodes += 1;
Ok(ty::ValTree::zst(tcx))
}
Expand Down Expand Up @@ -273,7 +273,7 @@ pub fn valtree_to_const_value<'tcx>(
// create inner `MPlace`s which are filled recursively.
// FIXME: Does this need an example?
match *cv.ty.kind() {
ty::FnDef(..) => {
ty::FnDef(..) | ty::FRT(..) => {
assert!(cv.valtree.is_zst());
mir::ConstValue::ZeroSized
}
Expand Down
30 changes: 29 additions & 1 deletion compiler/rustc_const_eval/src/interpret/intrinsics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use rustc_infer::infer::TyCtxtInferExt;
use rustc_middle::mir::interpret::{CTFE_ALLOC_SALT, read_target_uint, write_target_uint};
use rustc_middle::mir::{self, BinOp, ConstValue, NonDivergingIntrinsic};
use rustc_middle::ty::layout::TyAndLayout;
use rustc_middle::ty::{FloatTy, PolyExistentialPredicate, Ty, TyCtxt};
use rustc_middle::ty::{FieldIdData, FloatTy, PolyExistentialPredicate, Ty, TyCtxt};
use rustc_middle::{bug, span_bug, ty};
use rustc_span::{Symbol, sym};
use rustc_trait_selection::traits::{Obligation, ObligationCause, ObligationCtxt};
Expand Down Expand Up @@ -223,6 +223,33 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {

self.write_scalar(Scalar::from_target_usize(offset, self), dest)?;
}
sym::field_offset => {
let frt_ty = instance.args.type_at(0);

let (ty, field) = match frt_ty.kind() {
&ty::FRT(ty, field) => (ty, field),
ty::Alias(..) | ty::Param(..) | ty::Placeholder(..) | ty::Infer(..) => {
// This can happen in code which is generic over the field type.
throw_inval!(TooGeneric)
}
_ => {
span_bug!(self.cur_span(), "expected field representing type, got {frt_ty}")
}
};
let FieldIdData::Resolved { variant, field } = &*field.0 else {
span_bug!(
self.cur_span(),
"expected resolved field representing type, got `field_of!({ty}, {field:?})`"
)
};
let layout = self.layout_of(ty)?;
let cx = ty::layout::LayoutCx::new(*self.tcx, self.typing_env);

let layout = layout.for_variant(&cx, *variant);
let offset = layout.fields.offset(field.index()).bytes();

self.write_scalar(Scalar::from_target_usize(offset, self), dest)?;
}
sym::vtable_for => {
let tp_ty = instance.args.type_at(0);
let result_ty = instance.args.type_at(1);
Expand Down Expand Up @@ -301,6 +328,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
| ty::UnsafeBinder(_)
| ty::Never
| ty::Tuple(_)
| ty::FRT(..)
| ty::Error(_) => ConstValue::from_target_usize(0u64, &tcx),
};
let val = self.const_val_to_op(val, dest.layout.ty, Some(dest.layout))?;
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_const_eval/src/interpret/stack.rs
Original file line number Diff line number Diff line change
Expand Up @@ -507,6 +507,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> {
| ty::Closure(..)
| ty::CoroutineClosure(..)
| ty::Never
| ty::FRT(..)
| ty::Error(_) => true,

ty::Str | ty::Slice(_) | ty::Dynamic(_, _) | ty::Foreign(..) => false,
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_const_eval/src/interpret/validity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -785,6 +785,7 @@ impl<'rt, 'tcx, M: Machine<'tcx>> ValidityVisitor<'rt, 'tcx, M> {
| ty::Dynamic(..)
| ty::Closure(..)
| ty::Pat(..)
| ty::FRT(..)
| ty::CoroutineClosure(..)
| ty::Coroutine(..) => interp_ok(false),
// Some types only occur during typechecking, they have no layout.
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_const_eval/src/util/type_name.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ impl<'tcx> Printer<'tcx> for TypeNamePrinter<'tcx> {
| ty::Float(_)
| ty::Str
| ty::Pat(_, _)
| ty::FRT(..)
| ty::Array(_, _)
| ty::Slice(_)
| ty::RawPtr(_, _)
Expand Down
2 changes: 2 additions & 0 deletions compiler/rustc_feature/src/unstable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -488,6 +488,8 @@ declare_features! (
(unstable, ffi_const, "1.45.0", Some(58328)),
/// Allows the use of `#[ffi_pure]` on foreign functions.
(unstable, ffi_pure, "1.45.0", Some(58329)),
/// Experimental field projections.
(incomplete, field_projections, "CURRENT_RUSTC_VERSION", Some(145383)),
/// Controlling the behavior of fmt::Debug
(unstable, fmt_debug, "1.82.0", Some(129709)),
/// Allows using `#[align(...)]` on function items
Expand Down
10 changes: 10 additions & 0 deletions compiler/rustc_hir/src/hir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1729,6 +1729,12 @@ impl<'hir> Block<'hir> {
}
}

#[derive(Debug, Clone, Copy, HashStable_Generic)]
pub struct TyFieldPath {
pub variant: Option<Ident>,
pub field: Ident,
}

#[derive(Debug, Clone, Copy, HashStable_Generic)]
pub struct TyPat<'hir> {
#[stable_hasher(ignore)]
Expand Down Expand Up @@ -3773,6 +3779,10 @@ pub enum TyKind<'hir, Unambig = ()> {
Err(rustc_span::ErrorGuaranteed),
/// Pattern types (`pattern_type!(u32 is 1..)`)
Pat(&'hir Ty<'hir>, &'hir TyPat<'hir>),
/// Field representing type (`field_of!(Struct, field)`).
///
/// The optional ident is the variant when an enum is passed `field_of!(Enum, Variant.field)`.
FieldOf(&'hir Ty<'hir>, &'hir TyFieldPath),
/// `TyKind::Infer` means the type should be inferred instead of it having been
/// specified. This can appear anywhere in a type.
///
Expand Down
7 changes: 7 additions & 0 deletions compiler/rustc_hir/src/intravisit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1047,6 +1047,13 @@ pub fn walk_ty<'v, V: Visitor<'v>>(visitor: &mut V, typ: &'v Ty<'v, AmbigArg>) -
try_visit!(visitor.visit_ty_unambig(ty));
try_visit!(visitor.visit_pattern_type_pattern(pat));
}
TyKind::FieldOf(ty, TyFieldPath { variant, field }) => {
try_visit!(visitor.visit_ty_unambig(ty));
if let Some(variant) = *variant {
try_visit!(visitor.visit_ident(variant));
}
try_visit!(visitor.visit_ident(*field));
}
}
V::Result::output()
}
Expand Down
6 changes: 6 additions & 0 deletions compiler/rustc_hir/src/lang_items.rs
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,12 @@ language_item_table! {
// Reborrowing related lang-items
Reborrow, sym::reborrow, reborrow, Target::Trait, GenericRequirement::Exact(0);
CoerceShared, sym::coerce_shared, coerce_shared, Target::Trait, GenericRequirement::Exact(0);

// Field projection related lang-items
Field, sym::field, field, Target::Trait, GenericRequirement::Exact(0);
FieldBase, sym::field_base, field_base, Target::AssocTy, GenericRequirement::Exact(0);
FieldType, sym::field_type, field_type, Target::AssocTy, GenericRequirement::Exact(0);
FieldOffset, sym::field_offset, field_offset, Target::AssocConst, GenericRequirement::Exact(0);
}

/// The requirement imposed on the generics of a lang item
Expand Down
2 changes: 2 additions & 0 deletions compiler/rustc_hir_analysis/src/check/intrinsic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ fn intrinsic_operation_unsafety(tcx: TyCtxt<'_>, intrinsic_id: LocalDefId) -> hi
| sym::fabsf128
| sym::fadd_algebraic
| sym::fdiv_algebraic
| sym::field_offset
| sym::floorf16
| sym::floorf32
| sym::floorf64
Expand Down Expand Up @@ -293,6 +294,7 @@ pub(crate) fn check_intrinsic_type(
(1, 0, vec![Ty::new_imm_ptr(tcx, param(0))], tcx.types.usize)
}
sym::offset_of => (1, 0, vec![tcx.types.u32, tcx.types.u32], tcx.types.usize),
sym::field_offset => (1, 0, vec![], tcx.types.usize),
sym::rustc_peek => (1, 0, vec![param(0)], param(0)),
sym::caller_location => (0, 0, vec![], tcx.caller_location_ty()),
sym::assert_inhabited | sym::assert_zero_valid | sym::assert_mem_uninitialized_valid => {
Expand Down
11 changes: 11 additions & 0 deletions compiler/rustc_hir_analysis/src/check/wfcheck.rs
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,17 @@ pub(super) fn check_item<'tcx>(
.with_span_label(sp, "auto trait")
.emit());
}
if is_auto && let rustc_hir::TyKind::FieldOf(..) = impl_.self_ty.kind {
res = res.and(Err(tcx
.dcx()
.struct_span_err(
item.span,
"impls of auto traits for field representing types not supported",
)
.with_span_label(impl_.self_ty.span, "field representing type")
.with_span_label(of_trait.trait_ref.path.span, "auto trait")
.emit()));
}
Comment on lines +264 to +274
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This feels pretty hacky, but I copied this from the previous version.

match header.polarity {
ty::ImplPolarity::Positive => {
res = res.and(check_impl(tcx, item, impl_));
Expand Down
Loading
Loading