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
24f22b3
parse field representing types
BennoLossin Jan 1, 2026
3a99ff6
lower field representing types to HIR
BennoLossin Jan 1, 2026
da4ff72
lower field representing types to MIR
BennoLossin Jan 1, 2026
16d633e
add field representing types to rustc_public
BennoLossin Jan 1, 2026
1727045
add `Field` trait, lang items and `field_offset` intrinsic
BennoLossin Jan 1, 2026
5723932
add builtin impl of `Field` for field representing types
BennoLossin Jan 1, 2026
833bbb5
add feature gate test
BennoLossin Jan 1, 2026
257014d
add run-pass tests
BennoLossin Jan 1, 2026
5be70e1
test incoherent impls
BennoLossin Jan 1, 2026
d529054
deny accessing FRTs of private fields
BennoLossin Jan 1, 2026
6d94428
deny manual impls of the `Field` trait
BennoLossin Jan 1, 2026
82b0576
deny auto trait and drop impls for FRTs
BennoLossin Jan 1, 2026
c6463cb
do not impl `Field` for FRTs of `repr(packed)` types
BennoLossin Jan 1, 2026
5939bee
fixup! lower field representing types to HIR
BennoLossin Jan 16, 2026
dfc55f8
fixup! lower field representing types to MIR
BennoLossin Jan 16, 2026
6d18e7f
tests: fix ui output
BennoLossin Jan 16, 2026
f56993c
tests: check auto traits and `Copy` for FRTs
BennoLossin Jan 16, 2026
6296230
consolidate tests
BennoLossin Jan 16, 2026
48ed87d
fixup! lower field representing types to MIR
BennoLossin Jan 16, 2026
bb26be9
tests: more nonexistent
BennoLossin Jan 16, 2026
520a709
tests: add invalid syntax
BennoLossin Jan 16, 2026
eaa0f24
tests: fix symbol generation
BennoLossin Jan 16, 2026
f328caf
tests: add nonexistent tuple fields
BennoLossin Jan 16, 2026
caa7bfd
tests: expect(incomplete_features) instead of allow
BennoLossin Jan 16, 2026
ac29483
tests: add offset function
BennoLossin Jan 16, 2026
2d7a52e
tests: bad normalization case for next-solver
BennoLossin Jan 16, 2026
2896c63
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 @@ -4293,6 +4294,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 @@ -1499,6 +1499,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 @@ -104,6 +104,7 @@ impl<'tcx> InterpCx<'tcx, CompileTimeMachine<'tcx>> {
variant
}
ty::Adt(_, _)
| ty::FRT(_, _)
| ty::Foreign(_)
| ty::Pat(_, _)
| ty::Slice(_)
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 @@ -117,6 +117,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 @@ -295,6 +296,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