From 144a12acdd673f333b420e946965621be76d3544 Mon Sep 17 00:00:00 2001 From: Zalathar Date: Fri, 25 Oct 2024 18:22:03 +1100 Subject: [PATCH] Add a macro that derives `TryFrom` for fieldless enums --- compiler/rustc_macros/src/lib.rs | 10 ++++ compiler/rustc_macros/src/try_from.rs | 53 ++++++++++++++++++++ tests/ui-fulldeps/try-from-u32/errors.rs | 24 +++++++++ tests/ui-fulldeps/try-from-u32/errors.stderr | 32 ++++++++++++ tests/ui-fulldeps/try-from-u32/hygiene.rs | 32 ++++++++++++ tests/ui-fulldeps/try-from-u32/values.rs | 36 +++++++++++++ 6 files changed, 187 insertions(+) create mode 100644 compiler/rustc_macros/src/try_from.rs create mode 100644 tests/ui-fulldeps/try-from-u32/errors.rs create mode 100644 tests/ui-fulldeps/try-from-u32/errors.stderr create mode 100644 tests/ui-fulldeps/try-from-u32/hygiene.rs create mode 100644 tests/ui-fulldeps/try-from-u32/values.rs diff --git a/compiler/rustc_macros/src/lib.rs b/compiler/rustc_macros/src/lib.rs index f46c795b956..0df674eb4c9 100644 --- a/compiler/rustc_macros/src/lib.rs +++ b/compiler/rustc_macros/src/lib.rs @@ -20,6 +20,7 @@ mod query; mod serialize; mod symbols; +mod try_from; mod type_foldable; mod type_visitable; @@ -165,3 +166,12 @@ pub fn extension(attr: TokenStream, input: TokenStream) -> TokenStream { suggestion_part, applicability)] => diagnostics::subdiagnostic_derive ); + +decl_derive! { + [TryFromU32] => + /// Derives `TryFrom` for the annotated `enum`, which must have no fields. + /// Each variant maps to the value it would produce under an `as u32` cast. + /// + /// The error type is `u32`. + try_from::try_from_u32 +} diff --git a/compiler/rustc_macros/src/try_from.rs b/compiler/rustc_macros/src/try_from.rs new file mode 100644 index 00000000000..fde7c8f6204 --- /dev/null +++ b/compiler/rustc_macros/src/try_from.rs @@ -0,0 +1,53 @@ +use proc_macro2::TokenStream; +use quote::{quote, quote_spanned}; +use syn::Data; +use syn::spanned::Spanned; +use synstructure::Structure; + +pub(crate) fn try_from_u32(s: Structure<'_>) -> TokenStream { + let span_error = |span, message: &str| { + quote_spanned! { span => const _: () = ::core::compile_error!(#message); } + }; + + // Must be applied to an enum type. + if let Some(span) = match &s.ast().data { + Data::Enum(_) => None, + Data::Struct(s) => Some(s.struct_token.span()), + Data::Union(u) => Some(u.union_token.span()), + } { + return span_error(span, "type is not an enum (TryFromU32)"); + } + + // The enum's variants must not have fields. + let variant_field_errors = s + .variants() + .iter() + .filter_map(|v| v.ast().fields.iter().map(|f| f.span()).next()) + .map(|span| span_error(span, "enum variant cannot have fields (TryFromU32)")) + .collect::(); + if !variant_field_errors.is_empty() { + return variant_field_errors; + } + + let ctor = s + .variants() + .iter() + .map(|v| v.construct(|_, _| -> TokenStream { unreachable!() })) + .collect::>(); + s.gen_impl(quote! { + // The surrounding code might have shadowed these identifiers. + use ::core::convert::TryFrom; + use ::core::primitive::u32; + use ::core::result::Result::{self, Ok, Err}; + + gen impl TryFrom for @Self { + type Error = u32; + + #[allow(deprecated)] // Don't warn about deprecated variants. + fn try_from(value: u32) -> Result { + #( if value == const { #ctor as u32 } { return Ok(#ctor) } )* + Err(value) + } + } + }) +} diff --git a/tests/ui-fulldeps/try-from-u32/errors.rs b/tests/ui-fulldeps/try-from-u32/errors.rs new file mode 100644 index 00000000000..0470063312c --- /dev/null +++ b/tests/ui-fulldeps/try-from-u32/errors.rs @@ -0,0 +1,24 @@ +#![feature(rustc_private)] +//@ edition: 2021 + +// Checks the error messages produced by `#[derive(TryFromU32)]`. + +extern crate rustc_macros; + +use rustc_macros::TryFromU32; + +#[derive(TryFromU32)] +struct MyStruct {} //~ type is not an enum + +#[derive(TryFromU32)] +enum NonTrivial { + A, + B(), + C {}, + D(bool), //~ enum variant cannot have fields + E(bool, bool), //~ enum variant cannot have fields + F { x: bool }, //~ enum variant cannot have fields + G { x: bool, y: bool }, //~ enum variant cannot have fields +} + +fn main() {} diff --git a/tests/ui-fulldeps/try-from-u32/errors.stderr b/tests/ui-fulldeps/try-from-u32/errors.stderr new file mode 100644 index 00000000000..d20567061d7 --- /dev/null +++ b/tests/ui-fulldeps/try-from-u32/errors.stderr @@ -0,0 +1,32 @@ +error: type is not an enum (TryFromU32) + --> $DIR/errors.rs:11:1 + | +LL | struct MyStruct {} + | ^^^^^^ + +error: enum variant cannot have fields (TryFromU32) + --> $DIR/errors.rs:18:7 + | +LL | D(bool), + | ^^^^ + +error: enum variant cannot have fields (TryFromU32) + --> $DIR/errors.rs:19:7 + | +LL | E(bool, bool), + | ^^^^ + +error: enum variant cannot have fields (TryFromU32) + --> $DIR/errors.rs:20:9 + | +LL | F { x: bool }, + | ^ + +error: enum variant cannot have fields (TryFromU32) + --> $DIR/errors.rs:21:9 + | +LL | G { x: bool, y: bool }, + | ^ + +error: aborting due to 5 previous errors + diff --git a/tests/ui-fulldeps/try-from-u32/hygiene.rs b/tests/ui-fulldeps/try-from-u32/hygiene.rs new file mode 100644 index 00000000000..e0655a64a64 --- /dev/null +++ b/tests/ui-fulldeps/try-from-u32/hygiene.rs @@ -0,0 +1,32 @@ +#![feature(rustc_private)] +//@ edition: 2021 +//@ check-pass + +// Checks that the derive macro still works even if the surrounding code has +// shadowed the relevant library types. + +extern crate rustc_macros; + +mod submod { + use rustc_macros::TryFromU32; + + struct Result; + trait TryFrom {} + #[allow(non_camel_case_types)] + struct u32; + struct Ok; + struct Err; + mod core {} + mod std {} + + #[derive(TryFromU32)] + pub(crate) enum MyEnum { + Zero, + One, + } +} + +fn main() { + use submod::MyEnum; + let _: Result = MyEnum::try_from(1u32); +} diff --git a/tests/ui-fulldeps/try-from-u32/values.rs b/tests/ui-fulldeps/try-from-u32/values.rs new file mode 100644 index 00000000000..180a8f2beb7 --- /dev/null +++ b/tests/ui-fulldeps/try-from-u32/values.rs @@ -0,0 +1,36 @@ +#![feature(assert_matches)] +#![feature(rustc_private)] +//@ edition: 2021 +//@ run-pass + +// Checks the values accepted by the `TryFrom` impl produced by `#[derive(TryFromU32)]`. + +extern crate rustc_macros; + +use core::assert_matches::assert_matches; +use rustc_macros::TryFromU32; + +#[derive(TryFromU32, Debug, PartialEq)] +#[repr(u32)] +enum Repr { + Zero, + One(), + Seven = 7, +} + +#[derive(TryFromU32, Debug)] +enum NoRepr { + Zero, + One, +} + +fn main() { + assert_eq!(Repr::try_from(0u32), Ok(Repr::Zero)); + assert_eq!(Repr::try_from(1u32), Ok(Repr::One())); + assert_eq!(Repr::try_from(2u32), Err(2)); + assert_eq!(Repr::try_from(7u32), Ok(Repr::Seven)); + + assert_matches!(NoRepr::try_from(0u32), Ok(NoRepr::Zero)); + assert_matches!(NoRepr::try_from(1u32), Ok(NoRepr::One)); + assert_matches!(NoRepr::try_from(2u32), Err(2)); +}