Skip to content

Commit 146dafa

Browse files
committed
Auto merge of #114208 - GKFX:offset_of_enum, r=wesleywiser
Support enum variants in offset_of! This MR implements support for navigating through enum variants in `offset_of!`, placing the enum variant name in the second argument to `offset_of!`. The RFC placed it in the first argument, but I think it interacts better with nested field access in the second, as you can then write things like ```rust offset_of!(Type, field.Variant.field) ``` Alternatively, a syntactic distinction could be made between variants and fields (e.g. `field::Variant.field`) but I'm not convinced this would be helpful. [RFC 3308 # Enum Support](https://rust-lang.github.io/rfcs/3308-offset_of.html#enum-support-offset_ofsomeenumstructvariant-field_on_variant) Tracking Issue #106655.
2 parents 11cd1f0 + e742f80 commit 146dafa

33 files changed

+477
-89
lines changed

compiler/rustc_codegen_cranelift/src/base.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -766,7 +766,7 @@ fn codegen_stmt<'tcx>(
766766
NullOp::SizeOf => layout.size.bytes(),
767767
NullOp::AlignOf => layout.align.abi.bytes(),
768768
NullOp::OffsetOf(fields) => {
769-
layout.offset_of_subfield(fx, fields.iter().map(|f| f.index())).bytes()
769+
layout.offset_of_subfield(fx, fields.iter()).bytes()
770770
}
771771
};
772772
let val = CValue::by_val(

compiler/rustc_codegen_ssa/src/mir/rvalue.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -680,7 +680,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
680680
layout.align.abi.bytes()
681681
}
682682
mir::NullOp::OffsetOf(fields) => {
683-
layout.offset_of_subfield(bx.cx(), fields.iter().map(|f| f.index())).bytes()
683+
layout.offset_of_subfield(bx.cx(), fields.iter()).bytes()
684684
}
685685
};
686686
let val = bx.cx().const_usize(val);

compiler/rustc_const_eval/src/interpret/step.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -275,7 +275,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
275275
mir::NullOp::SizeOf => layout.size.bytes(),
276276
mir::NullOp::AlignOf => layout.align.abi.bytes(),
277277
mir::NullOp::OffsetOf(fields) => {
278-
layout.offset_of_subfield(self, fields.iter().map(|f| f.index())).bytes()
278+
layout.offset_of_subfield(self, fields.iter()).bytes()
279279
}
280280
};
281281
self.write_scalar(Scalar::from_target_usize(val, self), &dest)?;

compiler/rustc_const_eval/src/transform/validate.rs

+11-12
Original file line numberDiff line numberDiff line change
@@ -1056,16 +1056,23 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
10561056
}
10571057
}
10581058
}
1059-
Rvalue::NullaryOp(NullOp::OffsetOf(fields), container) => {
1059+
Rvalue::NullaryOp(NullOp::OffsetOf(indices), container) => {
10601060
let fail_out_of_bounds = |this: &mut Self, location, field, ty| {
10611061
this.fail(location, format!("Out of bounds field {field:?} for {ty:?}"));
10621062
};
10631063

10641064
let mut current_ty = *container;
10651065

1066-
for field in fields.iter() {
1066+
for (variant, field) in indices.iter() {
10671067
match current_ty.kind() {
10681068
ty::Tuple(fields) => {
1069+
if variant != FIRST_VARIANT {
1070+
self.fail(
1071+
location,
1072+
format!("tried to get variant {variant:?} of tuple"),
1073+
);
1074+
return;
1075+
}
10691076
let Some(&f_ty) = fields.get(field.as_usize()) else {
10701077
fail_out_of_bounds(self, location, field, current_ty);
10711078
return;
@@ -1074,15 +1081,7 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
10741081
current_ty = self.tcx.normalize_erasing_regions(self.param_env, f_ty);
10751082
}
10761083
ty::Adt(adt_def, args) => {
1077-
if adt_def.is_enum() {
1078-
self.fail(
1079-
location,
1080-
format!("Cannot get field offset from enum {current_ty:?}"),
1081-
);
1082-
return;
1083-
}
1084-
1085-
let Some(field) = adt_def.non_enum_variant().fields.get(field) else {
1084+
let Some(field) = adt_def.variant(variant).fields.get(field) else {
10861085
fail_out_of_bounds(self, location, field, current_ty);
10871086
return;
10881087
};
@@ -1093,7 +1092,7 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
10931092
_ => {
10941093
self.fail(
10951094
location,
1096-
format!("Cannot get field offset from non-adt type {current_ty:?}"),
1095+
format!("Cannot get offset ({variant:?}, {field:?}) from type {current_ty:?}"),
10971096
);
10981097
return;
10991098
}

compiler/rustc_error_codes/src/error_codes.rs

+1
Original file line numberDiff line numberDiff line change
@@ -514,6 +514,7 @@ E0791: include_str!("./error_codes/E0791.md"),
514514
E0792: include_str!("./error_codes/E0792.md"),
515515
E0793: include_str!("./error_codes/E0793.md"),
516516
E0794: include_str!("./error_codes/E0794.md"),
517+
E0795: include_str!("./error_codes/E0795.md"),
517518
}
518519

519520
// Undocumented removed error codes. Note that many removed error codes are kept in the list above
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
Invalid argument for the `offset_of!` macro.
2+
3+
Erroneous code example:
4+
5+
```compile_fail,E0795
6+
#![feature(offset_of)]
7+
8+
let x = std::mem::offset_of!(Option<u8>, Some);
9+
```
10+
11+
The `offset_of!` macro gives the offset of a field within a type. It can
12+
navigate through enum variants, but the final component of its second argument
13+
must be a field and not a variant.
14+
15+
The offset of the contained `u8` in the `Option<u8>` can be found by specifying
16+
the field name `0`:
17+
18+
```
19+
#![feature(offset_of)]
20+
21+
let x: usize = std::mem::offset_of!(Option<u8>, Some.0);
22+
```
23+
24+
The discriminant of an enumeration may be read with `core::mem::discriminant`,
25+
but this is not always a value physically present within the enum.
26+
27+
Further information about enum layout may be found at
28+
https://rust-lang.github.io/unsafe-code-guidelines/layout/enums.html.

compiler/rustc_hir_typeck/src/expr.rs

+74-5
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ use rustc_span::edit_distance::find_best_match_for_name;
5454
use rustc_span::hygiene::DesugaringKind;
5555
use rustc_span::source_map::{Span, Spanned};
5656
use rustc_span::symbol::{kw, sym, Ident, Symbol};
57-
use rustc_target::abi::FieldIdx;
57+
use rustc_target::abi::{FieldIdx, FIRST_VARIANT};
5858
use rustc_target::spec::abi::Abi::RustIntrinsic;
5959
use rustc_trait_selection::infer::InferCtxtExt;
6060
use rustc_trait_selection::traits::error_reporting::TypeErrCtxtExt;
@@ -3107,12 +3107,81 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
31073107

31083108
let mut field_indices = Vec::with_capacity(fields.len());
31093109
let mut current_container = container;
3110+
let mut fields = fields.into_iter();
31103111

3111-
for &field in fields {
3112+
while let Some(&field) = fields.next() {
31123113
let container = self.structurally_resolve_type(expr.span, current_container);
31133114

31143115
match container.kind() {
3115-
ty::Adt(container_def, args) if !container_def.is_enum() => {
3116+
ty::Adt(container_def, args) if container_def.is_enum() => {
3117+
let block = self.tcx.hir().local_def_id_to_hir_id(self.body_id);
3118+
let (ident, _def_scope) =
3119+
self.tcx.adjust_ident_and_get_scope(field, container_def.did(), block);
3120+
3121+
let Some((index, variant)) = container_def.variants()
3122+
.iter_enumerated()
3123+
.find(|(_, v)| v.ident(self.tcx).normalize_to_macros_2_0() == ident) else {
3124+
let mut err = type_error_struct!(
3125+
self.tcx().sess,
3126+
ident.span,
3127+
container,
3128+
E0599,
3129+
"no variant named `{ident}` found for enum `{container}`",
3130+
);
3131+
err.span_label(field.span, "variant not found");
3132+
err.emit();
3133+
break;
3134+
};
3135+
let Some(&subfield) = fields.next() else {
3136+
let mut err = type_error_struct!(
3137+
self.tcx().sess,
3138+
ident.span,
3139+
container,
3140+
E0795,
3141+
"`{ident}` is an enum variant; expected field at end of `offset_of`",
3142+
);
3143+
err.span_label(field.span, "enum variant");
3144+
err.emit();
3145+
break;
3146+
};
3147+
let (subident, sub_def_scope) =
3148+
self.tcx.adjust_ident_and_get_scope(subfield, variant.def_id, block);
3149+
3150+
let Some((subindex, field)) = variant.fields
3151+
.iter_enumerated()
3152+
.find(|(_, f)| f.ident(self.tcx).normalize_to_macros_2_0() == subident) else {
3153+
let mut err = type_error_struct!(
3154+
self.tcx().sess,
3155+
ident.span,
3156+
container,
3157+
E0609,
3158+
"no field named `{subfield}` on enum variant `{container}::{ident}`",
3159+
);
3160+
err.span_label(field.span, "this enum variant...");
3161+
err.span_label(subident.span, "...does not have this field");
3162+
err.emit();
3163+
break;
3164+
};
3165+
3166+
let field_ty = self.field_ty(expr.span, field, args);
3167+
3168+
// FIXME: DSTs with static alignment should be allowed
3169+
self.require_type_is_sized(field_ty, expr.span, traits::MiscObligation);
3170+
3171+
if field.vis.is_accessible_from(sub_def_scope, self.tcx) {
3172+
self.tcx.check_stability(field.did, Some(expr.hir_id), expr.span, None);
3173+
} else {
3174+
self.private_field_err(ident, container_def.did()).emit();
3175+
}
3176+
3177+
// Save the index of all fields regardless of their visibility in case
3178+
// of error recovery.
3179+
field_indices.push((index, subindex));
3180+
current_container = field_ty;
3181+
3182+
continue;
3183+
}
3184+
ty::Adt(container_def, args) => {
31163185
let block = self.tcx.hir().local_def_id_to_hir_id(self.body_id);
31173186
let (ident, def_scope) =
31183187
self.tcx.adjust_ident_and_get_scope(field, container_def.did(), block);
@@ -3135,7 +3204,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
31353204

31363205
// Save the index of all fields regardless of their visibility in case
31373206
// of error recovery.
3138-
field_indices.push(index);
3207+
field_indices.push((FIRST_VARIANT, index));
31393208
current_container = field_ty;
31403209

31413210
continue;
@@ -3149,7 +3218,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
31493218
self.require_type_is_sized(ty, expr.span, traits::MiscObligation);
31503219
}
31513220
if let Some(&field_ty) = tys.get(index) {
3152-
field_indices.push(index.into());
3221+
field_indices.push((FIRST_VARIANT, index.into()));
31533222
current_container = field_ty;
31543223

31553224
continue;

compiler/rustc_middle/src/mir/syntax.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1354,7 +1354,7 @@ pub enum NullOp<'tcx> {
13541354
/// Returns the minimum alignment of a type
13551355
AlignOf,
13561356
/// Returns the offset of a field
1357-
OffsetOf(&'tcx List<FieldIdx>),
1357+
OffsetOf(&'tcx List<(VariantIdx, FieldIdx)>),
13581358
}
13591359

13601360
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]

compiler/rustc_middle/src/thir.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -492,7 +492,7 @@ pub enum ExprKind<'tcx> {
492492
/// Field offset (`offset_of!`)
493493
OffsetOf {
494494
container: Ty<'tcx>,
495-
fields: &'tcx List<FieldIdx>,
495+
fields: &'tcx List<(VariantIdx, FieldIdx)>,
496496
},
497497
/// An expression taking a reference to a thread local.
498498
ThreadLocalRef(DefId),

compiler/rustc_middle/src/ty/codec.rs

+13-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ use rustc_data_structures::fx::FxHashMap;
1919
use rustc_middle::ty::TyCtxt;
2020
use rustc_serialize::{Decodable, Encodable};
2121
use rustc_span::Span;
22-
use rustc_target::abi::FieldIdx;
22+
use rustc_target::abi::{FieldIdx, VariantIdx};
2323
pub use rustc_type_ir::{TyDecoder, TyEncoder};
2424
use std::hash::Hash;
2525
use std::intrinsics;
@@ -414,6 +414,17 @@ impl<'tcx, D: TyDecoder<I = TyCtxt<'tcx>>> RefDecodable<'tcx, D> for ty::List<Fi
414414
}
415415
}
416416

417+
impl<'tcx, D: TyDecoder<I = TyCtxt<'tcx>>> RefDecodable<'tcx, D>
418+
for ty::List<(VariantIdx, FieldIdx)>
419+
{
420+
fn decode(decoder: &mut D) -> &'tcx Self {
421+
let len = decoder.read_usize();
422+
decoder.interner().mk_offset_of_from_iter(
423+
(0..len).map::<(VariantIdx, FieldIdx), _>(|_| Decodable::decode(decoder)),
424+
)
425+
}
426+
}
427+
417428
impl_decodable_via_ref! {
418429
&'tcx ty::TypeckResults<'tcx>,
419430
&'tcx ty::List<Ty<'tcx>>,
@@ -426,6 +437,7 @@ impl_decodable_via_ref! {
426437
&'tcx ty::List<ty::BoundVariableKind>,
427438
&'tcx ty::List<ty::Clause<'tcx>>,
428439
&'tcx ty::List<FieldIdx>,
440+
&'tcx ty::List<(VariantIdx, FieldIdx)>,
429441
}
430442

431443
#[macro_export]

compiler/rustc_middle/src/ty/context.rs

+11
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,7 @@ pub struct CtxtInterners<'tcx> {
163163
predefined_opaques_in_body: InternedSet<'tcx, PredefinedOpaquesData<'tcx>>,
164164
fields: InternedSet<'tcx, List<FieldIdx>>,
165165
local_def_ids: InternedSet<'tcx, List<LocalDefId>>,
166+
offset_of: InternedSet<'tcx, List<(VariantIdx, FieldIdx)>>,
166167
}
167168

168169
impl<'tcx> CtxtInterners<'tcx> {
@@ -189,6 +190,7 @@ impl<'tcx> CtxtInterners<'tcx> {
189190
predefined_opaques_in_body: Default::default(),
190191
fields: Default::default(),
191192
local_def_ids: Default::default(),
193+
offset_of: Default::default(),
192194
}
193195
}
194196

@@ -1587,6 +1589,7 @@ slice_interners!(
15871589
bound_variable_kinds: pub mk_bound_variable_kinds(ty::BoundVariableKind),
15881590
fields: pub mk_fields(FieldIdx),
15891591
local_def_ids: intern_local_def_ids(LocalDefId),
1592+
offset_of: pub mk_offset_of((VariantIdx, FieldIdx)),
15901593
);
15911594

15921595
impl<'tcx> TyCtxt<'tcx> {
@@ -1914,6 +1917,14 @@ impl<'tcx> TyCtxt<'tcx> {
19141917
T::collect_and_apply(iter, |xs| self.mk_fields(xs))
19151918
}
19161919

1920+
pub fn mk_offset_of_from_iter<I, T>(self, iter: I) -> T::Output
1921+
where
1922+
I: Iterator<Item = T>,
1923+
T: CollectAndApply<(VariantIdx, FieldIdx), &'tcx List<(VariantIdx, FieldIdx)>>,
1924+
{
1925+
T::collect_and_apply(iter, |xs| self.mk_offset_of(xs))
1926+
}
1927+
19171928
pub fn mk_args_trait(
19181929
self,
19191930
self_ty: Ty<'tcx>,

compiler/rustc_middle/src/ty/typeck_results.rs

+8-4
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ use rustc_macros::HashStable;
2424
use rustc_middle::mir::FakeReadCause;
2525
use rustc_session::Session;
2626
use rustc_span::Span;
27-
use rustc_target::abi::FieldIdx;
27+
use rustc_target::abi::{FieldIdx, VariantIdx};
2828
use std::{collections::hash_map::Entry, hash::Hash, iter};
2929

3030
use super::RvalueScopes;
@@ -205,7 +205,7 @@ pub struct TypeckResults<'tcx> {
205205
pub closure_size_eval: LocalDefIdMap<ClosureSizeProfileData<'tcx>>,
206206

207207
/// Container types and field indices of `offset_of!` expressions
208-
offset_of_data: ItemLocalMap<(Ty<'tcx>, Vec<FieldIdx>)>,
208+
offset_of_data: ItemLocalMap<(Ty<'tcx>, Vec<(VariantIdx, FieldIdx)>)>,
209209
}
210210

211211
impl<'tcx> TypeckResults<'tcx> {
@@ -464,11 +464,15 @@ impl<'tcx> TypeckResults<'tcx> {
464464
&self.coercion_casts
465465
}
466466

467-
pub fn offset_of_data(&self) -> LocalTableInContext<'_, (Ty<'tcx>, Vec<FieldIdx>)> {
467+
pub fn offset_of_data(
468+
&self,
469+
) -> LocalTableInContext<'_, (Ty<'tcx>, Vec<(VariantIdx, FieldIdx)>)> {
468470
LocalTableInContext { hir_owner: self.hir_owner, data: &self.offset_of_data }
469471
}
470472

471-
pub fn offset_of_data_mut(&mut self) -> LocalTableInContextMut<'_, (Ty<'tcx>, Vec<FieldIdx>)> {
473+
pub fn offset_of_data_mut(
474+
&mut self,
475+
) -> LocalTableInContextMut<'_, (Ty<'tcx>, Vec<(VariantIdx, FieldIdx)>)> {
472476
LocalTableInContextMut { hir_owner: self.hir_owner, data: &mut self.offset_of_data }
473477
}
474478
}

compiler/rustc_mir_build/src/thir/cx/expr.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -670,7 +670,7 @@ impl<'tcx> Cx<'tcx> {
670670
hir::ExprKind::OffsetOf(_, _) => {
671671
let data = self.typeck_results.offset_of_data();
672672
let &(container, ref indices) = data.get(expr.hir_id).unwrap();
673-
let fields = tcx.mk_fields_from_iter(indices.iter().copied());
673+
let fields = tcx.mk_offset_of_from_iter(indices.iter().copied());
674674

675675
ExprKind::OffsetOf { container, fields }
676676
}

compiler/rustc_mir_transform/src/dataflow_const_prop.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -286,9 +286,9 @@ impl<'tcx> ValueAnalysis<'tcx> for ConstAnalysis<'_, 'tcx> {
286286
let val = match null_op {
287287
NullOp::SizeOf if layout.is_sized() => layout.size.bytes(),
288288
NullOp::AlignOf if layout.is_sized() => layout.align.abi.bytes(),
289-
NullOp::OffsetOf(fields) => layout
290-
.offset_of_subfield(&self.ecx, fields.iter().map(|f| f.index()))
291-
.bytes(),
289+
NullOp::OffsetOf(fields) => {
290+
layout.offset_of_subfield(&self.ecx, fields.iter()).bytes()
291+
}
292292
_ => return ValueOrPlace::Value(FlatSet::Top),
293293
};
294294
FlatSet::Elem(Scalar::from_target_usize(val, &self.tcx))

compiler/rustc_mir_transform/src/gvn.rs

+3-3
Original file line numberDiff line numberDiff line change
@@ -467,9 +467,9 @@ impl<'body, 'tcx> VnState<'body, 'tcx> {
467467
let val = match null_op {
468468
NullOp::SizeOf => layout.size.bytes(),
469469
NullOp::AlignOf => layout.align.abi.bytes(),
470-
NullOp::OffsetOf(fields) => layout
471-
.offset_of_subfield(&self.ecx, fields.iter().map(|f| f.index()))
472-
.bytes(),
470+
NullOp::OffsetOf(fields) => {
471+
layout.offset_of_subfield(&self.ecx, fields.iter()).bytes()
472+
}
473473
};
474474
let usize_layout = self.ecx.layout_of(self.tcx.types.usize).unwrap();
475475
let imm = ImmTy::try_from_uint(val, usize_layout)?;

0 commit comments

Comments
 (0)