Skip to content

Commit e936416

Browse files
committed
Support enum variants in offset_of!
1 parent 9d83ac2 commit e936416

File tree

27 files changed

+472
-75
lines changed

27 files changed

+472
-75
lines changed

compiler/rustc_abi/src/lib.rs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1107,6 +1107,39 @@ impl Scalar {
11071107
}
11081108

11091109
// NOTE: This struct is generic over the FieldIdx for rust-analyzer usage.
1110+
rustc_index::newtype_index! {
1111+
/// The *source-order* index of a field in a variant.
1112+
///
1113+
/// This is how most code after type checking refers to fields, rather than
1114+
/// using names (as names have hygiene complications and more complex lookup).
1115+
///
1116+
/// Particularly for `repr(Rust)` types, this may not be the same as *layout* order.
1117+
/// (It is for `repr(C)` `struct`s, however.)
1118+
///
1119+
/// For example, in the following types,
1120+
/// ```rust
1121+
/// # enum Never {}
1122+
/// # #[repr(u16)]
1123+
/// enum Demo1 {
1124+
/// Variant0 { a: Never, b: i32 } = 100,
1125+
/// Variant1 { c: u8, d: u64 } = 10,
1126+
/// }
1127+
/// struct Demo2 { e: u8, f: u16, g: u8 }
1128+
/// ```
1129+
/// `b` is `FieldIdx(1)` in `VariantIdx(0)`,
1130+
/// `d` is `FieldIdx(1)` in `VariantIdx(1)`, and
1131+
/// `f` is `FieldIdx(1)` in `VariantIdx(0)`.
1132+
#[derive(HashStable_Generic)]
1133+
pub struct FieldIdx {}
1134+
}
1135+
1136+
/// `offset_of` can traverse fields and enum variants and should keep track of which is which.
1137+
#[derive(Copy, Clone, Debug, Eq, Hash, HashStable_Generic, PartialEq, Encodable, Decodable)]
1138+
pub enum OffsetOfIdx {
1139+
Field(FieldIdx),
1140+
Variant(VariantIdx),
1141+
}
1142+
11101143
/// Describes how the fields of a type are located in memory.
11111144
#[derive(PartialEq, Eq, Hash, Clone, Debug)]
11121145
#[cfg_attr(feature = "nightly", derive(HashStable_Generic))]

compiler/rustc_codegen_cranelift/src/base.rs

Lines changed: 1 addition & 1 deletion
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

Lines changed: 1 addition & 1 deletion
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

Lines changed: 1 addition & 1 deletion
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

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1056,33 +1056,44 @@ 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;
1065+
let mut indices = indices.into_iter();
10651066

1066-
for field in fields.iter() {
1067-
match current_ty.kind() {
1068-
ty::Tuple(fields) => {
1067+
use rustc_target::abi::OffsetOfIdx::*;
1068+
1069+
while let Some(index) = indices.next() {
1070+
match (current_ty.kind(), index) {
1071+
(ty::Tuple(fields), Field(field)) => {
10691072
let Some(&f_ty) = fields.get(field.as_usize()) else {
10701073
fail_out_of_bounds(self, location, field, current_ty);
10711074
return;
10721075
};
10731076

10741077
current_ty = self.tcx.normalize_erasing_regions(self.param_env, f_ty);
10751078
}
1076-
ty::Adt(adt_def, args) => {
1077-
if adt_def.is_enum() {
1079+
(ty::Adt(adt_def, args), Field(field)) if !adt_def.is_enum() => {
1080+
let Some(field) = adt_def.non_enum_variant().fields.get(field) else {
1081+
fail_out_of_bounds(self, location, field, current_ty);
1082+
return;
1083+
};
1084+
1085+
let f_ty = field.ty(self.tcx, args);
1086+
current_ty = self.tcx.normalize_erasing_regions(self.param_env, f_ty);
1087+
}
1088+
(ty::Adt(adt_def, args), Variant(variant)) if adt_def.is_enum() => {
1089+
let Some(Field(field)) = indices.next() else {
10781090
self.fail(
10791091
location,
1080-
format!("Cannot get field offset from enum {current_ty:?}"),
1092+
format!("enum variant must be followed by field index in offset_of; in {current_ty:?}"),
10811093
);
10821094
return;
1083-
}
1084-
1085-
let Some(field) = adt_def.non_enum_variant().fields.get(field) else {
1095+
};
1096+
let Some(field) = adt_def.variant(variant).fields.get(field) else {
10861097
fail_out_of_bounds(self, location, field, current_ty);
10871098
return;
10881099
};
@@ -1093,7 +1104,7 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
10931104
_ => {
10941105
self.fail(
10951106
location,
1096-
format!("Cannot get field offset from non-adt type {current_ty:?}"),
1107+
format!("Cannot get offset {index:?} from type {current_ty:?}"),
10971108
);
10981109
return;
10991110
}

compiler/rustc_error_codes/src/error_codes.rs

Lines changed: 1 addition & 0 deletions
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
Lines changed: 28 additions & 0 deletions
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

Lines changed: 56 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3103,16 +3103,68 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
31033103
fields: &[Ident],
31043104
expr: &'tcx hir::Expr<'tcx>,
31053105
) -> Ty<'tcx> {
3106+
use rustc_target::abi::OffsetOfIdx::*;
3107+
31063108
let container = self.to_ty(container).normalized;
31073109

31083110
let mut field_indices = Vec::with_capacity(fields.len());
31093111
let mut current_container = container;
3112+
let mut fields = fields.into_iter();
31103113

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

31143117
match container.kind() {
3115-
ty::Adt(container_def, args) if !container_def.is_enum() => {
3118+
ty::Adt(container_def, args) if container_def.is_enum() => {
3119+
let block = self.tcx.hir().local_def_id_to_hir_id(self.body_id);
3120+
let (ident, _def_scope) =
3121+
self.tcx.adjust_ident_and_get_scope(field, container_def.did(), block);
3122+
3123+
if let Some((index, variant)) = container_def.variants()
3124+
.iter_enumerated()
3125+
.find(|(_, v)| v.ident(self.tcx).normalize_to_macros_2_0() == ident)
3126+
{
3127+
let Some(&subfield) = fields.next() else {
3128+
let mut err = type_error_struct!(
3129+
self.tcx().sess,
3130+
ident.span,
3131+
container,
3132+
E0795,
3133+
"`{ident}` is an enum variant; expected field at end of `offset_of`",
3134+
);
3135+
err.span_label(field.span, "enum variant");
3136+
err.emit();
3137+
break;
3138+
};
3139+
let (subident, sub_def_scope) =
3140+
self.tcx.adjust_ident_and_get_scope(subfield, variant.def_id, block);
3141+
3142+
if let Some((subindex, field)) = variant.fields
3143+
.iter_enumerated()
3144+
.find(|(_, f)| f.ident(self.tcx).normalize_to_macros_2_0() == subident)
3145+
{
3146+
let field_ty = self.field_ty(expr.span, field, args);
3147+
3148+
// FIXME: DSTs with static alignment should be allowed
3149+
self.require_type_is_sized(field_ty, expr.span, traits::MiscObligation);
3150+
3151+
if field.vis.is_accessible_from(sub_def_scope, self.tcx) {
3152+
self.tcx.check_stability(field.did, Some(expr.hir_id), expr.span, None);
3153+
} else {
3154+
self.private_field_err(ident, container_def.did()).emit();
3155+
}
3156+
3157+
// Save the index of all fields regardless of their visibility in case
3158+
// of error recovery.
3159+
field_indices.push(Variant(index));
3160+
field_indices.push(Field(subindex));
3161+
current_container = field_ty;
3162+
3163+
continue;
3164+
}
3165+
}
3166+
}
3167+
ty::Adt(container_def, args) => {
31163168
let block = self.tcx.hir().local_def_id_to_hir_id(self.body_id);
31173169
let (ident, def_scope) =
31183170
self.tcx.adjust_ident_and_get_scope(field, container_def.did(), block);
@@ -3135,7 +3187,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
31353187

31363188
// Save the index of all fields regardless of their visibility in case
31373189
// of error recovery.
3138-
field_indices.push(index);
3190+
field_indices.push(Field(index));
31393191
current_container = field_ty;
31403192

31413193
continue;
@@ -3149,7 +3201,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
31493201
self.require_type_is_sized(ty, expr.span, traits::MiscObligation);
31503202
}
31513203
if let Some(&field_ty) = tys.get(index) {
3152-
field_indices.push(index.into());
3204+
field_indices.push(Field(index.into()));
31533205
current_container = field_ty;
31543206

31553207
continue;

compiler/rustc_middle/src/mir/syntax.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ use rustc_hir::def_id::DefId;
1717
use rustc_hir::{self as hir};
1818
use rustc_hir::{self, CoroutineKind};
1919
use rustc_index::IndexVec;
20-
use rustc_target::abi::{FieldIdx, VariantIdx};
20+
use rustc_target::abi::{FieldIdx, OffsetOfIdx, VariantIdx};
2121

2222
use rustc_ast::Mutability;
2323
use rustc_span::def_id::LocalDefId;
@@ -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<OffsetOfIdx>),
13581358
}
13591359

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

compiler/rustc_middle/src/thir.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ use rustc_middle::ty::{
2525
};
2626
use rustc_span::def_id::LocalDefId;
2727
use rustc_span::{sym, ErrorGuaranteed, Span, Symbol, DUMMY_SP};
28-
use rustc_target::abi::{FieldIdx, VariantIdx};
28+
use rustc_span::{sym, Span, Symbol, DUMMY_SP};
29+
use rustc_target::abi::{FieldIdx, OffsetOfIdx, VariantIdx};
2930
use rustc_target::asm::InlineAsmRegOrRegClass;
3031
use std::fmt;
3132
use std::ops::Index;
@@ -490,7 +491,7 @@ pub enum ExprKind<'tcx> {
490491
/// Field offset (`offset_of!`)
491492
OffsetOf {
492493
container: Ty<'tcx>,
493-
fields: &'tcx List<FieldIdx>,
494+
fields: &'tcx List<OffsetOfIdx>,
494495
},
495496
/// An expression taking a reference to a thread local.
496497
ThreadLocalRef(DefId),

compiler/rustc_middle/src/ty/codec.rs

Lines changed: 11 additions & 1 deletion
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, OffsetOfIdx};
2323
pub use rustc_type_ir::{TyDecoder, TyEncoder};
2424
use std::hash::Hash;
2525
use std::intrinsics;
@@ -414,6 +414,15 @@ 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> for ty::List<OffsetOfIdx> {
418+
fn decode(decoder: &mut D) -> &'tcx Self {
419+
let len = decoder.read_usize();
420+
decoder
421+
.interner()
422+
.mk_offset_of_from_iter((0..len).map::<OffsetOfIdx, _>(|_| Decodable::decode(decoder)))
423+
}
424+
}
425+
417426
impl_decodable_via_ref! {
418427
&'tcx ty::TypeckResults<'tcx>,
419428
&'tcx ty::List<Ty<'tcx>>,
@@ -426,6 +435,7 @@ impl_decodable_via_ref! {
426435
&'tcx ty::List<ty::BoundVariableKind>,
427436
&'tcx ty::List<ty::Clause<'tcx>>,
428437
&'tcx ty::List<FieldIdx>,
438+
&'tcx ty::List<OffsetOfIdx>,
429439
}
430440

431441
#[macro_export]

compiler/rustc_middle/src/ty/context.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ use rustc_session::{Limit, MetadataKind, Session};
6363
use rustc_span::def_id::{DefPathHash, StableCrateId};
6464
use rustc_span::symbol::{kw, sym, Ident, Symbol};
6565
use rustc_span::{Span, DUMMY_SP};
66-
use rustc_target::abi::{FieldIdx, Layout, LayoutS, TargetDataLayout, VariantIdx};
66+
use rustc_target::abi::{FieldIdx, Layout, LayoutS, OffsetOfIdx, TargetDataLayout, VariantIdx};
6767
use rustc_target::spec::abi;
6868
use rustc_type_ir::TyKind::*;
6969
use rustc_type_ir::WithCachedTypeInfo;
@@ -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<OffsetOfIdx>>,
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(OffsetOfIdx),
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<OffsetOfIdx, &'tcx List<OffsetOfIdx>>,
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>,

0 commit comments

Comments
 (0)