Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<T>` 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.
Expand Down
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 = []
Expand Down
15 changes: 10 additions & 5 deletions src/auto_enum/type_analysis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,16 @@ pub(super) fn collect_impl_trait(args: &[Path], traits: &mut Vec<Path>, 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);
Expand All @@ -51,6 +55,7 @@ const TRAITS: &[&str] = &[
// core
"AsRef",
"AsMut",
"Into",
"Debug",
"fmt::Debug",
"Display",
Expand Down
43 changes: 43 additions & 0 deletions src/derive/core/convert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<TokenStream> {
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<TargetType>.",
));
};

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())
}
}
17 changes: 15 additions & 2 deletions src/enum_derive.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,21 @@ pub(crate) fn attribute(args: TokenStream, input: TokenStream) -> TokenStream {
#[derive(Default)]
pub(crate) struct DeriveContext {
needs_pin_projection: Cell<bool>,
trait_path: Option<Path>,
}

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<Path>) {
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<TokenStream>;
Expand All @@ -46,6 +55,8 @@ fn get_derive(s: &str) -> Option<DeriveFn> {
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")]
Expand Down Expand Up @@ -180,7 +191,7 @@ struct Args {
impl Parse for Args {
fn parse(input: ParseStream<'_>) -> Result<Self> {
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![];
Expand Down Expand Up @@ -279,10 +290,12 @@ fn expand(args: TokenStream, input: TokenStream) -> Result<TokenStream> {

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))?,
);
Expand Down
19 changes: 19 additions & 0 deletions tests/auto_enum.rs
Original file line number Diff line number Diff line change
Expand Up @@ -490,6 +490,25 @@ fn marker() {
assert_eq!(marker6(10).sum::<i32>(), 3);
}

#[cfg(feature = "convert")]
#[test]
fn into() {
#[auto_enum(Into<i64>)]
fn number(x: u8) -> impl Into<i64> {
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]
Expand Down
11 changes: 11 additions & 0 deletions tests/ui/enum_derive/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,15 @@ enum Enum4<A, B> {
B(B),
}

#[enum_derive(Into)] //~ ERROR missing Into generic argument
enum Enum5<A, B> {
A(A),
B(B),
}
#[enum_derive(Into<i32, f64>)] //~ ERROR too many Into generic arguments
enum Enum6<A, B> {
A(A),
B(B),
}

fn main() {}
18 changes: 18 additions & 0 deletions tests/ui/enum_derive/args.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -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<TargetType>.
--> tests/ui/enum_derive/args.rs:30:1
|
30 | / enum Enum5<A, B> {
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<A, B> {
36 | | A(A),
37 | | B(B),
38 | | }
| |_^