diff --git a/CHANGELOG.md b/CHANGELOG.md index 023e65c..1e7b4ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,8 @@ Note: In this file, do not use the hard wrap in the middle of a sentence for com ## [Unreleased] +- Add `Into` derive implementation ([#164](https://github.com/taiki-e/auto_enums/pull/164)). + ## [0.8.7] - 2025-01-16 - Update `derive_utils` to 0.15. This uses `#[automatically_derived]` on generated impls to improve coverage support. diff --git a/Cargo.toml b/Cargo.toml index 8a25ee5..21974b4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,7 +37,7 @@ transpose_methods = [] std = [] # Enable to use `[std|core]::ops`'s `Deref`, `DerefMut`, `Index`, `IndexMut`, and `RangeBounds` traits. ops = [] -# Enable to use `[std|core]::convert`'s `AsRef` and `AsMut` traits. +# Enable to use `[std|core]::convert`'s `AsRef`, `AsMut`, `Into` traits. convert = [] # Enable to use `[std|core]::fmt`'s traits other than `Debug`, `Display` and `Write` fmt = [] diff --git a/src/auto_enum/type_analysis.rs b/src/auto_enum/type_analysis.rs index 7948b02..8aedede 100644 --- a/src/auto_enum/type_analysis.rs +++ b/src/auto_enum/type_analysis.rs @@ -20,12 +20,16 @@ pub(super) fn collect_impl_trait(args: &[Path], traits: &mut Vec, ty: &mut visit_mut::visit_type_impl_trait_mut(self, node); for ty in &node.bounds { - if let TypeParamBound::Trait(ty) = ty { - let ty = path(ty.path.segments.iter().map(|ty| ty.ident.clone().into())); + if let TypeParamBound::Trait(orig_ty) = ty { + let ty = path(orig_ty.path.segments.iter().map(|ty| ty.ident.clone().into())); let ty_str = ty.to_token_stream().to_string(); - let ty_trimmed = ty_str.replace(' ', ""); - if TRAITS.contains(&&*ty_trimmed) - && !self.args.iter().any(|x| x.to_token_stream().to_string() == ty_str) + let orig_ty_str = orig_ty.to_token_stream().to_string(); + if TRAITS.contains(&&*ty_str) + && !self.args.iter().any(|x| { + let arg_str = x.to_token_stream().to_string(); + // Some derives (ie: Into) need to check against the derive argument itself + arg_str == ty_str || arg_str == orig_ty_str + }) { self.has_impl_trait = true; self.traits.push(ty); @@ -51,6 +55,7 @@ const TRAITS: &[&str] = &[ // core "AsRef", "AsMut", + "Into", "Debug", "fmt::Debug", "Display", diff --git a/src/derive/core/convert.rs b/src/derive/core/convert.rs index e65d169..969c653 100644 --- a/src/derive/core/convert.rs +++ b/src/derive/core/convert.rs @@ -29,3 +29,46 @@ pub(crate) mod as_mut { })) } } + +pub(crate) mod into { + use syn::{Error, PathArguments, spanned::Spanned as _}; + + use crate::derive::prelude::*; + + pub(crate) const NAME: &[&str] = &["Into"]; + + pub(crate) fn derive(cx: &Context, data: &Data) -> Result { + let path = cx.trait_path().unwrap_or_else(|| unreachable!()); + let trait_name = path.segments.last().unwrap_or_else(|| unreachable!()); + let PathArguments::AngleBracketed(ref into_type_generics) = trait_name.arguments else { + return Err(Error::new( + path.span(), + "Into trait requires a generic argument, eg: Into.", + )); + }; + + if into_type_generics.args.len() != 1 { + return Err(Error::new( + into_type_generics.span(), + "Into trait must take one argument.", + )); + } + + let target = into_type_generics.args.first().unwrap().clone(); + let path = path.clone(); + + let mut enum_impl = derive_utils::EnumImpl::new(data); + enum_impl.set_trait(path.clone()); + for v in &data.variants { + let variant_name = &v.ident; + enum_impl.push_where_predicate(parse_quote! { + #variant_name: #path + }); + } + enum_impl.push_method(parse_quote! { + #[inline] + fn into(self) -> #target; + }); + Ok(enum_impl.build()) + } +} diff --git a/src/enum_derive.rs b/src/enum_derive.rs index dd85f65..9bd4987 100644 --- a/src/enum_derive.rs +++ b/src/enum_derive.rs @@ -18,12 +18,21 @@ pub(crate) fn attribute(args: TokenStream, input: TokenStream) -> TokenStream { #[derive(Default)] pub(crate) struct DeriveContext { needs_pin_projection: Cell, + trait_path: Option, } impl DeriveContext { pub(crate) fn needs_pin_projection(&self) { self.needs_pin_projection.set(true); } + pub(crate) fn set_trait_path(&mut self, trait_path: Option) { + self.trait_path = trait_path; + } + + #[allow(dead_code)] // only used for `Into` derive + pub(crate) fn trait_path(&self) -> Option<&Path> { + self.trait_path.as_ref() + } } type DeriveFn = fn(&'_ DeriveContext, &'_ Data) -> Result; @@ -46,6 +55,8 @@ fn get_derive(s: &str) -> Option { core::convert::as_mut, #[cfg(feature = "convert")] core::convert::as_ref, + #[cfg(feature = "convert")] + core::convert::into, core::fmt::debug, core::fmt::display, #[cfg(feature = "fmt")] @@ -180,7 +191,7 @@ struct Args { impl Parse for Args { fn parse(input: ParseStream<'_>) -> Result { fn to_trimmed_string(p: &Path) -> String { - p.to_token_stream().to_string().replace(' ', "") + p.to_token_stream().to_string().replace(' ', "").split('<').next().unwrap().to_owned() } let mut inner = vec![]; @@ -279,10 +290,12 @@ fn expand(args: TokenStream, input: TokenStream) -> Result { let mut derive = vec![]; let mut items = TokenStream::new(); - let cx = DeriveContext::default(); + let mut cx = DeriveContext::default(); for (s, arg) in args { + // TODO: add error message "`...` derive is only supported via `...` feature." match (get_derive(s), arg) { (Some(f), _) => { + cx.set_trait_path(arg.cloned()); items.extend( f(&cx, &data).map_err(|e| format_err!(data, "`enum_derive({})` {}", s, e))?, ); diff --git a/tests/auto_enum.rs b/tests/auto_enum.rs index 3807084..bfe11fc 100644 --- a/tests/auto_enum.rs +++ b/tests/auto_enum.rs @@ -490,6 +490,25 @@ fn marker() { assert_eq!(marker6(10).sum::(), 3); } +#[cfg(feature = "convert")] +#[test] +fn into() { + #[auto_enum(Into)] + fn number(x: u8) -> impl Into { + match x { + 0 => 1_u8, + 1 => 1_u16, + 2 => 1_u32, + 3 => 1_i8, + 4 => 1_i16, + 5 => 1_i32, + 6 => 1_i64, + _ => true, + } + } + assert_eq!(number(2).into(), 1_i64); +} + #[cfg(feature = "transpose_methods")] #[cfg(feature = "std")] #[test] diff --git a/tests/ui/enum_derive/args.rs b/tests/ui/enum_derive/args.rs index 6b9e17c..ce62127 100644 --- a/tests/ui/enum_derive/args.rs +++ b/tests/ui/enum_derive/args.rs @@ -26,4 +26,15 @@ enum Enum4 { B(B), } +#[enum_derive(Into)] //~ ERROR missing Into generic argument +enum Enum5 { + A(A), + B(B), +} +#[enum_derive(Into)] //~ ERROR too many Into generic arguments +enum Enum6 { + A(A), + B(B), +} + fn main() {} diff --git a/tests/ui/enum_derive/args.stderr b/tests/ui/enum_derive/args.stderr index fc5edd0..9e7440c 100644 --- a/tests/ui/enum_derive/args.stderr +++ b/tests/ui/enum_derive/args.stderr @@ -21,3 +21,21 @@ error: expected `,` | 23 | #[enum_derive(Clone Foo)] //~ ERROR expected `,` | ^^^ + +error: `enum_derive(Into)` Into trait requires a generic argument, eg: Into. + --> tests/ui/enum_derive/args.rs:30:1 + | +30 | / enum Enum5 { +31 | | A(A), +32 | | B(B), +33 | | } + | |_^ + +error: `enum_derive(Into)` Into trait must take one argument. + --> tests/ui/enum_derive/args.rs:35:1 + | +35 | / enum Enum6 { +36 | | A(A), +37 | | B(B), +38 | | } + | |_^