about summary refs log tree commit diff
diff options
context:
space:
mode:
authorGeorge Bateman <george.bateman16@gmail.com>2023-08-15 22:32:55 +0100
committerGeorge Bateman <george.bateman16@gmail.com>2023-10-31 23:26:02 +0000
commitd995bd61e77986d8466c4379ca575683c594a9c6 (patch)
treeb3e8cc827024b1c70f2056a18a968caaab004811
parente936416a8d3cfb501504f00d7250d5b595fed244 (diff)
downloadrust-d995bd61e77986d8466c4379ca575683c594a9c6.tar.gz
rust-d995bd61e77986d8466c4379ca575683c594a9c6.zip
Enums in offset_of: update based on est31, scottmcm & llogiq review
-rw-r--r--compiler/rustc_abi/src/lib.rs7
-rw-r--r--compiler/rustc_const_eval/src/transform/validate.rs36
-rw-r--r--compiler/rustc_hir_typeck/src/expr.rs101
-rw-r--r--compiler/rustc_middle/src/mir/syntax.rs4
-rw-r--r--compiler/rustc_middle/src/thir.rs4
-rw-r--r--compiler/rustc_middle/src/ty/codec.rs14
-rw-r--r--compiler/rustc_middle/src/ty/context.rs8
-rw-r--r--compiler/rustc_middle/src/ty/typeck_results.rs10
-rw-r--r--compiler/rustc_passes/src/dead.rs25
-rw-r--r--compiler/rustc_smir/src/rustc_smir/mod.rs11
-rw-r--r--compiler/rustc_target/src/abi/mod.rs29
-rw-r--r--compiler/stable_mir/src/mir/body.rs2
-rw-r--r--library/core/src/mem/mod.rs10
-rw-r--r--tests/ui/offset-of/offset-of-enum.rs3
-rw-r--r--tests/ui/offset-of/offset-of-enum.stderr26
15 files changed, 150 insertions, 140 deletions
diff --git a/compiler/rustc_abi/src/lib.rs b/compiler/rustc_abi/src/lib.rs
index 0654bfd11c5..9626eeea03b 100644
--- a/compiler/rustc_abi/src/lib.rs
+++ b/compiler/rustc_abi/src/lib.rs
@@ -1133,13 +1133,6 @@ rustc_index::newtype_index! {
     pub struct FieldIdx {}
 }
 
-/// `offset_of` can traverse fields and enum variants and should keep track of which is which.
-#[derive(Copy, Clone, Debug, Eq, Hash, HashStable_Generic, PartialEq, Encodable, Decodable)]
-pub enum OffsetOfIdx {
-    Field(FieldIdx),
-    Variant(VariantIdx),
-}
-
 /// Describes how the fields of a type are located in memory.
 #[derive(PartialEq, Eq, Hash, Clone, Debug)]
 #[cfg_attr(feature = "nightly", derive(HashStable_Generic))]
diff --git a/compiler/rustc_const_eval/src/transform/validate.rs b/compiler/rustc_const_eval/src/transform/validate.rs
index bdccd9b0edb..4d71f763667 100644
--- a/compiler/rustc_const_eval/src/transform/validate.rs
+++ b/compiler/rustc_const_eval/src/transform/validate.rs
@@ -1062,37 +1062,25 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
                 };
 
                 let mut current_ty = *container;
-                let mut indices = indices.into_iter();
 
-                use rustc_target::abi::OffsetOfIdx::*;
-
-                while let Some(index) = indices.next() {
-                    match (current_ty.kind(), index) {
-                        (ty::Tuple(fields), Field(field)) => {
-                            let Some(&f_ty) = fields.get(field.as_usize()) else {
-                                fail_out_of_bounds(self, location, field, current_ty);
+                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;
-                            };
-
-                            current_ty = self.tcx.normalize_erasing_regions(self.param_env, f_ty);
-                        }
-                        (ty::Adt(adt_def, args), Field(field)) if !adt_def.is_enum() => {
-                            let Some(field) = adt_def.non_enum_variant().fields.get(field) else {
+                            }
+                            let Some(&f_ty) = fields.get(field.as_usize()) else {
                                 fail_out_of_bounds(self, location, field, current_ty);
                                 return;
                             };
 
-                            let f_ty = field.ty(self.tcx, args);
                             current_ty = self.tcx.normalize_erasing_regions(self.param_env, f_ty);
                         }
-                        (ty::Adt(adt_def, args), Variant(variant)) if adt_def.is_enum() => {
-                            let Some(Field(field)) = indices.next() else {
-                                self.fail(
-                                    location,
-                                    format!("enum variant must be followed by field index in offset_of; in {current_ty:?}"),
-                                );
-                                return;
-                            };
+                        ty::Adt(adt_def, args) => {
                             let Some(field) = adt_def.variant(variant).fields.get(field) else {
                                 fail_out_of_bounds(self, location, field, current_ty);
                                 return;
@@ -1104,7 +1092,7 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> {
                         _ => {
                             self.fail(
                                 location,
-                                format!("Cannot get offset {index:?} from type {current_ty:?}"),
+                                format!("Cannot get offset ({variant:?}, {field:?}) from type {current_ty:?}"),
                             );
                             return;
                         }
diff --git a/compiler/rustc_hir_typeck/src/expr.rs b/compiler/rustc_hir_typeck/src/expr.rs
index 394a9033765..bdac886bf9e 100644
--- a/compiler/rustc_hir_typeck/src/expr.rs
+++ b/compiler/rustc_hir_typeck/src/expr.rs
@@ -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;
@@ -3103,8 +3103,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
         fields: &[Ident],
         expr: &'tcx hir::Expr<'tcx>,
     ) -> Ty<'tcx> {
-        use rustc_target::abi::OffsetOfIdx::*;
-
         let container = self.to_ty(container).normalized;
 
         let mut field_indices = Vec::with_capacity(fields.len());
@@ -3120,49 +3118,68 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
                     let (ident, _def_scope) =
                         self.tcx.adjust_ident_and_get_scope(field, container_def.did(), block);
 
-                    if let Some((index, variant)) = container_def.variants()
+                    let Some((index, variant)) = container_def.variants()
                         .iter_enumerated()
-                        .find(|(_, v)| v.ident(self.tcx).normalize_to_macros_2_0() == ident)
-                    {
-                        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);
-
-                        if let Some((subindex, field)) = variant.fields
-                            .iter_enumerated()
-                            .find(|(_, f)| f.ident(self.tcx).normalize_to_macros_2_0() == subident)
-                        {
-                            let field_ty = self.field_ty(expr.span, field, args);
+                        .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);
 
-                            // FIXME: DSTs with static alignment should be allowed
-                            self.require_type_is_sized(field_ty, expr.span, traits::MiscObligation);
+                    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;
+                    };
 
-                            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();
-                            }
+                    let field_ty = self.field_ty(expr.span, field, args);
 
-                            // Save the index of all fields regardless of their visibility in case
-                            // of error recovery.
-                            field_indices.push(Variant(index));
-                            field_indices.push(Field(subindex));
-                            current_container = field_ty;
+                    // FIXME: DSTs with static alignment should be allowed
+                    self.require_type_is_sized(field_ty, expr.span, traits::MiscObligation);
 
-                            continue;
-                        }
+                    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);
@@ -3187,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(Field(index));
+                        field_indices.push((FIRST_VARIANT, index));
                         current_container = field_ty;
 
                         continue;
@@ -3201,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(Field(index.into()));
+                            field_indices.push((FIRST_VARIANT, index.into()));
                             current_container = field_ty;
 
                             continue;
diff --git a/compiler/rustc_middle/src/mir/syntax.rs b/compiler/rustc_middle/src/mir/syntax.rs
index d0094a81634..b6543affc6d 100644
--- a/compiler/rustc_middle/src/mir/syntax.rs
+++ b/compiler/rustc_middle/src/mir/syntax.rs
@@ -17,7 +17,7 @@ use rustc_hir::def_id::DefId;
 use rustc_hir::{self as hir};
 use rustc_hir::{self, CoroutineKind};
 use rustc_index::IndexVec;
-use rustc_target::abi::{FieldIdx, OffsetOfIdx, VariantIdx};
+use rustc_target::abi::{FieldIdx, VariantIdx};
 
 use rustc_ast::Mutability;
 use rustc_span::def_id::LocalDefId;
@@ -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<OffsetOfIdx>),
+    OffsetOf(&'tcx List<(VariantIdx, FieldIdx)>),
 }
 
 #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
diff --git a/compiler/rustc_middle/src/thir.rs b/compiler/rustc_middle/src/thir.rs
index 2ee726c76bf..3780eb4f9ac 100644
--- a/compiler/rustc_middle/src/thir.rs
+++ b/compiler/rustc_middle/src/thir.rs
@@ -26,7 +26,7 @@ use rustc_middle::ty::{
 use rustc_span::def_id::LocalDefId;
 use rustc_span::{sym, ErrorGuaranteed, Span, Symbol, DUMMY_SP};
 use rustc_span::{sym, Span, Symbol, DUMMY_SP};
-use rustc_target::abi::{FieldIdx, OffsetOfIdx, VariantIdx};
+use rustc_target::abi::{FieldIdx, VariantIdx};
 use rustc_target::asm::InlineAsmRegOrRegClass;
 use std::fmt;
 use std::ops::Index;
@@ -491,7 +491,7 @@ pub enum ExprKind<'tcx> {
     /// Field offset (`offset_of!`)
     OffsetOf {
         container: Ty<'tcx>,
-        fields: &'tcx List<OffsetOfIdx>,
+        fields: &'tcx List<(VariantIdx, FieldIdx)>,
     },
     /// An expression taking a reference to a thread local.
     ThreadLocalRef(DefId),
diff --git a/compiler/rustc_middle/src/ty/codec.rs b/compiler/rustc_middle/src/ty/codec.rs
index ce5198ee3ba..8b67e39667b 100644
--- a/compiler/rustc_middle/src/ty/codec.rs
+++ b/compiler/rustc_middle/src/ty/codec.rs
@@ -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, OffsetOfIdx};
+use rustc_target::abi::{FieldIdx, VariantIdx};
 pub use rustc_type_ir::{TyDecoder, TyEncoder};
 use std::hash::Hash;
 use std::intrinsics;
@@ -414,12 +414,14 @@ 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<OffsetOfIdx> {
+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::<OffsetOfIdx, _>(|_| Decodable::decode(decoder)))
+        decoder.interner().mk_offset_of_from_iter(
+            (0..len).map::<(VariantIdx, FieldIdx), _>(|_| Decodable::decode(decoder)),
+        )
     }
 }
 
@@ -435,7 +437,7 @@ impl_decodable_via_ref! {
     &'tcx ty::List<ty::BoundVariableKind>,
     &'tcx ty::List<ty::Clause<'tcx>>,
     &'tcx ty::List<FieldIdx>,
-    &'tcx ty::List<OffsetOfIdx>,
+    &'tcx ty::List<(VariantIdx, FieldIdx)>,
 }
 
 #[macro_export]
diff --git a/compiler/rustc_middle/src/ty/context.rs b/compiler/rustc_middle/src/ty/context.rs
index c89ae1e661e..68812bba42f 100644
--- a/compiler/rustc_middle/src/ty/context.rs
+++ b/compiler/rustc_middle/src/ty/context.rs
@@ -63,7 +63,7 @@ use rustc_session::{Limit, MetadataKind, Session};
 use rustc_span::def_id::{DefPathHash, StableCrateId};
 use rustc_span::symbol::{kw, sym, Ident, Symbol};
 use rustc_span::{Span, DUMMY_SP};
-use rustc_target::abi::{FieldIdx, Layout, LayoutS, OffsetOfIdx, TargetDataLayout, VariantIdx};
+use rustc_target::abi::{FieldIdx, Layout, LayoutS, TargetDataLayout, VariantIdx};
 use rustc_target::spec::abi;
 use rustc_type_ir::TyKind::*;
 use rustc_type_ir::WithCachedTypeInfo;
@@ -163,7 +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<OffsetOfIdx>>,
+    offset_of: InternedSet<'tcx, List<(VariantIdx, FieldIdx)>>,
 }
 
 impl<'tcx> CtxtInterners<'tcx> {
@@ -1589,7 +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(OffsetOfIdx),
+    offset_of: pub mk_offset_of((VariantIdx, FieldIdx)),
 );
 
 impl<'tcx> TyCtxt<'tcx> {
@@ -1920,7 +1920,7 @@ impl<'tcx> TyCtxt<'tcx> {
     pub fn mk_offset_of_from_iter<I, T>(self, iter: I) -> T::Output
     where
         I: Iterator<Item = T>,
-        T: CollectAndApply<OffsetOfIdx, &'tcx List<OffsetOfIdx>>,
+        T: CollectAndApply<(VariantIdx, FieldIdx), &'tcx List<(VariantIdx, FieldIdx)>>,
     {
         T::collect_and_apply(iter, |xs| self.mk_offset_of(xs))
     }
diff --git a/compiler/rustc_middle/src/ty/typeck_results.rs b/compiler/rustc_middle/src/ty/typeck_results.rs
index 623d8d61d2d..e9240d1b268 100644
--- a/compiler/rustc_middle/src/ty/typeck_results.rs
+++ b/compiler/rustc_middle/src/ty/typeck_results.rs
@@ -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, OffsetOfIdx};
+use rustc_target::abi::{FieldIdx, VariantIdx};
 use std::{collections::hash_map::Entry, hash::Hash, iter};
 
 use super::RvalueScopes;
@@ -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<OffsetOfIdx>)>,
+    offset_of_data: ItemLocalMap<(Ty<'tcx>, Vec<(VariantIdx, FieldIdx)>)>,
 }
 
 impl<'tcx> TypeckResults<'tcx> {
@@ -464,13 +464,15 @@ impl<'tcx> TypeckResults<'tcx> {
         &self.coercion_casts
     }
 
-    pub fn offset_of_data(&self) -> LocalTableInContext<'_, (Ty<'tcx>, Vec<OffsetOfIdx>)> {
+    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<OffsetOfIdx>)> {
+    ) -> LocalTableInContextMut<'_, (Ty<'tcx>, Vec<(VariantIdx, FieldIdx)>)> {
         LocalTableInContextMut { hir_owner: self.hir_owner, data: &mut self.offset_of_data }
     }
 }
diff --git a/compiler/rustc_passes/src/dead.rs b/compiler/rustc_passes/src/dead.rs
index 3b4f6a618c8..2e8c58b0241 100644
--- a/compiler/rustc_passes/src/dead.rs
+++ b/compiler/rustc_passes/src/dead.rs
@@ -248,8 +248,6 @@ impl<'tcx> MarkSymbolVisitor<'tcx> {
     }
 
     fn handle_offset_of(&mut self, expr: &'tcx hir::Expr<'tcx>) {
-        use rustc_target::abi::OffsetOfIdx::*;
-
         let data = self.typeck_results().offset_of_data();
         let &(container, ref indices) =
             data.get(expr.hir_id).expect("no offset_of_data for offset_of");
@@ -258,22 +256,10 @@ impl<'tcx> MarkSymbolVisitor<'tcx> {
         let param_env = self.tcx.param_env(body_did);
 
         let mut current_ty = container;
-        let mut indices = indices.into_iter();
-
-        while let Some(&index) = indices.next() {
-            match (current_ty.kind(), index) {
-                (ty::Adt(def, subst), Field(field)) if !def.is_enum() => {
-                    let field = &def.non_enum_variant().fields[field];
 
-                    self.insert_def_id(field.did);
-                    let field_ty = field.ty(self.tcx, subst);
-
-                    current_ty = self.tcx.normalize_erasing_regions(param_env, field_ty);
-                }
-                (ty::Adt(def, subst), Variant(variant)) if def.is_enum() => {
-                    let Some(&Field(field)) = indices.next() else {
-                        span_bug!(expr.span, "variant must be followed by field in offset_of")
-                    };
+        for &(variant, field) in indices {
+            match current_ty.kind() {
+                ty::Adt(def, subst) => {
                     let field = &def.variant(variant).fields[field];
 
                     self.insert_def_id(field.did);
@@ -283,12 +269,11 @@ impl<'tcx> MarkSymbolVisitor<'tcx> {
                 }
                 // we don't need to mark tuple fields as live,
                 // but we may need to mark subfields
-                (ty::Tuple(tys), Field(field)) => {
+                ty::Tuple(tys) => {
                     current_ty =
                         self.tcx.normalize_erasing_regions(param_env, tys[field.as_usize()]);
                 }
-                (_, Field(_)) => span_bug!(expr.span, "named field access on non-ADT"),
-                (_, Variant(_)) => span_bug!(expr.span, "enum variant access on non-enum"),
+                _ => span_bug!(expr.span, "named field access on non-ADT"),
             }
         }
     }
diff --git a/compiler/rustc_smir/src/rustc_smir/mod.rs b/compiler/rustc_smir/src/rustc_smir/mod.rs
index e64afa5f8d9..2abad677bf0 100644
--- a/compiler/rustc_smir/src/rustc_smir/mod.rs
+++ b/compiler/rustc_smir/src/rustc_smir/mod.rs
@@ -16,7 +16,7 @@ use rustc_middle::mir::interpret::{alloc_range, AllocId};
 use rustc_middle::mir::mono::MonoItem;
 use rustc_middle::ty::{self, Instance, ParamEnv, Ty, TyCtxt, Variance};
 use rustc_span::def_id::{CrateNum, DefId, LOCAL_CRATE};
-use rustc_target::abi::{FieldIdx, OffsetOfIdx};
+use rustc_target::abi::FieldIdx;
 use stable_mir::mir::mono::InstanceDef;
 use stable_mir::mir::{Body, CopyNonOverlapping, Statement, UserTypeProjection, VariantIdx};
 use stable_mir::ty::{
@@ -643,13 +643,10 @@ impl<'tcx> Stable<'tcx> for FieldIdx {
     }
 }
 
-impl<'tcx> Stable<'tcx> for OffsetOfIdx {
-    type T = usize;
+impl<'tcx> Stable<'tcx> for (rustc_target::abi::VariantIdx, FieldIdx) {
+    type T = (usize, usize);
     fn stable(&self, _: &mut Tables<'tcx>) -> Self::T {
-        match self {
-            OffsetOfIdx::Field(f) => f.as_usize(),
-            OffsetOfIdx::Variant(v) => v.as_usize(),
-        }
+        (self.0.as_usize(), self.1.as_usize())
     }
 }
 
diff --git a/compiler/rustc_target/src/abi/mod.rs b/compiler/rustc_target/src/abi/mod.rs
index a37a5bb2689..b00567e87c6 100644
--- a/compiler/rustc_target/src/abi/mod.rs
+++ b/compiler/rustc_target/src/abi/mod.rs
@@ -250,29 +250,24 @@ impl<'a, Ty> TyAndLayout<'a, Ty> {
         Ty::is_transparent(self)
     }
 
-    pub fn offset_of_subfield<C>(self, cx: &C, indices: impl Iterator<Item = OffsetOfIdx>) -> Size
+    pub fn offset_of_subfield<C, I>(self, cx: &C, indices: I) -> Size
     where
         Ty: TyAbiInterface<'a, C>,
+        I: Iterator<Item = (VariantIdx, FieldIdx)>,
     {
         let mut layout = self;
         let mut offset = Size::ZERO;
 
-        for index in indices {
-            match index {
-                OffsetOfIdx::Field(field) => {
-                    let index = field.index();
-                    offset += layout.fields.offset(index);
-                    layout = layout.field(cx, index);
-                    assert!(
-                        layout.is_sized(),
-                        "offset of unsized field (type {:?}) cannot be computed statically",
-                        layout.ty
-                    );
-                }
-                OffsetOfIdx::Variant(variant) => {
-                    layout = layout.for_variant(cx, variant);
-                }
-            }
+        for (variant, field) in indices {
+            layout = layout.for_variant(cx, variant);
+            let index = field.index();
+            offset += layout.fields.offset(index);
+            layout = layout.field(cx, index);
+            assert!(
+                layout.is_sized(),
+                "offset of unsized field (type {:?}) cannot be computed statically",
+                layout.ty
+            );
         }
 
         offset
diff --git a/compiler/stable_mir/src/mir/body.rs b/compiler/stable_mir/src/mir/body.rs
index 9f69e61d6fe..9dc746f5c4d 100644
--- a/compiler/stable_mir/src/mir/body.rs
+++ b/compiler/stable_mir/src/mir/body.rs
@@ -514,7 +514,7 @@ pub enum NullOp {
     /// Returns the minimum alignment of a type.
     AlignOf,
     /// Returns the offset of a field.
-    OffsetOf(Vec<FieldIdx>),
+    OffsetOf(Vec<(VariantIdx, FieldIdx)>),
 }
 
 impl Operand {
diff --git a/library/core/src/mem/mod.rs b/library/core/src/mem/mod.rs
index df79c3a338a..862619f31f2 100644
--- a/library/core/src/mem/mod.rs
+++ b/library/core/src/mem/mod.rs
@@ -1292,11 +1292,15 @@ impl<T> SizedTypeProperties for T {}
 
 /// Expands to the offset in bytes of a field from the beginning of the given type.
 ///
-/// Only structs, unions and tuples are supported.
+/// Structs, enums, unions and tuples are supported.
 ///
 /// Nested field accesses may be used, but not array indexes like in `C`'s `offsetof`.
 ///
-/// Note that the output of this macro is not stable, except for `#[repr(C)]` types.
+/// Enum variants may be traversed as if they were fields. Variants themselves do
+/// not have an offset.
+///
+/// Note that type layout is, in general, [platform-specific, and subject to
+/// change](https://doc.rust-lang.org/reference/type-layout.html).
 ///
 /// # Examples
 ///
@@ -1324,6 +1328,8 @@ impl<T> SizedTypeProperties for T {}
 /// struct NestedB(u8);
 ///
 /// assert_eq!(mem::offset_of!(NestedA, b.0), 0);
+///
+/// assert_eq!(mem::offset_of!(Option<&u8>, Some.0), 0);
 /// ```
 #[unstable(feature = "offset_of", issue = "106655")]
 #[allow_internal_unstable(builtin_syntax, hint_must_use)]
diff --git a/tests/ui/offset-of/offset-of-enum.rs b/tests/ui/offset-of/offset-of-enum.rs
index 92d34e67c71..cf516175085 100644
--- a/tests/ui/offset-of/offset-of-enum.rs
+++ b/tests/ui/offset-of/offset-of-enum.rs
@@ -11,4 +11,7 @@ fn main() {
     offset_of!(Alpha::One, 0); //~ ERROR expected type, found variant `Alpha::One`
     offset_of!(Alpha, One); //~ ERROR `One` is an enum variant; expected field at end of `offset_of`
     offset_of!(Alpha, Two.0);
+    offset_of!(Alpha, Two.1); //~ ERROR no field named `1` on enum variant `Alpha::Two`
+    offset_of!(Alpha, Two.foo); //~ ERROR no field named `foo` on enum variant `Alpha::Two`
+    offset_of!(Alpha, NonExistent); //~ ERROR no variant named `NonExistent` found for enum `Alpha`
 }
diff --git a/tests/ui/offset-of/offset-of-enum.stderr b/tests/ui/offset-of/offset-of-enum.stderr
index 1d849e682e6..2867cc6befb 100644
--- a/tests/ui/offset-of/offset-of-enum.stderr
+++ b/tests/ui/offset-of/offset-of-enum.stderr
@@ -13,7 +13,29 @@ error[E0795]: `One` is an enum variant; expected field at end of `offset_of`
 LL |     offset_of!(Alpha, One);
    |                       ^^^ enum variant
 
-error: aborting due to 2 previous errors
+error[E0609]: no field named `1` on enum variant `Alpha::Two`
+  --> $DIR/offset-of-enum.rs:14:23
+   |
+LL |     offset_of!(Alpha, Two.1);
+   |                       ^^^ - ...does not have this field
+   |                       |
+   |                       this enum variant...
+
+error[E0609]: no field named `foo` on enum variant `Alpha::Two`
+  --> $DIR/offset-of-enum.rs:15:23
+   |
+LL |     offset_of!(Alpha, Two.foo);
+   |                       ^^^ --- ...does not have this field
+   |                       |
+   |                       this enum variant...
+
+error[E0599]: no variant named `NonExistent` found for enum `Alpha`
+  --> $DIR/offset-of-enum.rs:16:23
+   |
+LL |     offset_of!(Alpha, NonExistent);
+   |                       ^^^^^^^^^^^ variant not found
+
+error: aborting due to 5 previous errors
 
-Some errors have detailed explanations: E0573, E0795.
+Some errors have detailed explanations: E0573, E0599, E0609, E0795.
 For more information about an error, try `rustc --explain E0573`.