diff --git a/compiler/rustc_lint_defs/src/builtin.rs b/compiler/rustc_lint_defs/src/builtin.rs index 27a06943cbc25..7ac07594cb34b 100644 --- a/compiler/rustc_lint_defs/src/builtin.rs +++ b/compiler/rustc_lint_defs/src/builtin.rs @@ -3067,6 +3067,7 @@ declare_lint_pass! { DEREF_INTO_DYN_SUPERTRAIT, DEPRECATED_CFG_ATTR_CRATE_TYPE_NAME, DUPLICATE_MACRO_ATTRIBUTES, + NOT_CENUM_CAST, ] } @@ -3633,3 +3634,43 @@ declare_lint! { Warn, "duplicated attribute" } + +declare_lint! { + /// The `not_cenum_cast` lint detects an `as` cast of a field-less + /// `enum` that is not C-like. + /// + /// ### Example + /// + /// ```rust + /// # #![allow(unused)] + /// enum E { + /// A(), + /// B{}, + /// C, + /// } + /// + /// fn main() { + /// let i = E::A() as u32; + /// } + /// ``` + /// + /// {{produces}} + /// + /// ### Explanation + /// + /// Historically we permit casting of enums if none of the variants have + /// fields. The intended behaviour is that only C-like (all variants are + /// unit variants) enums can be casted this way. + /// + /// This is a [future-incompatible] lint to transition this to a hard error + /// in the future. See [issue #88621] for more details. + /// + /// [future-incompatible]: ../index.md#future-incompatible-lints + /// [issue #88621]: https://github.com/rust-lang/rust/issues/88621 + pub NOT_CENUM_CAST, + Warn, + "an enum that is not C-like is cast", + @future_incompatible = FutureIncompatibleInfo { + reference: "issue #88611 ", + }; +} diff --git a/compiler/rustc_middle/src/ty/adt.rs b/compiler/rustc_middle/src/ty/adt.rs index 5cde54c9328d1..c9a0d462fb50b 100644 --- a/compiler/rustc_middle/src/ty/adt.rs +++ b/compiler/rustc_middle/src/ty/adt.rs @@ -354,6 +354,20 @@ impl<'tcx> AdtDef { self.variants.iter().all(|v| v.fields.is_empty()) } + /// Whether all variants have only constant constructors + /// (i.e. there are no tuple or struct variants). + /// This is distinct from `is_payloadfree` specifically for the case of + /// empty tuple constructors, e.g. for: + /// ``` + /// enum Number { + /// Zero(), + /// } + /// ``` + /// this function returns false, where `is_payloadfree` returns true. + pub fn is_c_like_enum(&self) -> bool { + self.is_enum() && self.variants.iter().all(|v| v.ctor_kind == CtorKind::Const) + } + /// Return a `VariantDef` given a variant id. pub fn variant_with_id(&self, vid: DefId) -> &VariantDef { self.variants.iter().find(|v| v.def_id == vid).expect("variant_with_id: unknown variant") diff --git a/compiler/rustc_typeck/src/check/cast.rs b/compiler/rustc_typeck/src/check/cast.rs index a397ee771af59..bcab1067698ea 100644 --- a/compiler/rustc_typeck/src/check/cast.rs +++ b/compiler/rustc_typeck/src/check/cast.rs @@ -767,6 +767,7 @@ impl<'a, 'tcx> CastCheck<'tcx> { // prim -> prim (Int(CEnum), Int(_)) => { self.cenum_impl_drop_lint(fcx); + self.not_cenum_lint(fcx); Ok(CastKind::EnumCast) } (Int(Char) | Int(Bool), Int(_)) => Ok(CastKind::PrimIntCast), @@ -919,6 +920,25 @@ impl<'a, 'tcx> CastCheck<'tcx> { } } } + + fn not_cenum_lint(&self, fcx: &FnCtxt<'a, 'tcx>) { + if let ty::Adt(d, _) = self.expr_ty.kind() { + if !d.is_c_like_enum() { + fcx.tcx.struct_span_lint_hir( + lint::builtin::NOT_CENUM_CAST, + self.expr.hir_id, + self.span, + |err| { + err.build(&format!( + "cannot cast enum `{}` into integer `{}` because it is not C-like", + self.expr_ty, self.cast_ty + )) + .emit(); + }, + ); + } + } + } } impl<'a, 'tcx> FnCtxt<'a, 'tcx> { diff --git a/src/test/ui/cast/not_cenum_cast.rs b/src/test/ui/cast/not_cenum_cast.rs new file mode 100644 index 0000000000000..adc7b01e2be27 --- /dev/null +++ b/src/test/ui/cast/not_cenum_cast.rs @@ -0,0 +1,13 @@ +#![deny(not_cenum_cast)] + +enum E { + A(), + B{}, + C, +} + +fn main() { + let i = E::A() as u32; + //~^ ERROR cannot cast enum `E` into integer `u32` because it is not C-like + //~| WARN this was previously accepted +} diff --git a/src/test/ui/cast/not_cenum_cast.stderr b/src/test/ui/cast/not_cenum_cast.stderr new file mode 100644 index 0000000000000..0a9a1b826eccf --- /dev/null +++ b/src/test/ui/cast/not_cenum_cast.stderr @@ -0,0 +1,16 @@ +error: cannot cast enum `E` into integer `u32` because it is not C-like + --> $DIR/not_cenum_cast.rs:10:13 + | +LL | let i = E::A() as u32; + | ^^^^^^^^^^^^^ + | +note: the lint level is defined here + --> $DIR/not_cenum_cast.rs:1:9 + | +LL | #![deny(not_cenum_cast)] + | ^^^^^^^^^^^^^^ + = warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release! + = note: for more information, see issue #88611 + +error: aborting due to previous error +