Skip to content

Commit

Permalink
Auto merge of #114208 - GKFX:offset_of_enum, r=wesleywiser
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
bors committed Nov 1, 2023
2 parents 11cd1f0 + e742f80 commit 146dafa
Show file tree
Hide file tree
Showing 33 changed files with 477 additions and 89 deletions.
2 changes: 1 addition & 1 deletion compiler/rustc_codegen_cranelift/src/base.rs
Original file line number Diff line number Diff line change
Expand Up @@ -766,7 +766,7 @@ fn codegen_stmt<'tcx>(
NullOp::SizeOf => layout.size.bytes(),
NullOp::AlignOf => layout.align.abi.bytes(),
NullOp::OffsetOf(fields) => {
layout.offset_of_subfield(fx, fields.iter().map(|f| f.index())).bytes()
layout.offset_of_subfield(fx, fields.iter()).bytes()
}
};
let val = CValue::by_val(
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_codegen_ssa/src/mir/rvalue.rs
Original file line number Diff line number Diff line change
Expand Up @@ -680,7 +680,7 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
layout.align.abi.bytes()
}
mir::NullOp::OffsetOf(fields) => {
layout.offset_of_subfield(bx.cx(), fields.iter().map(|f| f.index())).bytes()
layout.offset_of_subfield(bx.cx(), fields.iter()).bytes()
}
};
let val = bx.cx().const_usize(val);
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_const_eval/src/interpret/step.rs
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> {
mir::NullOp::SizeOf => layout.size.bytes(),
mir::NullOp::AlignOf => layout.align.abi.bytes(),
mir::NullOp::OffsetOf(fields) => {
layout.offset_of_subfield(self, fields.iter().map(|f| f.index())).bytes()
layout.offset_of_subfield(self, fields.iter()).bytes()
}
};
self.write_scalar(Scalar::from_target_usize(val, self), &dest)?;
Expand Down
23 changes: 11 additions & 12 deletions compiler/rustc_const_eval/src/transform/validate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1056,16 +1056,23 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
}
}
}
Rvalue::NullaryOp(NullOp::OffsetOf(fields), container) => {
Rvalue::NullaryOp(NullOp::OffsetOf(indices), container) => {
let fail_out_of_bounds = |this: &mut Self, location, field, ty| {
this.fail(location, format!("Out of bounds field {field:?} for {ty:?}"));
};

let mut current_ty = *container;

for field in fields.iter() {
for (variant, field) in indices.iter() {
match current_ty.kind() {
ty::Tuple(fields) => {
if variant != FIRST_VARIANT {
self.fail(
location,
format!("tried to get variant {variant:?} of tuple"),
);
return;
}
let Some(&f_ty) = fields.get(field.as_usize()) else {
fail_out_of_bounds(self, location, field, current_ty);
return;
Expand All @@ -1074,15 +1081,7 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
current_ty = self.tcx.normalize_erasing_regions(self.param_env, f_ty);
}
ty::Adt(adt_def, args) => {
if adt_def.is_enum() {
self.fail(
location,
format!("Cannot get field offset from enum {current_ty:?}"),
);
return;
}

let Some(field) = adt_def.non_enum_variant().fields.get(field) else {
let Some(field) = adt_def.variant(variant).fields.get(field) else {
fail_out_of_bounds(self, location, field, current_ty);
return;
};
Expand All @@ -1093,7 +1092,7 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
_ => {
self.fail(
location,
format!("Cannot get field offset from non-adt type {current_ty:?}"),
format!("Cannot get offset ({variant:?}, {field:?}) from type {current_ty:?}"),
);
return;
}
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_error_codes/src/error_codes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -514,6 +514,7 @@ E0791: include_str!("./error_codes/E0791.md"),
E0792: include_str!("./error_codes/E0792.md"),
E0793: include_str!("./error_codes/E0793.md"),
E0794: include_str!("./error_codes/E0794.md"),
E0795: include_str!("./error_codes/E0795.md"),
}

// Undocumented removed error codes. Note that many removed error codes are kept in the list above
Expand Down
28 changes: 28 additions & 0 deletions compiler/rustc_error_codes/src/error_codes/E0795.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
Invalid argument for the `offset_of!` macro.

Erroneous code example:

```compile_fail,E0795
#![feature(offset_of)]
let x = std::mem::offset_of!(Option<u8>, Some);
```

The `offset_of!` macro gives the offset of a field within a type. It can
navigate through enum variants, but the final component of its second argument
must be a field and not a variant.

The offset of the contained `u8` in the `Option<u8>` can be found by specifying
the field name `0`:

```
#![feature(offset_of)]
let x: usize = std::mem::offset_of!(Option<u8>, Some.0);
```

The discriminant of an enumeration may be read with `core::mem::discriminant`,
but this is not always a value physically present within the enum.

Further information about enum layout may be found at
https://rust-lang.github.io/unsafe-code-guidelines/layout/enums.html.
79 changes: 74 additions & 5 deletions compiler/rustc_hir_typeck/src/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ use rustc_span::edit_distance::find_best_match_for_name;
use rustc_span::hygiene::DesugaringKind;
use rustc_span::source_map::{Span, Spanned};
use rustc_span::symbol::{kw, sym, Ident, Symbol};
use rustc_target::abi::FieldIdx;
use rustc_target::abi::{FieldIdx, FIRST_VARIANT};
use rustc_target::spec::abi::Abi::RustIntrinsic;
use rustc_trait_selection::infer::InferCtxtExt;
use rustc_trait_selection::traits::error_reporting::TypeErrCtxtExt;
Expand Down Expand Up @@ -3107,12 +3107,81 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {

let mut field_indices = Vec::with_capacity(fields.len());
let mut current_container = container;
let mut fields = fields.into_iter();

for &field in fields {
while let Some(&field) = fields.next() {
let container = self.structurally_resolve_type(expr.span, current_container);

match container.kind() {
ty::Adt(container_def, args) if !container_def.is_enum() => {
ty::Adt(container_def, args) if container_def.is_enum() => {
let block = self.tcx.hir().local_def_id_to_hir_id(self.body_id);
let (ident, _def_scope) =
self.tcx.adjust_ident_and_get_scope(field, container_def.did(), block);

let Some((index, variant)) = container_def.variants()
.iter_enumerated()
.find(|(_, v)| v.ident(self.tcx).normalize_to_macros_2_0() == ident) else {
let mut err = type_error_struct!(
self.tcx().sess,
ident.span,
container,
E0599,
"no variant named `{ident}` found for enum `{container}`",
);
err.span_label(field.span, "variant not found");
err.emit();
break;
};
let Some(&subfield) = fields.next() else {
let mut err = type_error_struct!(
self.tcx().sess,
ident.span,
container,
E0795,
"`{ident}` is an enum variant; expected field at end of `offset_of`",
);
err.span_label(field.span, "enum variant");
err.emit();
break;
};
let (subident, sub_def_scope) =
self.tcx.adjust_ident_and_get_scope(subfield, variant.def_id, block);

let Some((subindex, field)) = variant.fields
.iter_enumerated()
.find(|(_, f)| f.ident(self.tcx).normalize_to_macros_2_0() == subident) else {
let mut err = type_error_struct!(
self.tcx().sess,
ident.span,
container,
E0609,
"no field named `{subfield}` on enum variant `{container}::{ident}`",
);
err.span_label(field.span, "this enum variant...");
err.span_label(subident.span, "...does not have this field");
err.emit();
break;
};

let field_ty = self.field_ty(expr.span, field, args);

// FIXME: DSTs with static alignment should be allowed
self.require_type_is_sized(field_ty, expr.span, traits::MiscObligation);

if field.vis.is_accessible_from(sub_def_scope, self.tcx) {
self.tcx.check_stability(field.did, Some(expr.hir_id), expr.span, None);
} else {
self.private_field_err(ident, container_def.did()).emit();
}

// Save the index of all fields regardless of their visibility in case
// of error recovery.
field_indices.push((index, subindex));
current_container = field_ty;

continue;
}
ty::Adt(container_def, args) => {
let block = self.tcx.hir().local_def_id_to_hir_id(self.body_id);
let (ident, def_scope) =
self.tcx.adjust_ident_and_get_scope(field, container_def.did(), block);
Expand All @@ -3135,7 +3204,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {

// Save the index of all fields regardless of their visibility in case
// of error recovery.
field_indices.push(index);
field_indices.push((FIRST_VARIANT, index));
current_container = field_ty;

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

continue;
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_middle/src/mir/syntax.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1354,7 +1354,7 @@ pub enum NullOp<'tcx> {
/// Returns the minimum alignment of a type
AlignOf,
/// Returns the offset of a field
OffsetOf(&'tcx List<FieldIdx>),
OffsetOf(&'tcx List<(VariantIdx, FieldIdx)>),
}

#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_middle/src/thir.rs
Original file line number Diff line number Diff line change
Expand Up @@ -492,7 +492,7 @@ pub enum ExprKind<'tcx> {
/// Field offset (`offset_of!`)
OffsetOf {
container: Ty<'tcx>,
fields: &'tcx List<FieldIdx>,
fields: &'tcx List<(VariantIdx, FieldIdx)>,
},
/// An expression taking a reference to a thread local.
ThreadLocalRef(DefId),
Expand Down
14 changes: 13 additions & 1 deletion compiler/rustc_middle/src/ty/codec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ use rustc_data_structures::fx::FxHashMap;
use rustc_middle::ty::TyCtxt;
use rustc_serialize::{Decodable, Encodable};
use rustc_span::Span;
use rustc_target::abi::FieldIdx;
use rustc_target::abi::{FieldIdx, VariantIdx};
pub use rustc_type_ir::{TyDecoder, TyEncoder};
use std::hash::Hash;
use std::intrinsics;
Expand Down Expand Up @@ -414,6 +414,17 @@ impl<'tcx, D: TyDecoder<I = TyCtxt<'tcx>>> RefDecodable<'tcx, D> for ty::List<Fi
}
}

impl<'tcx, D: TyDecoder<I = TyCtxt<'tcx>>> RefDecodable<'tcx, D>
for ty::List<(VariantIdx, FieldIdx)>
{
fn decode(decoder: &mut D) -> &'tcx Self {
let len = decoder.read_usize();
decoder.interner().mk_offset_of_from_iter(
(0..len).map::<(VariantIdx, FieldIdx), _>(|_| Decodable::decode(decoder)),
)
}
}

impl_decodable_via_ref! {
&'tcx ty::TypeckResults<'tcx>,
&'tcx ty::List<Ty<'tcx>>,
Expand All @@ -426,6 +437,7 @@ impl_decodable_via_ref! {
&'tcx ty::List<ty::BoundVariableKind>,
&'tcx ty::List<ty::Clause<'tcx>>,
&'tcx ty::List<FieldIdx>,
&'tcx ty::List<(VariantIdx, FieldIdx)>,
}

#[macro_export]
Expand Down
11 changes: 11 additions & 0 deletions compiler/rustc_middle/src/ty/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ pub struct CtxtInterners<'tcx> {
predefined_opaques_in_body: InternedSet<'tcx, PredefinedOpaquesData<'tcx>>,
fields: InternedSet<'tcx, List<FieldIdx>>,
local_def_ids: InternedSet<'tcx, List<LocalDefId>>,
offset_of: InternedSet<'tcx, List<(VariantIdx, FieldIdx)>>,
}

impl<'tcx> CtxtInterners<'tcx> {
Expand All @@ -189,6 +190,7 @@ impl<'tcx> CtxtInterners<'tcx> {
predefined_opaques_in_body: Default::default(),
fields: Default::default(),
local_def_ids: Default::default(),
offset_of: Default::default(),
}
}

Expand Down Expand Up @@ -1587,6 +1589,7 @@ slice_interners!(
bound_variable_kinds: pub mk_bound_variable_kinds(ty::BoundVariableKind),
fields: pub mk_fields(FieldIdx),
local_def_ids: intern_local_def_ids(LocalDefId),
offset_of: pub mk_offset_of((VariantIdx, FieldIdx)),
);

impl<'tcx> TyCtxt<'tcx> {
Expand Down Expand Up @@ -1914,6 +1917,14 @@ impl<'tcx> TyCtxt<'tcx> {
T::collect_and_apply(iter, |xs| self.mk_fields(xs))
}

pub fn mk_offset_of_from_iter<I, T>(self, iter: I) -> T::Output
where
I: Iterator<Item = T>,
T: CollectAndApply<(VariantIdx, FieldIdx), &'tcx List<(VariantIdx, FieldIdx)>>,
{
T::collect_and_apply(iter, |xs| self.mk_offset_of(xs))
}

pub fn mk_args_trait(
self,
self_ty: Ty<'tcx>,
Expand Down
12 changes: 8 additions & 4 deletions compiler/rustc_middle/src/ty/typeck_results.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ use rustc_macros::HashStable;
use rustc_middle::mir::FakeReadCause;
use rustc_session::Session;
use rustc_span::Span;
use rustc_target::abi::FieldIdx;
use rustc_target::abi::{FieldIdx, VariantIdx};
use std::{collections::hash_map::Entry, hash::Hash, iter};

use super::RvalueScopes;
Expand Down Expand Up @@ -205,7 +205,7 @@ pub struct TypeckResults<'tcx> {
pub closure_size_eval: LocalDefIdMap<ClosureSizeProfileData<'tcx>>,

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

impl<'tcx> TypeckResults<'tcx> {
Expand Down Expand Up @@ -464,11 +464,15 @@ impl<'tcx> TypeckResults<'tcx> {
&self.coercion_casts
}

pub fn offset_of_data(&self) -> LocalTableInContext<'_, (Ty<'tcx>, Vec<FieldIdx>)> {
pub fn offset_of_data(
&self,
) -> LocalTableInContext<'_, (Ty<'tcx>, Vec<(VariantIdx, FieldIdx)>)> {
LocalTableInContext { hir_owner: self.hir_owner, data: &self.offset_of_data }
}

pub fn offset_of_data_mut(&mut self) -> LocalTableInContextMut<'_, (Ty<'tcx>, Vec<FieldIdx>)> {
pub fn offset_of_data_mut(
&mut self,
) -> LocalTableInContextMut<'_, (Ty<'tcx>, Vec<(VariantIdx, FieldIdx)>)> {
LocalTableInContextMut { hir_owner: self.hir_owner, data: &mut self.offset_of_data }
}
}
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_mir_build/src/thir/cx/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -670,7 +670,7 @@ impl<'tcx> Cx<'tcx> {
hir::ExprKind::OffsetOf(_, _) => {
let data = self.typeck_results.offset_of_data();
let &(container, ref indices) = data.get(expr.hir_id).unwrap();
let fields = tcx.mk_fields_from_iter(indices.iter().copied());
let fields = tcx.mk_offset_of_from_iter(indices.iter().copied());

ExprKind::OffsetOf { container, fields }
}
Expand Down
6 changes: 3 additions & 3 deletions compiler/rustc_mir_transform/src/dataflow_const_prop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -286,9 +286,9 @@ impl<'tcx> ValueAnalysis<'tcx> for ConstAnalysis<'_, 'tcx> {
let val = match null_op {
NullOp::SizeOf if layout.is_sized() => layout.size.bytes(),
NullOp::AlignOf if layout.is_sized() => layout.align.abi.bytes(),
NullOp::OffsetOf(fields) => layout
.offset_of_subfield(&self.ecx, fields.iter().map(|f| f.index()))
.bytes(),
NullOp::OffsetOf(fields) => {
layout.offset_of_subfield(&self.ecx, fields.iter()).bytes()
}
_ => return ValueOrPlace::Value(FlatSet::Top),
};
FlatSet::Elem(Scalar::from_target_usize(val, &self.tcx))
Expand Down
6 changes: 3 additions & 3 deletions compiler/rustc_mir_transform/src/gvn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -467,9 +467,9 @@ impl<'body, 'tcx> VnState<'body, 'tcx> {
let val = match null_op {
NullOp::SizeOf => layout.size.bytes(),
NullOp::AlignOf => layout.align.abi.bytes(),
NullOp::OffsetOf(fields) => layout
.offset_of_subfield(&self.ecx, fields.iter().map(|f| f.index()))
.bytes(),
NullOp::OffsetOf(fields) => {
layout.offset_of_subfield(&self.ecx, fields.iter()).bytes()
}
};
let usize_layout = self.ecx.layout_of(self.tcx.types.usize).unwrap();
let imm = ImmTy::try_from_uint(val, usize_layout)?;
Expand Down
Loading

0 comments on commit 146dafa

Please sign in to comment.