diff --git a/CHANGELOG.md b/CHANGELOG.md index 0332d278..10ca0695 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,8 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - The `From` derive doesn't derive `From<()>` for enum variants without any fields anymore. This feature was removed because it was considered useless in practice. +- The `From` derive now uses `#[from()]` instead of `#[from(types())]` + and ignores field type itself. ### Added diff --git a/Cargo.toml b/Cargo.toml index 7ad2a175..deede6e6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,6 +33,7 @@ rustc_version = { version = "0.4", optional = true } [dev-dependencies] rustversion = "1.0" +static_assertions = "1.1" trybuild = "1.0.56" [badges] @@ -229,7 +230,7 @@ required-features = ["try_unwrap"] [[test]] name = "compile_fail" path = "tests/compile_fail/mod.rs" -required-features = ["debug", "display"] +required-features = ["debug", "display", "from"] [[test]] name = "no_std" diff --git a/impl/doc/from.md b/impl/doc/from.md index d0ab8f6d..1bc0cb4d 100644 --- a/impl/doc/from.md +++ b/impl/doc/from.md @@ -2,263 +2,187 @@ The point of deriving this type is that it makes it easy to create a new instance of the type by using the `.into()` method on the value(s) that it -should contain. -This is done by implementing the `From` trait for the type that is passed to the -derive. -For structs with a single field you can call `.into()` on the desired content -itself after deriving `From`. -For structs that have multiple fields `.into()` needs to be called on a tuple -containing the desired content for each field. -For enums `.into()` works for each variant as if they were structs. -This way the variant can not only be initialized, but also be chosen based on -the type that `.into()` is called on. +should contain. This is done by implementing the `From` trait for the type +that is passed to the derive. -## Example usage +## Structs + +For structs with a single field you can call `.into()` on the desired content +itself after deriving `From`. ```rust # use derive_more::From; # -// Allow converting from i32 -#[derive(From, PartialEq)] -struct MyInt(i32); +#[derive(Debug, From, PartialEq)] +struct Int(i32); -// Forward from call to the field, so allow converting -// from anything that can be converted into an i64 (so most integers) -#[derive(From, PartialEq)] -#[from(forward)] -struct MyInt64(i64); - -// You can ignore a variant -#[derive(From, PartialEq)] -enum MyEnum { - SmallInt(i32), - NamedBigInt { int: i64 }, - #[from(ignore)] - NoFromImpl(i64), -} - -// Or explicitly annotate the ones you need -#[derive(From, PartialEq)] -enum MyEnum2 { - #[from] - SmallInt(i32), - #[from] - NamedBigInt { int: i64 }, - NoFromImpl(i64), -} - -// And even specify additional conversions for them -#[derive(From, PartialEq)] -enum MyEnum3 { - #[from(types(i8))] - SmallInt(i32), - #[from(types(i16))] - NamedBigInt { int: i64 }, - NoFromImpl(i64), -} - -assert!(MyInt(2) == 2.into()); -assert!(MyInt64(6) == 6u8.into()); -assert!(MyEnum::SmallInt(123) == 123i32.into()); -assert!(MyEnum::SmallInt(123) != 123i64.into()); -assert!(MyEnum::NamedBigInt{int: 123} == 123i64.into()); -assert!(MyEnum3::SmallInt(123) == 123i8.into()); -assert!(MyEnum3::NamedBigInt{int: 123} == 123i16.into()); +assert_eq!(Int(2), 2.into()); ``` +For structs that have multiple fields `.into()` needs to be called on a tuple +containing the desired content for each field. +```rust +# use derive_more::From; +# +#[derive(Debug, From, PartialEq)] +struct Point(i32, i32); +assert_eq!(Point(1, 2), (1, 2).into()); +``` -## Tuple structs - -When deriving for a tuple struct with a single field (i.e. a newtype) like this: +To specify concrete types to derive convert from use `#[from()]`. ```rust +# use std::borrow::Cow; +# # use derive_more::From; # -#[derive(From)] -struct MyInt(i32); -``` +#[derive(Debug, From, PartialEq)] +#[from(Cow<'static, str>, String, &'static str)] +struct Str(Cow<'static, str>); -Code like this will be generated: +assert_eq!(Str("&str".into()), "&str".into()); +assert_eq!(Str("String".into()), "String".to_owned().into()); +assert_eq!(Str("Cow".into()), Cow::Borrowed("Cow").to_owned().into()); -```rust -# struct MyInt(i32); -impl ::core::convert::From<(i32)> for MyInt { - fn from(original: (i32)) -> MyInt { - MyInt(original) - } +#[derive(Debug, From, PartialEq)] +#[from((i16, i16), (i32, i32))] +struct Point { + x: i32, + y: i32, } + +assert_eq!(Point { x: 1_i32, y: 2_i32 }, (1_i16, 2_i16).into()); +assert_eq!(Point { x: 3_i32, y: 4_i32 }, (3_i32, 4_i32).into()); ``` -The behaviour is a bit different when deriving for a struct with multiple -fields. For instance when deriving for a tuple struct with two fields like this: +Also, you can forward implementation to the inner type, which means deriving `From` for any type, that derives `From` +inner type. ```rust +# use std::borrow::Cow; +# # use derive_more::From; # -#[derive(From)] -struct MyInts(i32, i32); -``` - -Code like this will be generated: - -```rust -# struct MyInts(i32, i32); -impl ::core::convert::From<(i32, i32)> for MyInts { - fn from(original: (i32, i32)) -> MyInts { - MyInts(original.0, original.1) - } +#[derive(Debug, From, PartialEq)] +#[from(forward)] +struct Str { + inner: Cow<'static, str>, } + +assert_eq!(Str { inner: "&str".into() }, "&str".into()); +assert_eq!(Str { inner: "String".into() }, "String".to_owned().into()); +assert_eq!(Str { inner: "Cow".into() }, Cow::Borrowed("Cow").to_owned().into()); ``` -## Regular structs +## Enums -For regular structs almost the same code is generated as for tuple structs -except in the way the field values are assigned to the new struct. -When deriving for a regular struct with a single field like this: +For enums `.into()` works for each variant as if they were structs. This +includes specifying concrete types via `#[from()]` or forwarding +implementation with `#[from(forward)]`. ```rust # use derive_more::From; # -#[derive(From)] -struct Point1D { - x: i32, +#[derive(Debug, From, PartialEq)] +enum IntOrPoint { + Int(i32), + Point { + x: i32, + y: i32, + }, } -``` - -Code like this will be generated: -```rust -# struct Point1D { -# x: i32, -# } -impl ::core::convert::From<(i32)> for Point1D { - fn from(original: (i32)) -> Point1D { - Point1D { x: original } - } -} +assert_eq!(IntOrPoint::Int(1), 1.into()); +assert_eq!(IntOrPoint::Point { x: 1, y: 2 }, (1, 2).into()); ``` -The behaviour is a bit different when deriving for a struct with multiple -fields. For instance when deriving for a tuple struct with two fields like this: +By default, `From` is generated for every enum variant, but you can skip some +variants via `#[from(skip)]` or only concrete fields via `#[from]`. ```rust +# mod from { # use derive_more::From; -# -#[derive(From)] -struct Point2D { - x: i32, - y: i32, +#[derive(Debug, From, PartialEq)] +enum Int { + #[from] + Derived(i32), + NotDerived(i32), } -``` +# } -Code like this will be generated: +// Is equivalent to: -```rust -# struct Point2D { -# x: i32, -# y: i32, -# } -impl ::core::convert::From<(i32, i32)> for Point2D { - fn from(original: (i32, i32)) -> Point2D { - Point2D { - x: original.0, - y: original.1, - } - } +# mod skip { +# use derive_more::From; +#[derive(Debug, From, PartialEq)] +enum Int { + Derived(i32), + #[from(skip)] + NotDerived(i32), } +# } ``` -## Enums - -When deriving `From` for enums a new `impl` will be generated for each of its -variants. -If you don't want this for a variant you can put the `#[from(ignore)]` attribute -on that variant. One case where this can be useful is when two variants would -overlap. -For instance when deriving `From` for the following enum: +## Example usage ```rust # use derive_more::From; # -#[derive(From)] -enum MixedInts { +// Allow converting from i32 +#[derive(From, PartialEq)] +struct MyInt(i32); + +// Forward from call to the field, so allow converting +// from anything that can be converted into an i64 (so most integers) +#[derive(From, PartialEq)] +#[from(forward)] +struct MyInt64(i64); + +// You can ignore a variant +#[derive(From, PartialEq)] +enum MyEnum { SmallInt(i32), NamedBigInt { int: i64 }, - TwoSmallInts(i32, i32), - NamedBigInts { x: i64, y: i64 }, #[from(ignore)] - Unsigned(u32), - NamedUnsigned { x: u32 }, -} -``` - -Code like this will be generated: - -```rust -# enum MixedInts { -# SmallInt(i32), -# NamedBigInt { int: i64 }, -# TwoSmallInts(i32, i32), -# NamedBigInts { x: i64, y: i64 }, -# Unsigned(u32), -# NamedUnsigned { x: u32 }, -# } -impl ::core::convert::From<(i32)> for MixedInts { - #[allow(unused_variables)] - #[inline] - fn from(original: (i32)) -> MixedInts { - MixedInts::SmallInt(original) - } -} - -impl ::core::convert::From<(i64)> for MixedInts { - #[allow(unused_variables)] - #[inline] - fn from(original: (i64)) -> MixedInts { - MixedInts::NamedBigInt { int: original } - } + NoFromImpl(i64), } -impl ::core::convert::From<(i32, i32)> for MixedInts { - #[allow(unused_variables)] - #[inline] - fn from(original: (i32, i32)) -> MixedInts { - MixedInts::TwoSmallInts(original.0, original.1) - } +// Or explicitly annotate the ones you need +#[derive(From, PartialEq)] +enum MyEnum2 { + #[from] + SmallInt(i32), + #[from] + NamedBigInt { int: i64 }, + NoFromImpl(i64), } -impl ::core::convert::From<(i64, i64)> for MixedInts { - #[allow(unused_variables)] - #[inline] - fn from(original: (i64, i64)) -> MixedInts { - MixedInts::NamedBigInts { - x: original.0, - y: original.1, - } - } +// And even specify additional conversions for them +#[derive(From, PartialEq)] +enum MyEnum3 { + #[from(i8, i32)] + SmallInt(i32), + #[from(i16, i64)] + NamedBigInt { int: i64 }, + NoFromImpl(i64), } -impl ::core::convert::From<(u32)> for MixedInts { - #[allow(unused_variables)] - #[inline] - fn from(original: (u32)) -> MixedInts { - MixedInts::NamedUnsigned { x: original } - } -} +assert!(MyInt(2) == 2.into()); +assert!(MyInt64(6) == 6u8.into()); +assert!(MyEnum::SmallInt(123) == 123i32.into()); +assert!(MyEnum::SmallInt(123) != 123i64.into()); +assert!(MyEnum::NamedBigInt{int: 123} == 123i64.into()); +assert!(MyEnum3::SmallInt(123) == 123i8.into()); +assert!(MyEnum3::NamedBigInt{int: 123} == 123i16.into()); ``` - -Without the `#[from(ignore)]` on `Unsigned`, no `impl` would be generated for -`Unsigned` and `NamedUnsigned`. The reason for this is that it would be -impossible for the compiler to know which implementation to choose, since they -would both implement `From`. diff --git a/impl/src/fmt/display.rs b/impl/src/fmt/display.rs index 7eb893e4..ff8f9280 100644 --- a/impl/src/fmt/display.rs +++ b/impl/src/fmt/display.rs @@ -9,7 +9,6 @@ use syn::{ parse::{Parse, ParseStream}, parse_quote, spanned::Spanned as _, - Error, Result, }; use super::{BoundsAttribute, FmtAttribute}; @@ -25,7 +24,7 @@ use super::{BoundsAttribute, FmtAttribute}; /// - [`Pointer`](fmt::Pointer) /// - [`UpperExp`](fmt::UpperExp) /// - [`UpperHex`](fmt::UpperHex) -pub fn expand(input: &syn::DeriveInput, trait_name: &str) -> Result { +pub fn expand(input: &syn::DeriveInput, trait_name: &str) -> syn::Result { let trait_name = normalize_trait_name(trait_name); let attrs = Attributes::parse_attrs(&input.attrs, trait_name)?; @@ -73,7 +72,7 @@ type ExpansionCtx<'a> = (&'a Attributes, &'a Ident, &'a Ident, &'a str); fn expand_struct( s: &syn::DataStruct, (attrs, ident, trait_ident, _): ExpansionCtx<'_>, -) -> Result<(Vec, TokenStream)> { +) -> syn::Result<(Vec, TokenStream)> { let s = Expansion { attrs, fields: &s.fields, @@ -106,7 +105,7 @@ fn expand_struct( fn expand_enum( e: &syn::DataEnum, (attrs, _, trait_ident, trait_name): ExpansionCtx<'_>, -) -> Result<(Vec, TokenStream)> { +) -> syn::Result<(Vec, TokenStream)> { if attrs.fmt.is_some() { todo!("https://github.com/JelteF/derive_more/issues/142"); } @@ -121,7 +120,7 @@ fn expand_enum( && variant.fields.is_empty() && trait_name != "Display" { - return Err(Error::new( + return Err(syn::Error::new( e.variants.span(), format!( "implicit formatting of unit enum variant is supported \ @@ -157,7 +156,7 @@ fn expand_enum( arms.extend([quote! { #matcher => { #arm_body }, }]); - Ok::<_, Error>((bounds, arms)) + Ok::<_, syn::Error>((bounds, arms)) }, )?; @@ -173,9 +172,9 @@ fn expand_enum( fn expand_union( u: &syn::DataUnion, (attrs, _, _, trait_name): ExpansionCtx<'_>, -) -> Result<(Vec, TokenStream)> { +) -> syn::Result<(Vec, TokenStream)> { let fmt = &attrs.fmt.as_ref().ok_or_else(|| { - Error::new( + syn::Error::new( u.fields.span(), format!( "unions must have `#[{}(\"...\", ...)]` attribute", @@ -183,13 +182,11 @@ fn expand_union( ), ) })?; - let (lit, args) = (&fmt.lit, &fmt.args); - let body = quote! { - ::core::write!(__derive_more_f, #lit, #( #args ),*) - }; - - Ok((attrs.bounds.0.clone().into_iter().collect(), body)) + Ok(( + attrs.bounds.0.clone().into_iter().collect(), + quote! { ::core::write!(__derive_more_f, #fmt) }, + )) } /// Representation of a [`fmt::Display`]-like derive macro attribute. @@ -215,7 +212,7 @@ impl Attributes { fn parse_attrs( attrs: impl AsRef<[syn::Attribute]>, trait_name: &str, - ) -> Result { + ) -> syn::Result { attrs .as_ref() .iter() @@ -227,10 +224,10 @@ impl Attributes { attrs.bounds.0.extend(more.0); } Attribute::Fmt(fmt) => { - attrs.fmt.replace(fmt).map_or(Ok(()), |dup| Err(Error::new( + attrs.fmt.replace(fmt).map_or(Ok(()), |dup| Err(syn::Error::new( dup.span(), format!( - "Multiple `#[{}(\"...\", ...)]` attributes aren't allowed", + "multiple `#[{}(\"...\", ...)]` attributes aren't allowed", trait_name_to_attribute_name(trait_name), ))))?; } @@ -251,7 +248,7 @@ enum Attribute { } impl Parse for Attribute { - fn parse(input: ParseStream<'_>) -> Result { + fn parse(input: ParseStream<'_>) -> syn::Result { BoundsAttribute::check_legacy_fmt(input)?; FmtAttribute::check_legacy_fmt(input)?; @@ -291,19 +288,12 @@ impl<'a> Expansion<'a> { /// greater than 1. /// /// [`Display::fmt()`]: fmt::Display::fmt() - fn generate_body(&self) -> Result { + fn generate_body(&self) -> syn::Result { match &self.attrs.fmt { - Some(fmt) => { - let (lit, args) = (&fmt.lit, &fmt.args); - Ok(quote! { - ::core::write!(__derive_more_f, #lit, #( #args ),*) - }) - } + Some(fmt) => Ok(quote! { ::core::write!(__derive_more_f, #fmt) }), None if self.fields.is_empty() => { let ident_str = self.ident.to_string(); - Ok(quote! { - ::core::write!(__derive_more_f, #ident_str) - }) + Ok(quote! { ::core::write!(__derive_more_f, #ident_str) }) } None if self.fields.len() == 1 => { let field = self @@ -317,7 +307,7 @@ impl<'a> Expansion<'a> { ::core::fmt::#trait_ident::fmt(#ident, __derive_more_f) }) } - _ => Err(Error::new( + _ => Err(syn::Error::new( self.fields.span(), format!( "struct or enum variant with more than 1 field must have \ diff --git a/impl/src/fmt/mod.rs b/impl/src/fmt/mod.rs index 41e57547..5727d671 100644 --- a/impl/src/fmt/mod.rs +++ b/impl/src/fmt/mod.rs @@ -8,25 +8,24 @@ pub(crate) mod debug; pub(crate) mod display; mod parsing; -use std::{iter, mem}; - -use proc_macro2::{Ident, TokenStream, TokenTree}; +use proc_macro2::TokenStream; use quote::ToTokens; use syn::{ - buffer::Cursor, parse::{Parse, ParseStream}, punctuated::Punctuated, spanned::Spanned as _, - token, Error, Result, + token, Ident, }; +use crate::parsing::Expr; + /// Representation of a macro attribute expressing additional trait bounds. #[derive(Debug, Default)] struct BoundsAttribute(Punctuated); impl BoundsAttribute { /// Errors in case legacy syntax is encountered: `bound = "..."`. - fn check_legacy_fmt(input: ParseStream<'_>) -> Result<()> { + fn check_legacy_fmt(input: ParseStream<'_>) -> syn::Result<()> { let fork = input.fork(); let path = fork @@ -41,7 +40,7 @@ impl BoundsAttribute { _ => None, }) .map_or(Ok(()), |bound| { - Err(Error::new( + Err(syn::Error::new( input.span(), format!("legacy syntax, use `bound({bound})` instead"), )) @@ -52,7 +51,7 @@ impl BoundsAttribute { } impl Parse for BoundsAttribute { - fn parse(input: ParseStream<'_>) -> Result { + fn parse(input: ParseStream<'_>) -> syn::Result { let _ = input.parse::().and_then(|p| { if ["bound", "bounds", "where"] .into_iter() @@ -60,7 +59,7 @@ impl Parse for BoundsAttribute { { Ok(p) } else { - Err(Error::new( + Err(syn::Error::new( p.span(), "unknown attribute, expected `bound(...)`", )) @@ -86,8 +85,11 @@ struct FmtAttribute { /// [`syn::LitStr`]: struct@syn::LitStr lit: syn::LitStr, + /// Optional [`token::Comma`]. + comma: Option, + /// Interpolation arguments. - args: Vec, + args: Punctuated, } impl FmtAttribute { @@ -104,11 +106,12 @@ impl FmtAttribute { Parameter::Named(name) => self .args .iter() - .find_map(|a| (a.alias.as_ref()? == &name).then_some(&a.expr)) + .find_map(|a| (a.alias()? == &name).then_some(&a.expr)) .map_or(Some(name), |expr| expr.ident().map(ToString::to_string))?, Parameter::Positional(i) => self .args - .get(i) + .iter() + .nth(i) .and_then(|a| a.expr.ident().filter(|_| a.alias.is_none()))? .to_string(), }; @@ -129,7 +132,7 @@ impl FmtAttribute { } /// Errors in case legacy syntax is encountered: `fmt = "...", (arg),*`. - fn check_legacy_fmt(input: ParseStream<'_>) -> Result<()> { + fn check_legacy_fmt(input: ParseStream<'_>) -> syn::Result<()> { let fork = input.fork(); let path = fork @@ -154,7 +157,7 @@ impl FmtAttribute { (!args.is_empty()).then_some(args) })() .map_or(Ok(()), |fmt| { - Err(Error::new( + Err(syn::Error::new( input.span(), format!( "legacy syntax, remove `fmt =` and use `{}` instead", @@ -168,28 +171,14 @@ impl FmtAttribute { } impl Parse for FmtAttribute { - fn parse(input: ParseStream) -> Result { - let fmt_lit = input.parse()?; - - if input.peek(syn::token::Comma) { - let _ = input.parse::()?; - } - - let fmt_args = input.step(|cursor| { - let mut cursor = *cursor; - let arguments = iter::from_fn(|| { - let (arg, c) = FmtArgument::parse(cursor)?; - cursor = c; - Some(arg) - }) - .collect(); - - Ok((arguments, cursor)) - })?; - + fn parse(input: ParseStream) -> syn::Result { Ok(Self { - lit: fmt_lit, - args: fmt_args, + lit: input.parse()?, + comma: input + .peek(syn::token::Comma) + .then(|| input.parse()) + .transpose()?, + args: input.parse_terminated(FmtArgument::parse, token::Comma)?, }) } } @@ -197,10 +186,8 @@ impl Parse for FmtAttribute { impl ToTokens for FmtAttribute { fn to_tokens(&self, tokens: &mut TokenStream) { self.lit.to_tokens(tokens); - for arg in &self.args { - syn::token::Comma(arg.span()).to_tokens(tokens); - arg.to_tokens(tokens); - } + self.comma.to_tokens(tokens); + self.args.to_tokens(tokens); } } @@ -208,252 +195,45 @@ impl ToTokens for FmtAttribute { /// in a [`FmtAttribute`]. /// /// [1]: https://doc.rust-lang.org/stable/std/fmt/index.html#named-parameters -#[derive(Debug, Default)] +#[derive(Debug)] struct FmtArgument { - /// `identifier` [`Ident`]. - alias: Option, + /// `identifier =` [`Ident`]. + alias: Option<(Ident, token::Eq)>, - /// `expression` [`FmtExpr`]. - expr: FmtExpr, + /// `expression` [`Expr`]. + expr: Expr, } impl FmtArgument { - /// Parses a [`FmtArgument`] and returns a new [`Cursor`], where parsing - /// can be continued. + /// Returns an `identifier` of the [named parameter][1]. /// - /// Returns [`None`] in case of eof or trailing comma. - fn parse(mut cursor: Cursor<'_>) -> Option<(FmtArgument, Cursor<'_>)> { - let mut arg = FmtArgument::default(); - - if let Some((ident, c)) = cursor.ident() { - if let Some((_eq, c)) = c.punct().filter(|(p, _)| p.as_char() == '=') { - arg.alias = Some(ident); - cursor = c; - } - } - - if let Some((ident, c)) = cursor.ident() { - if let Some(c) = c - .punct() - .and_then(|(p, c)| (p.as_char() == ',').then_some(c)) - .or_else(|| c.eof().then_some(c)) - { - arg.expr = FmtExpr::Ident(ident); - return Some((arg, c)); - } - } - - let (rest, c) = Self::parse_rest(cursor); - arg.expr.extend(rest); - - (!arg.expr.is_empty()).then_some((arg, c)) - } - - /// Parses the rest, until the end of a [`FmtArgument`] (comma or eof), - /// in case simplest case of `(ident =)? ident(,|eof)` wasn't parsed. - fn parse_rest(mut cursor: Cursor<'_>) -> (TokenStream, Cursor<'_>) { - let mut out = TokenStream::new(); - - loop { - if let Some(extend) = Self::turbofish(cursor) { - cursor = extend(&mut out); - continue; - } - if let Some(extend) = Self::closure_args(cursor) { - cursor = extend(&mut out); - continue; - } - if let Some(extend) = Self::qself(cursor) { - cursor = extend(&mut out); - continue; - } - - if let Some(c) = cursor - .punct() - .and_then(|(p, c)| (p.as_char() == ',').then_some(c)) - .or_else(|| cursor.eof().then_some(cursor)) - { - return (out, c); - } - - let (tt, c) = cursor - .token_tree() - .unwrap_or_else(|| unreachable!("checked for eof")); - out.extend([tt]); - cursor = c; - } - } - - /// Tries to parse `| (closure_arg),* |`. - fn closure_args<'a>( - cursor: Cursor<'a>, - ) -> Option Cursor<'a> + 'a> { - let (open, c) = cursor.punct().filter(|(p, _)| p.as_char() == '|')?; - - Some(move |stream: &mut TokenStream| { - stream.extend([TokenTree::Punct(open)]); - // We can ignore inner `|`, because only other place it can appear - // is in or pattern (ex. `Either::Left(v) | Either::Right(v)`), - // which must be parenthesized, so will be parsed as one - // `TokenTree`. - let (more, c) = Self::parse_until_closing('|', '|', c); - stream.extend(more); - c - }) - } - - /// Tries to parse `::< ... >`. - fn turbofish<'a>( - cursor: Cursor<'a>, - ) -> Option Cursor<'a> + 'a> { - use proc_macro2::Spacing; - - let (colon1, c) = cursor - .punct() - .filter(|(p, _)| p.as_char() == ':' && p.spacing() == Spacing::Joint)?; - let (colon2, c) = c.punct().filter(|(p, _)| p.as_char() == ':')?; - let (less, c) = c.punct().filter(|(p, _)| p.as_char() == '<')?; - - Some(move |stream: &mut TokenStream| { - stream.extend([colon1, colon2, less].map(TokenTree::Punct)); - let (more, c) = Self::parse_until_closing('<', '>', c); - stream.extend(more); - c - }) + /// [1]: https://doc.rust-lang.org/stable/std/fmt/index.html#named-parameters + fn alias(&self) -> Option<&Ident> { + self.alias.as_ref().map(|(ident, _)| ident) } +} - /// Tries to parse `< ... as ... >::`. - fn qself<'a>( - cursor: Cursor<'a>, - ) -> Option Cursor<'a> + 'a> { - use proc_macro2::Spacing; - - let (less, c) = cursor.punct().filter(|(p, _)| p.as_char() == '<')?; - let (more, c) = Self::parse_until_closing('<', '>', c); - let (colon1, c) = c - .punct() - .filter(|(p, _)| p.as_char() == ':' && p.spacing() == Spacing::Joint)?; - let (colon2, c) = c.punct().filter(|(p, _)| p.as_char() == ':')?; - - Some(move |stream: &mut TokenStream| { - stream.extend([less].map(TokenTree::Punct)); - stream.extend(more); - stream.extend([colon1, colon2].map(TokenTree::Punct)); - c +impl Parse for FmtArgument { + fn parse(input: ParseStream) -> syn::Result { + Ok(Self { + alias: (input.peek(Ident) && input.peek2(token::Eq)) + .then(|| Ok::<_, syn::Error>((input.parse()?, input.parse()?))) + .transpose()?, + expr: input.parse()?, }) } - - /// Parses until balanced amount of `open` and `close` [`TokenTree::Punct`] - /// or eof. - /// - /// [`Cursor`] should be pointing **right after** the first `open`ing. - fn parse_until_closing( - open: char, - close: char, - mut cursor: Cursor<'_>, - ) -> (TokenStream, Cursor<'_>) { - let mut out = TokenStream::new(); - let mut count = 1; - - while let Some((tt, c)) = cursor.token_tree().filter(|_| count != 0) { - match tt { - TokenTree::Punct(ref p) if p.as_char() == close => { - count -= 1; - } - TokenTree::Punct(ref p) if p.as_char() == open => { - count += 1; - } - _ => {} - } - - out.extend([tt]); - cursor = c; - } - - (out, cursor) - } } impl ToTokens for FmtArgument { fn to_tokens(&self, tokens: &mut TokenStream) { - if let Some(alias) = &self.alias { - alias.to_tokens(tokens); - syn::token::Eq::default().to_tokens(tokens); + if let Some((ident, eq)) = &self.alias { + ident.to_tokens(tokens); + eq.to_tokens(tokens); } self.expr.to_tokens(tokens); } } -/// Representation of an expression being a [`FmtArgument`]. -/// -/// **NOTE**: This type is used instead of a [`syn::Expr`] to avoid using -/// [`syn`]'s `full` feature significantly blowing up compilation times. -#[derive(Debug)] -enum FmtExpr { - /// [`Ident`]. - Ident(Ident), - - /// Plain [`TokenStream`]. - TokenStream(TokenStream), -} - -impl FmtExpr { - /// Returns an [`Ident`] in case this [`FmtExpr`] contains only it, or [`None`] - /// otherwise. - fn ident(&self) -> Option<&Ident> { - match self { - Self::Ident(i) => Some(i), - Self::TokenStream(_) => None, - } - } - - /// Checks whether this [`FmtExpr`] is empty. - fn is_empty(&self) -> bool { - match self { - Self::Ident(_) => false, - Self::TokenStream(stream) => stream.is_empty(), - } - } -} - -impl Default for FmtExpr { - fn default() -> Self { - Self::TokenStream(TokenStream::new()) - } -} - -impl ToTokens for FmtExpr { - fn to_tokens(&self, tokens: &mut TokenStream) { - match self { - Self::Ident(ident) => ident.to_tokens(tokens), - Self::TokenStream(ts) => ts.to_tokens(tokens), - } - } -} - -impl Extend for FmtExpr { - fn extend>(&mut self, iter: T) { - *self = iter - .into_iter() - .fold(mem::take(self), |this, tt| match (this, tt) { - (Self::TokenStream(stream), TokenTree::Ident(ident)) - if stream.is_empty() => - { - Self::Ident(ident) - } - (Self::TokenStream(mut stream), tt) => { - stream.extend([tt]); - Self::TokenStream(stream) - } - (Self::Ident(ident), tt) => { - let mut stream = ident.into_token_stream(); - stream.extend([tt]); - Self::TokenStream(stream) - } - }); - } -} - /// Representation of a [parameter][1] used in a [`Placeholder`]. /// /// [1]: https://doc.rust-lang.org/stable/std/fmt/index.html#formatting-parameters diff --git a/impl/src/from.rs b/impl/src/from.rs index 1896167b..4397f946 100644 --- a/impl/src/from.rs +++ b/impl/src/from.rs @@ -1,128 +1,583 @@ -use std::iter; +//! Implementation of a [`From`] derive macro. -use proc_macro2::TokenStream; -use quote::{format_ident, quote, ToTokens}; -use syn::{parse::Result, DeriveInput, Index}; +use std::{cmp, iter}; -use crate::utils::{ - add_where_clauses_for_new_ident, AttrParams, DeriveType, HashMap, MultiFieldData, - RefType, State, +use proc_macro2::{Span, TokenStream}; +use quote::{format_ident, quote, ToTokens as _, TokenStreamExt as _}; +use syn::{ + parse::{discouraged::Speculative as _, Parse, ParseStream}, + parse_quote, + punctuated::Punctuated, + spanned::Spanned as _, + token, Ident, }; -/// Provides the hook to expand `#[derive(From)]` into an implementation of `From` -pub fn expand(input: &DeriveInput, trait_name: &'static str) -> Result { - let state = State::with_attr_params( - input, - trait_name, - quote! { ::core::convert }, - trait_name.to_lowercase(), - AttrParams { - enum_: vec!["forward", "ignore"], - variant: vec!["forward", "ignore", "types"], - struct_: vec!["forward", "types"], - field: vec!["forward"], - }, - )?; - if state.derive_type == DeriveType::Enum { - Ok(enum_from(input, state)) - } else { - Ok(struct_from(input, &state)) +use crate::{parsing::Type, utils::polyfill}; + +/// Expands a [`From`] derive macro. +pub fn expand(input: &syn::DeriveInput, _: &'static str) -> syn::Result { + match &input.data { + syn::Data::Struct(data) => Expansion { + attrs: StructAttribute::parse_attrs(&input.attrs, &data.fields)? + .map(Into::into) + .as_ref(), + ident: &input.ident, + variant: None, + fields: &data.fields, + generics: &input.generics, + has_explicit_from: false, + } + .expand(), + syn::Data::Enum(data) => { + let mut has_explicit_from = false; + let attrs = data + .variants + .iter() + .map(|variant| { + let attrs = + VariantAttribute::parse_attrs(&variant.attrs, &variant.fields)?; + if matches!( + attrs, + Some( + VariantAttribute::From + | VariantAttribute::Types(_) + | VariantAttribute::Forward + ), + ) { + has_explicit_from = true; + } + Ok(attrs) + }) + .collect::>>()?; + + data.variants + .iter() + .zip(&attrs) + .map(|(variant, attrs)| { + Expansion { + attrs: attrs.as_ref(), + ident: &input.ident, + variant: Some(&variant.ident), + fields: &variant.fields, + generics: &input.generics, + has_explicit_from, + } + .expand() + }) + .collect() + } + syn::Data::Union(data) => Err(syn::Error::new( + data.union_token.span(), + "`From` cannot be derived for unions", + )), } } -pub fn struct_from(input: &DeriveInput, state: &State) -> TokenStream { - let multi_field_data = state.enabled_fields_data(); - let MultiFieldData { - fields, - variant_info, - infos, - input_type, - trait_path, - .. - } = multi_field_data.clone(); - - let additional_types = variant_info.additional_types(RefType::No); - let mut impls = Vec::with_capacity(additional_types.len() + 1); - for explicit_type in iter::once(None).chain(additional_types.iter().map(Some)) { - let mut new_generics = input.generics.clone(); - - let mut initializers = Vec::with_capacity(infos.len()); - let mut from_types = Vec::with_capacity(infos.len()); - for (i, (info, field)) in infos.iter().zip(fields.iter()).enumerate() { - let field_type = &field.ty; - let variable = if fields.len() == 1 { - quote! { original } - } else { - let tuple_index = Index::from(i); - quote! { original.#tuple_index } - }; - if let Some(type_) = explicit_type { - initializers.push(quote! { - <#field_type as #trait_path<#type_>>::from(#variable) - }); - from_types.push(quote! { #type_ }); - } else if info.forward { - let type_param = format_ident!("__FromT{i}"); - let sub_trait_path = quote! { #trait_path<#type_param> }; - let type_where_clauses = quote! { - where #field_type: #sub_trait_path - }; - new_generics = add_where_clauses_for_new_ident( - &new_generics, - &[field], - &type_param, - type_where_clauses, - true, - ); - let casted_trait = quote! { <#field_type as #sub_trait_path> }; - initializers.push(quote! { #casted_trait::from(#variable) }); - from_types.push(quote! { #type_param }); - } else { - initializers.push(variable); - from_types.push(quote! { #field_type }); +/// Representation of a [`From`] derive macro struct container attribute. +/// +/// ```rust,ignore +/// #[from()] +/// #[from(forward)] +/// ``` +enum StructAttribute { + /// [`Type`]s to derive [`From`]. + Types(Punctuated), + + /// Forward [`From`] implementation. + Forward, +} + +impl StructAttribute { + /// Parses a [`StructAttribute`] from the provided [`syn::Attribute`]s. + fn parse_attrs( + attrs: impl AsRef<[syn::Attribute]>, + fields: &syn::Fields, + ) -> syn::Result> { + Ok(attrs + .as_ref() + .iter() + .filter(|attr| attr.path().is_ident("from")) + .try_fold(None, |attrs, attr| { + let field_attr = attr.parse_args_with(|stream: ParseStream<'_>| { + Self::parse(stream, fields) + })?; + match (attrs, field_attr) { + ( + Some((path, StructAttribute::Types(mut tys))), + StructAttribute::Types(more), + ) => { + tys.extend(more); + Ok(Some((path, StructAttribute::Types(tys)))) + } + (None, field_attr) => Ok(Some((attr.path(), field_attr))), + _ => Err(syn::Error::new( + attr.path().span(), + "only single `#[from(...)]` attribute is allowed here", + )), + } + })? + .map(|(_, attr)| attr)) + } + + /// Parses single [`StructAttribute`]. + fn parse(input: ParseStream<'_>, fields: &syn::Fields) -> syn::Result { + let ahead = input.fork(); + match ahead.parse::() { + Ok(p) if p.is_ident("forward") => { + input.advance_to(&ahead); + Ok(Self::Forward) } + Ok(p) if p.is_ident("types") => legacy_error(&ahead, input.span(), fields), + _ => input + .parse_terminated(Type::parse, token::Comma) + .map(Self::Types), } + } +} + +/// Representation of a [`From`] derive macro enum variant attribute. +/// +/// ```rust,ignore +/// #[from] +/// #[from()] +/// #[from(forward)] +/// #[from(skip)] +/// ``` +enum VariantAttribute { + /// Explicitly derive [`From`]. + From, + + /// [`Type`]s to derive [`From`]. + Types(Punctuated), - let body = multi_field_data.initializer(&initializers); - let (impl_generics, _, where_clause) = new_generics.split_for_impl(); - let (_, ty_generics, _) = input.generics.split_for_impl(); + /// Forward [`From`] implementation. + Forward, - impls.push(quote! { - #[automatically_derived] - impl #impl_generics #trait_path<(#(#from_types),*)> for - #input_type #ty_generics #where_clause { + /// Skip variant. + Skip, +} + +impl VariantAttribute { + /// Parses a [`VariantAttribute`] from the provided [`syn::Attribute`]s. + fn parse_attrs( + attrs: impl AsRef<[syn::Attribute]>, + fields: &syn::Fields, + ) -> syn::Result> { + Ok(attrs + .as_ref() + .iter() + .filter(|attr| attr.path().is_ident("from")) + .try_fold(None, |mut attrs, attr| { + let field_attr = Self::parse_attr(attr, fields)?; + if let Some((path, _)) = attrs.replace((attr.path(), field_attr)) { + Err(syn::Error::new( + path.span(), + "only single `#[from(...)]` attribute is allowed here", + )) + } else { + Ok(attrs) + } + })? + .map(|(_, attr)| attr)) + } + + /// Parses a [`VariantAttribute`] from the single provided [`syn::Attribute`]. + fn parse_attr(attr: &syn::Attribute, fields: &syn::Fields) -> syn::Result { + if matches!(attr.meta, syn::Meta::Path(_)) { + return Ok(Self::From); + } - #[inline] - fn from(original: (#(#from_types),*)) -> #input_type #ty_generics { - #body + attr.parse_args_with(|input: ParseStream<'_>| { + let ahead = input.fork(); + match ahead.parse::() { + Ok(p) if p.is_ident("forward") => { + input.advance_to(&ahead); + Ok(Self::Forward) + } + Ok(p) if p.is_ident("skip") || p.is_ident("ignore") => { + input.advance_to(&ahead); + Ok(Self::Skip) + } + Ok(p) if p.is_ident("types") => { + legacy_error(&ahead, input.span(), fields) } + _ => input + .parse_terminated(Type::parse, token::Comma) + .map(Self::Types), } - }); + }) } +} - quote! { #( #impls )* } +impl From for VariantAttribute { + fn from(value: StructAttribute) -> Self { + match value { + StructAttribute::Types(tys) => Self::Types(tys), + StructAttribute::Forward => Self::Forward, + } + } } -fn enum_from(input: &DeriveInput, state: State) -> TokenStream { - let mut tokens = TokenStream::new(); - - let mut variants_per_types = HashMap::default(); - for variant_state in state.enabled_variant_data().variant_states { - let multi_field_data = variant_state.enabled_fields_data(); - let MultiFieldData { field_types, .. } = multi_field_data.clone(); - variants_per_types - .entry(field_types.clone()) - .or_insert_with(Vec::new) - .push(variant_state); +/// Expansion of a macro for generating [`From`] implementation of a struct or +/// enum. +struct Expansion<'a> { + /// [`From`] attributes. + /// + /// As a [`VariantAttribute`] is superset of a [`StructAttribute`], we use + /// it for both derives. + attrs: Option<&'a VariantAttribute>, + + /// Struct or enum [`Ident`]. + ident: &'a Ident, + + /// Variant [`Ident`] in case of enum expansion. + variant: Option<&'a Ident>, + + /// Struct or variant [`syn::Fields`]. + fields: &'a syn::Fields, + + /// Struct or enum [`syn::Generics`]. + generics: &'a syn::Generics, + + /// Indicator whether one of the enum variants has + /// [`VariantAttribute::From`], [`VariantAttribute::Types`] or + /// [`VariantAttribute::Forward`]. + /// + /// Always [`false`] for structs. + has_explicit_from: bool, +} + +impl<'a> Expansion<'a> { + /// Expands [`From`] implementations for a struct or an enum variant. + fn expand(&self) -> syn::Result { + let ident = self.ident; + let field_tys = self.fields.iter().map(|f| &f.ty).collect::>(); + let (impl_gens, ty_gens, where_clause) = self.generics.split_for_impl(); + + let skip_variant = self.has_explicit_from + || (self.variant.is_some() && self.fields.is_empty()); + match (self.attrs, skip_variant) { + (Some(VariantAttribute::Types(tys)), _) => { + tys.iter().map(|ty| { + let variant = self.variant.iter(); + + let mut from_tys = self.validate_type(ty)?; + let init = self.expand_fields(|ident, ty, index| { + let ident = ident.into_iter(); + let index = index.into_iter(); + let from_ty = from_tys.next().unwrap_or_else(|| unreachable!()); + quote! { + #( #ident: )* <#ty as ::core::convert::From<#from_ty>>::from( + value #( .#index )* + ), + } + }); + + Ok(quote! { + #[automatically_derived] + impl #impl_gens ::core::convert::From<#ty> + for #ident #ty_gens #where_clause + { + #[inline] + fn from(value: #ty) -> Self { + #ident #( :: #variant )* #init + } + } + }) + }) + .collect() + } + (Some(VariantAttribute::From), _) | (None, false) => { + let variant = self.variant.iter(); + let init = self.expand_fields(|ident, _, index| { + let ident = ident.into_iter(); + let index = index.into_iter(); + quote! { #( #ident: )* value #( . #index )*, } + }); + + Ok(quote! { + #[automatically_derived] + impl #impl_gens ::core::convert::From<(#( #field_tys ),*)> + for #ident #ty_gens #where_clause + { + #[inline] + fn from(value: (#( #field_tys ),*)) -> Self { + #ident #( :: #variant )* #init + } + } + }) + } + (Some(VariantAttribute::Forward), _) => { + let mut i = 0; + let mut gen_idents = Vec::with_capacity(self.fields.len()); + let init = self.expand_fields(|ident, ty, index| { + let ident = ident.into_iter(); + let index = index.into_iter(); + let gen_ident = format_ident!("__FromT{i}"); + let out = quote! { + #( #ident: )* <#ty as ::core::convert::From<#gen_ident>>::from( + value #( .#index )* + ), + }; + gen_idents.push(gen_ident); + i += 1; + out + }); + + let variant = self.variant.iter(); + let generics = { + let mut generics = self.generics.clone(); + for (ty, ident) in field_tys.iter().zip(&gen_idents) { + generics.make_where_clause().predicates.push( + parse_quote! { #ty: ::core::convert::From<#ident> }, + ); + generics + .params + .push(syn::TypeParam::from(ident.clone()).into()); + } + generics + }; + let (impl_gens, _, where_clause) = generics.split_for_impl(); + + Ok(quote! { + #[automatically_derived] + impl #impl_gens ::core::convert::From<(#( #gen_idents ),*)> + for #ident #ty_gens #where_clause + { + #[inline] + fn from(value: (#( #gen_idents ),*)) -> Self { + #ident #(:: #variant)* #init + } + } + }) + } + (Some(VariantAttribute::Skip), _) | (None, true) => { + Ok(TokenStream::new()) + } + } } - for (ref field_types, ref variant_states) in variants_per_types { - for variant_state in variant_states { - // Don't derive From for variants without any fields - if field_types.is_empty() { - continue; + + /// Expands fields initialization wrapped into [`token::Brace`]s in case of + /// [`syn::FieldsNamed`], or [`token::Paren`] in case of + /// [`syn::FieldsUnnamed`]. + fn expand_fields( + &self, + mut wrap: impl FnMut(Option<&Ident>, &syn::Type, Option) -> TokenStream, + ) -> TokenStream { + let surround = match self.fields { + syn::Fields::Named(_) | syn::Fields::Unnamed(_) => { + Some(|tokens| match self.fields { + syn::Fields::Named(named) => { + let mut out = TokenStream::new(); + named + .brace_token + .surround(&mut out, |out| out.append_all(tokens)); + out + } + syn::Fields::Unnamed(unnamed) => { + let mut out = TokenStream::new(); + unnamed + .paren_token + .surround(&mut out, |out| out.append_all(tokens)); + out + } + syn::Fields::Unit => unreachable!(), + }) + } + syn::Fields::Unit => None, + }; + + surround + .map(|surround| { + surround(if self.fields.len() == 1 { + let field = self + .fields + .iter() + .next() + .unwrap_or_else(|| unreachable!("self.fields.len() == 1")); + wrap(field.ident.as_ref(), &field.ty, None) + } else { + self.fields + .iter() + .enumerate() + .map(|(i, field)| { + wrap(field.ident.as_ref(), &field.ty, Some(i.into())) + }) + .collect() + }) + }) + .unwrap_or_default() + } + + /// Validates the provided [`Type`] against [`syn::Fields`]. + fn validate_type<'t>( + &self, + ty: &'t Type, + ) -> syn::Result> { + match ty { + Type::Tuple { items, .. } if self.fields.len() > 1 => { + match self.fields.len().cmp(&items.len()) { + cmp::Ordering::Greater => { + return Err(syn::Error::new( + ty.span(), + format!( + "wrong tuple length: expected {}, found {}. \ + Consider adding {} more type{}: `({})`", + self.fields.len(), + items.len(), + self.fields.len() - items.len(), + if self.fields.len() - items.len() > 1 { + "s" + } else { + "" + }, + items + .iter() + .map(|item| item.to_string()) + .chain( + (0..(self.fields.len() - items.len())) + .map(|_| "_".to_string()) + ) + .collect::>() + .join(", "), + ), + )); + } + cmp::Ordering::Less => { + return Err(syn::Error::new( + ty.span(), + format!( + "wrong tuple length: expected {}, found {}. \ + Consider removing last {} type{}: `({})`", + self.fields.len(), + items.len(), + items.len() - self.fields.len(), + if items.len() - self.fields.len() > 1 { + "s" + } else { + "" + }, + items + .iter() + .take(self.fields.len()) + .map(|item| item.to_string()) + .collect::>() + .join(", "), + ), + )); + } + cmp::Ordering::Equal => {} + } } - struct_from(input, variant_state).to_tokens(&mut tokens); + Type::Other(other) if self.fields.len() > 1 => { + if self.fields.len() > 1 { + return Err(syn::Error::new( + other.span(), + format!( + "expected tuple: `({}, {})`", + other, + (0..(self.fields.len() - 1)) + .map(|_| "_") + .collect::>() + .join(", "), + ), + )); + } + } + Type::Tuple { .. } | Type::Other(_) => {} + } + Ok(match ty { + Type::Tuple { items, .. } => Either::Left(items.iter()), + Type::Other(other) => Either::Right(iter::once(other)), + }) + } +} + +/// Either [`Left`] or [`Right`]. +/// +/// [`Left`]: Either::Left +/// [`Right`]: Either::Right +enum Either { + /// Left variant. + Left(L), + + /// Right variant. + Right(R), +} + +impl Iterator for Either +where + L: Iterator, + R: Iterator, +{ + type Item = T; + + fn next(&mut self) -> Option { + match self { + Either::Left(left) => left.next(), + Either::Right(right) => right.next(), } } - tokens +} + +/// Constructs a [`syn::Error`] for legacy syntax: `#[from(types(i32, "&str"))]`. +fn legacy_error( + tokens: ParseStream<'_>, + span: Span, + fields: &syn::Fields, +) -> syn::Result { + let content; + syn::parenthesized!(content in tokens); + + let types = content + .parse_terminated(polyfill::NestedMeta::parse, token::Comma)? + .into_iter() + .map(|meta| { + let value = match meta { + polyfill::NestedMeta::Meta(meta) => { + meta.into_token_stream().to_string() + } + polyfill::NestedMeta::Lit(syn::Lit::Str(str)) => str.value(), + polyfill::NestedMeta::Lit(_) => unreachable!(), + }; + if fields.len() > 1 { + format!( + "({})", + fields + .iter() + .map(|_| value.clone()) + .collect::>() + .join(", "), + ) + } else { + value + } + }) + .chain(match fields.len() { + 0 => Either::Left(iter::empty()), + 1 => Either::Right(iter::once( + fields + .iter() + .next() + .unwrap_or_else(|| unreachable!("fields.len() == 1")) + .ty + .to_token_stream() + .to_string(), + )), + _ => Either::Right(iter::once(format!( + "({})", + fields + .iter() + .map(|f| f.ty.to_token_stream().to_string()) + .collect::>() + .join(", ") + ))), + }) + .collect::>() + .join(", "); + + Err(syn::Error::new( + span, + format!("legacy syntax, remove `types` and use `{types}` instead"), + )) } diff --git a/impl/src/lib.rs b/impl/src/lib.rs index 1760086f..751fba5f 100644 --- a/impl/src/lib.rs +++ b/impl/src/lib.rs @@ -57,6 +57,8 @@ mod mul_helpers; mod mul_like; #[cfg(feature = "not")] mod not_like; +#[cfg(any(feature = "debug", feature = "display", feature = "from"))] +pub(crate) mod parsing; #[cfg(feature = "sum")] mod sum_like; #[cfg(feature = "try_into")] diff --git a/impl/src/parsing.rs b/impl/src/parsing.rs new file mode 100644 index 00000000..af571edc --- /dev/null +++ b/impl/src/parsing.rs @@ -0,0 +1,459 @@ +//! Common parsing utilities for derive macros. +//! +//! Fair parsing of [`syn::Type`] and [`syn::Expr`] requires [`syn`]'s `full` +//! feature to be enabled, which unnecessary increases compile times. As we +//! don't have complex AST manipulation, usually requiring only understanding +//! where syntax item begins and ends, simpler manual parsing is implemented. + +use proc_macro2::{Delimiter, Spacing, TokenStream}; +use quote::ToTokens; +use syn::{ + buffer::Cursor, + parse::{Parse, ParseStream}, + punctuated::Punctuated, + spanned::Spanned as _, + token, Error, Ident, Result, +}; + +/// [`syn::Type`] [`Parse`]ing polyfill. +#[derive(Debug)] +pub(crate) enum Type { + /// [`syn::Type::Tuple`] [`Parse`]ing polyfill. + Tuple { + paren: token::Paren, + items: Punctuated, + }, + + /// Every other [`syn::Type`] variant. + Other(TokenStream), +} + +impl Parse for Type { + fn parse(input: ParseStream) -> Result { + input.step(|c| { + let outer = *c; + + if let Some((mut cursor, paren_span, next_item)) = + outer.group(Delimiter::Parenthesis) + { + let mut items = Punctuated::new(); + while !cursor.eof() { + let (stream, c) = Self::parse_other(cursor).ok_or_else(|| { + Error::new(cursor.span(), "failed to parse type") + })?; + items.push_value(stream); + cursor = c; + if let Some((p, c)) = punct(',')(cursor) { + items.push_punct(token::Comma(p.span())); + cursor = c; + } + } + // `(Type)` is equivalent to `Type`, so isn't top-level tuple. + if items.len() == 1 && !items.trailing_punct() { + let stream = outer + .token_tree() + .unwrap_or_else(|| unreachable!()) + .0 + .into_token_stream(); + Ok((Type::Other(stream), next_item)) + } else { + Ok(( + Type::Tuple { + paren: token::Paren(paren_span), + items, + }, + next_item, + )) + } + } else { + Self::parse_other(outer) + .map(|(s, c)| (Self::Other(s), c)) + .ok_or_else(|| Error::new(outer.span(), "failed to parse type")) + } + }) + } +} + +impl ToTokens for Type { + fn to_tokens(&self, tokens: &mut TokenStream) { + match self { + Type::Tuple { paren, items } => { + paren.surround(tokens, |tokens| items.to_tokens(tokens)) + } + Type::Other(other) => other.to_tokens(tokens), + } + } +} + +impl Type { + /// Parses a single [`Type::Other`]. + pub fn parse_other(c: Cursor<'_>) -> Option<(TokenStream, Cursor<'_>)> { + take_until1( + alt([&mut balanced_pair(punct('<'), punct('>')), &mut token_tree]), + punct(','), + )(c) + } +} + +/// [`syn::Expr`] [`Parse`]ing polyfill. +#[derive(Debug)] +pub(crate) enum Expr { + /// [`syn::Expr::Path`] of length 1 [`Parse`]ing polyfill. + Ident(Ident), + + /// Every other [`syn::Expr`] variant. + Other(TokenStream), +} + +impl Expr { + /// Returns an [`Ident`] in case this [`Expr`] is represented only by it. + pub(crate) fn ident(&self) -> Option<&Ident> { + match self { + Self::Ident(ident) => Some(ident), + Self::Other(_) => None, + } + } +} + +impl Parse for Expr { + fn parse(input: ParseStream) -> Result { + if let Ok(ident) = input.step(|c| { + c.ident() + .filter(|(_, c)| c.eof() || punct(',')(*c).is_some()) + .ok_or_else(|| Error::new(c.span(), "expected `ident(,|eof)`")) + }) { + Ok(Self::Ident(ident)) + } else { + input.step(|c| { + take_until1( + alt([ + &mut seq([ + &mut colon2, + &mut balanced_pair(punct('<'), punct('>')), + ]), + &mut seq([ + &mut balanced_pair(punct('<'), punct('>')), + &mut colon2, + ]), + &mut balanced_pair(punct('|'), punct('|')), + &mut token_tree, + ]), + punct(','), + )(*c) + .map(|(stream, cursor)| (Self::Other(stream), cursor)) + .ok_or_else(|| Error::new(c.span(), "failed to parse expression")) + }) + } + } +} + +impl ToTokens for Expr { + fn to_tokens(&self, tokens: &mut TokenStream) { + match self { + Self::Ident(ident) => ident.to_tokens(tokens), + Self::Other(other) => other.to_tokens(tokens), + } + } +} + +/// Result of parsing. +type ParsingResult<'a> = Option<(TokenStream, Cursor<'a>)>; + +/// Tries to parse a [`syn::token::Colon2`]. +pub fn colon2(c: Cursor<'_>) -> ParsingResult<'_> { + seq([ + &mut punct_with_spacing(':', Spacing::Joint), + &mut punct(':'), + ])(c) +} + +/// Tries to parse a [`punct`] with [`Spacing`]. +pub fn punct_with_spacing( + p: char, + spacing: Spacing, +) -> impl FnMut(Cursor<'_>) -> ParsingResult<'_> { + move |c| { + c.punct().and_then(|(punct, c)| { + (punct.as_char() == p && punct.spacing() == spacing) + .then(|| (punct.into_token_stream(), c)) + }) + } +} + +/// Tries to parse a [`Punct`]. +/// +/// [`Punct`]: proc_macro2::Punct +pub fn punct(p: char) -> impl FnMut(Cursor<'_>) -> ParsingResult<'_> { + move |c| { + c.punct().and_then(|(punct, c)| { + (punct.as_char() == p).then(|| (punct.into_token_stream(), c)) + }) + } +} + +/// Tries to parse any [`TokenTree`]. +/// +/// [`TokenTree`]: proc_macro2::TokenTree +pub fn token_tree(c: Cursor<'_>) -> ParsingResult<'_> { + c.token_tree().map(|(tt, c)| (tt.into_token_stream(), c)) +} + +/// Parses until balanced amount of `open` and `close` or eof. +/// +/// [`Cursor`] should be pointing **right after** the first `open`ing. +pub fn balanced_pair( + mut open: impl FnMut(Cursor<'_>) -> ParsingResult<'_>, + mut close: impl FnMut(Cursor<'_>) -> ParsingResult<'_>, +) -> impl FnMut(Cursor<'_>) -> ParsingResult<'_> { + move |c| { + let (mut out, mut c) = open(c)?; + let mut count = 1; + + while count != 0 { + let (stream, cursor) = if let Some(closing) = close(c) { + count -= 1; + closing + } else if let Some(opening) = open(c) { + count += 1; + opening + } else { + let (tt, c) = c.token_tree()?; + (tt.into_token_stream(), c) + }; + out.extend(stream); + c = cursor; + } + + Some((out, c)) + } +} + +/// Tries to execute the provided sequence of `parsers`. +pub fn seq( + mut parsers: [&mut dyn FnMut(Cursor<'_>) -> ParsingResult<'_>; N], +) -> impl FnMut(Cursor<'_>) -> ParsingResult<'_> + '_ { + move |c| { + parsers + .iter_mut() + .fold(Some((TokenStream::new(), c)), |out, parser| { + let (mut out, mut c) = out?; + let (stream, cursor) = parser(c)?; + out.extend(stream); + c = cursor; + Some((out, c)) + }) + } +} + +/// Tries to execute the first successful parser. +pub fn alt( + mut parsers: [&mut dyn FnMut(Cursor<'_>) -> ParsingResult<'_>; N], +) -> impl FnMut(Cursor<'_>) -> ParsingResult<'_> + '_ { + move |c| { + parsers + .iter_mut() + .find_map(|parser| parser(c).map(|(s, c)| (s, c))) + } +} + +/// Parses with `basic` while `until` fails. Returns [`None`] in case +/// `until` succeeded initially or `basic` never succeeded. Doesn't consume +/// tokens parsed by `until`. +pub fn take_until1( + mut parser: P, + mut until: U, +) -> impl FnMut(Cursor<'_>) -> ParsingResult<'_> +where + P: FnMut(Cursor<'_>) -> ParsingResult<'_>, + U: FnMut(Cursor<'_>) -> ParsingResult<'_>, +{ + move |mut cursor| { + let mut out = TokenStream::new(); + let mut parsed = false; + + loop { + if cursor.eof() || until(cursor).is_some() { + return parsed.then_some((out, cursor)); + } + + let (stream, c) = parser(cursor)?; + out.extend(stream); + cursor = c; + parsed = true; + } + } +} + +#[cfg(test)] +mod spec { + use std::{fmt::Debug, str::FromStr}; + + use itertools::Itertools as _; + use proc_macro2::TokenStream; + use quote::ToTokens; + use syn::{ + parse::{Parse, Parser as _}, + punctuated::Punctuated, + token::Comma, + }; + + use super::{Expr, Type}; + + fn assert<'a, T: Debug + Parse + ToTokens>( + input: &'a str, + parsed: impl AsRef<[&'a str]>, + ) { + let parsed = parsed.as_ref(); + let punctuated = Punctuated::::parse_terminated + .parse2(TokenStream::from_str(input).unwrap()) + .unwrap(); + + assert_eq!( + parsed.len(), + punctuated.len(), + "Wrong length\n\ + Expected: {parsed:?}\n\ + Found: {punctuated:?}", + ); + + punctuated + .iter() + .map(|ty| ty.to_token_stream().to_string()) + .zip(parsed) + .enumerate() + .for_each(|(i, (found, expected))| { + assert_eq!( + *expected, &found, + "Mismatch at index {i}\n\ + Expected: {parsed:?}\n\ + Found: {punctuated:?}", + ); + }); + } + + mod tuple { + use super::*; + + #[test] + fn zst_is_tuple() { + let zst = "()"; + match syn::parse_str::(zst).unwrap() { + Type::Tuple { items, .. } => { + assert!(items.is_empty(), "Expected empty tuple, found: {items:?}"); + } + other => panic!("Expected `Type::Tuple {{ .. }}`, found: {other:?}"), + } + } + + #[test] + fn group_not_tuple() { + let group = "(Type)"; + match syn::parse_str::(group).unwrap() { + Type::Other(tokens) => { + assert_eq!(tokens.to_string(), group); + } + tuple => panic!("Expected `Type::Other(_)`, found: {tuple:?}"), + } + } + + #[test] + fn single_element_tuple() { + let tuple = "(Type,)"; + match syn::parse_str::(tuple).unwrap() { + Type::Tuple { items, .. } => { + assert_eq!( + items.len(), + 1, + "Expected empty tuple, found: {items:?}", + ); + assert_eq!(items.first().unwrap().to_string(), "Type"); + } + other => panic!("Expected `Type::Tuple {{ .. }}`, found: {other:?}"), + } + } + + #[test] + fn cases() { + let cases = [ + "[Type ; 3]", + "fn (usize) -> bool", + "for <'a > fn (&'a usize) -> bool", + "(Type)", + "path :: to :: Type", + "path :: to :: Generic < Type >", + "< Type as Trait >:: Assoc", + "< Type as Trait >:: Assoc < GAT >", + "* const ()", + "* mut ()", + "& i32", + "&'static str", + "& [str]", + "dyn Trait", + "dyn Trait + Send", + "()", + "(Type ,)", + "(Type , Type)", + "(Type , Type ,)", + ]; + + assert::("", []); + for i in 1..4 { + for permutations in cases.into_iter().permutations(i) { + let mut input = permutations.join(","); + assert::(&input, &permutations); + input.push(','); + assert::(&input, &permutations); + } + } + } + } + + mod expr { + use super::*; + + #[test] + fn cases() { + let cases = [ + "ident", + "[a , b , c , d]", + "counter += 1", + "async { fut . await }", + "a < b", + "a > b", + "{ let x = (a , b) ; }", + "invoke (a , b)", + "foo as f64", + "| a , b | a + b", + "obj . k", + "for pat in expr { break pat ; }", + "if expr { true } else { false }", + "vector [2]", + "1", + "\"foo\"", + "loop { break i ; }", + "format ! (\"{}\" , q)", + "match n { Some (n) => { } , None => { } }", + "x . foo ::< T > (a , b)", + "x . foo ::< T < [T < T >; if a < b { 1 } else { 2 }] >, { a < b } > (a , b)", + "(a + b)", + "i32 :: MAX", + "1 .. 2", + "& a", + "[0u8 ; N]", + "(a , b , c , d)", + "< Ty as Trait > :: T", + "< Ty < Ty < T >, { a < b } > as Trait < T > > :: T", + ]; + + assert::("", []); + for i in 1..4 { + for permutations in cases.into_iter().permutations(i) { + let mut input = permutations.clone().join(","); + assert::(&input, &permutations); + input.push(','); + assert::(&input, &permutations); + } + } + } + } +} diff --git a/impl/src/utils.rs b/impl/src/utils.rs index 3dd24ce3..f1922051 100644 --- a/impl/src/utils.rs +++ b/impl/src/utils.rs @@ -1111,7 +1111,7 @@ fn parse_punctuated_nested_meta( // TODO: Remove this eventually, once all macros migrate to // custom typed attributes parsing. /// Polyfill for [`syn`] 1.x AST. -mod polyfill { +pub(crate) mod polyfill { use proc_macro2::TokenStream; use quote::ToTokens; use syn::{ @@ -1121,7 +1121,7 @@ mod polyfill { }; #[derive(Clone)] - pub(super) enum PathOrKeyword { + pub(crate) enum PathOrKeyword { Path(syn::Path), Keyword(syn::Ident), } @@ -1173,7 +1173,7 @@ mod polyfill { } #[derive(Clone)] - pub(super) struct MetaList { + pub(crate) struct MetaList { pub(super) path: PathOrKeyword, pub(super) tokens: TokenStream, } @@ -1205,7 +1205,7 @@ mod polyfill { } #[derive(Clone)] - pub(super) enum Meta { + pub(crate) enum Meta { Path(PathOrKeyword), List(MetaList), } @@ -1236,7 +1236,7 @@ mod polyfill { } #[derive(Clone)] - pub(super) enum NestedMeta { + pub(crate) enum NestedMeta { Meta(Meta), Lit(syn::Lit), } diff --git a/tests/compile_fail/from/legacy_enum_attribute.rs b/tests/compile_fail/from/legacy_enum_attribute.rs new file mode 100644 index 00000000..26551a75 --- /dev/null +++ b/tests/compile_fail/from/legacy_enum_attribute.rs @@ -0,0 +1,7 @@ +#[derive(derive_more::From)] +enum Foo { + #[from(types(i32, "&str"))] + Bar(String), +} + +fn main() {} diff --git a/tests/compile_fail/from/legacy_enum_attribute.stderr b/tests/compile_fail/from/legacy_enum_attribute.stderr new file mode 100644 index 00000000..7d2d923f --- /dev/null +++ b/tests/compile_fail/from/legacy_enum_attribute.stderr @@ -0,0 +1,5 @@ +error: legacy syntax, remove `types` and use `i32, &str, String` instead + --> tests/compile_fail/from/legacy_enum_attribute.rs:3:12 + | +3 | #[from(types(i32, "&str"))] + | ^^^^^ diff --git a/tests/compile_fail/from/legacy_struct_attribute.rs b/tests/compile_fail/from/legacy_struct_attribute.rs new file mode 100644 index 00000000..1c7d9c5c --- /dev/null +++ b/tests/compile_fail/from/legacy_struct_attribute.rs @@ -0,0 +1,8 @@ +#[derive(derive_more::From)] +#[from(types(i32, "&str"))] +struct Foo { + foo: String, + bar: String, +} + +fn main() {} diff --git a/tests/compile_fail/from/legacy_struct_attribute.stderr b/tests/compile_fail/from/legacy_struct_attribute.stderr new file mode 100644 index 00000000..6e5300af --- /dev/null +++ b/tests/compile_fail/from/legacy_struct_attribute.stderr @@ -0,0 +1,5 @@ +error: legacy syntax, remove `types` and use `(i32, i32), (&str, &str), (String, String)` instead + --> tests/compile_fail/from/legacy_struct_attribute.rs:2:8 + | +2 | #[from(types(i32, "&str"))] + | ^^^^^ diff --git a/tests/compile_fail/from/multiple_enum_attributes.rs b/tests/compile_fail/from/multiple_enum_attributes.rs new file mode 100644 index 00000000..6fc73eed --- /dev/null +++ b/tests/compile_fail/from/multiple_enum_attributes.rs @@ -0,0 +1,8 @@ +#[derive(derive_more::From)] +enum Foo { + #[from(i32)] + #[from(forward)] + Bar(i32), +} + +fn main() {} diff --git a/tests/compile_fail/from/multiple_enum_attributes.stderr b/tests/compile_fail/from/multiple_enum_attributes.stderr new file mode 100644 index 00000000..966e989e --- /dev/null +++ b/tests/compile_fail/from/multiple_enum_attributes.stderr @@ -0,0 +1,5 @@ +error: only single `#[from(...)]` attribute is allowed here + --> tests/compile_fail/from/multiple_enum_attributes.rs:3:7 + | +3 | #[from(i32)] + | ^^^^ diff --git a/tests/compile_fail/from/multiple_struct_attributes.rs b/tests/compile_fail/from/multiple_struct_attributes.rs new file mode 100644 index 00000000..4fa2a84c --- /dev/null +++ b/tests/compile_fail/from/multiple_struct_attributes.rs @@ -0,0 +1,6 @@ +#[derive(derive_more::From)] +#[from(i32)] +#[from(forward)] +struct Foo(i32); + +fn main() {} diff --git a/tests/compile_fail/from/multiple_struct_attributes.stderr b/tests/compile_fail/from/multiple_struct_attributes.stderr new file mode 100644 index 00000000..6c3c8ca9 --- /dev/null +++ b/tests/compile_fail/from/multiple_struct_attributes.stderr @@ -0,0 +1,5 @@ +error: only single `#[from(...)]` attribute is allowed here + --> tests/compile_fail/from/multiple_struct_attributes.rs:3:3 + | +3 | #[from(forward)] + | ^^^^ diff --git a/tests/compile_fail/from/struct_tuple_no_parens.rs b/tests/compile_fail/from/struct_tuple_no_parens.rs new file mode 100644 index 00000000..8dd4462d --- /dev/null +++ b/tests/compile_fail/from/struct_tuple_no_parens.rs @@ -0,0 +1,8 @@ +#[derive(derive_more::From)] +#[from(i16, i16)] +struct Point { + x: i32, + y: i32, +} + +fn main() {} diff --git a/tests/compile_fail/from/struct_tuple_no_parens.stderr b/tests/compile_fail/from/struct_tuple_no_parens.stderr new file mode 100644 index 00000000..18b18069 --- /dev/null +++ b/tests/compile_fail/from/struct_tuple_no_parens.stderr @@ -0,0 +1,5 @@ +error: expected tuple: `(i16, _)` + --> tests/compile_fail/from/struct_tuple_no_parens.rs:2:8 + | +2 | #[from(i16, i16)] + | ^^^ diff --git a/tests/compile_fail/from/struct_tuple_too_long.rs b/tests/compile_fail/from/struct_tuple_too_long.rs new file mode 100644 index 00000000..ed56c0a5 --- /dev/null +++ b/tests/compile_fail/from/struct_tuple_too_long.rs @@ -0,0 +1,8 @@ +#[derive(derive_more::From)] +#[from((i16, i16, i16))] +struct Point { + x: i32, + y: i32, +} + +fn main() {} diff --git a/tests/compile_fail/from/struct_tuple_too_long.stderr b/tests/compile_fail/from/struct_tuple_too_long.stderr new file mode 100644 index 00000000..3ee791b7 --- /dev/null +++ b/tests/compile_fail/from/struct_tuple_too_long.stderr @@ -0,0 +1,5 @@ +error: wrong tuple length: expected 2, found 3. Consider removing last 1 type: `(i16, i16)` + --> tests/compile_fail/from/struct_tuple_too_long.rs:2:8 + | +2 | #[from((i16, i16, i16))] + | ^^^^^^^^^^^^^^^ diff --git a/tests/compile_fail/from/struct_tuple_too_short.rs b/tests/compile_fail/from/struct_tuple_too_short.rs new file mode 100644 index 00000000..2f514731 --- /dev/null +++ b/tests/compile_fail/from/struct_tuple_too_short.rs @@ -0,0 +1,8 @@ +#[derive(derive_more::From)] +#[from((i16,))] +struct Point { + x: i32, + y: i32, +} + +fn main() {} diff --git a/tests/compile_fail/from/struct_tuple_too_short.stderr b/tests/compile_fail/from/struct_tuple_too_short.stderr new file mode 100644 index 00000000..121c5bb0 --- /dev/null +++ b/tests/compile_fail/from/struct_tuple_too_short.stderr @@ -0,0 +1,5 @@ +error: wrong tuple length: expected 2, found 1. Consider adding 1 more type: `(i16, _)` + --> tests/compile_fail/from/struct_tuple_too_short.rs:2:8 + | +2 | #[from((i16,))] + | ^^^^^^ diff --git a/tests/compile_fail/from/union.rs b/tests/compile_fail/from/union.rs new file mode 100644 index 00000000..d0e78f14 --- /dev/null +++ b/tests/compile_fail/from/union.rs @@ -0,0 +1,6 @@ +#[derive(derive_more::From)] +pub union Foo { + bar: i32, +} + +fn main() {} diff --git a/tests/compile_fail/from/union.stderr b/tests/compile_fail/from/union.stderr new file mode 100644 index 00000000..1d6a44b7 --- /dev/null +++ b/tests/compile_fail/from/union.stderr @@ -0,0 +1,5 @@ +error: `From` cannot be derived for unions + --> tests/compile_fail/from/union.rs:2:5 + | +2 | pub union Foo { + | ^^^^^ diff --git a/tests/from.rs b/tests/from.rs index f6b26c8e..e43fa748 100644 --- a/tests/from.rs +++ b/tests/from.rs @@ -13,185 +13,1779 @@ use alloc::{ use std::borrow::Cow; use derive_more::From; +use static_assertions::assert_not_impl_any; -#[derive(From)] -struct EmptyTuple(); +mod structs { + use super::*; -#[derive(From)] -struct EmptyStruct {} + mod unit { + use super::*; -#[derive(From)] -struct EmptyUnit; + #[derive(Debug, From, PartialEq)] + struct Unit; -#[derive(From)] -struct MyInt(i32); + #[derive(Debug, From, PartialEq)] + struct Tuple(); -#[derive(From)] -struct MyInts(i32, i32); + #[derive(Debug, From, PartialEq)] + struct Struct {} -#[derive(From)] -struct Point1D { - x: i32, -} + #[test] + fn assert() { + assert_eq!(Unit, ().into()); + assert_eq!(Tuple(), ().into()); + assert_eq!(Struct {}, ().into()); + } -#[derive(From)] -struct Point2D { - x: i32, - y: i32, -} + mod generic { + use super::*; -#[derive(From)] -enum MixedInts { - SmallInt(i32), - NamedBigInt { - int: i64, - }, - TwoSmallInts(i32, i32), - NamedBigInts { - x: i64, - y: i64, - }, - #[from(ignore)] - Unsigned(u32), - NamedUnsigned { - x: u32, - }, -} + #[derive(Debug, From, PartialEq)] + struct Unit; -#[derive(Debug, Eq, PartialEq)] -#[derive(From)] -#[from(forward)] -struct MyIntForward(u64); + #[derive(Debug, From, PartialEq)] + struct Tuple(); -#[test] -fn forward_struct() { - assert_eq!(MyIntForward(42), 42u32.into()); - assert_eq!(MyIntForward(42), 42u16.into()); - assert_eq!(MyIntForward(42), 42u64.into()); -} + #[derive(Debug, From, PartialEq)] + struct Struct {} -#[derive(Debug, Eq, PartialEq)] -#[derive(From)] -enum MixedIntsForward { - #[from(forward)] - SmallInt(i32), - NamedBigInt { - int: i64, - }, -} + #[test] + fn assert() { + assert_eq!(Unit::<1>, ().into()); + assert_eq!(Tuple::<1>(), ().into()); + assert_eq!(Struct::<1> {}, ().into()); + } + } + } -#[test] -fn forward_enum() { - assert_eq!(MixedIntsForward::SmallInt(42), 42i32.into()); - assert_eq!(MixedIntsForward::SmallInt(42), 42i16.into()); -} + mod single_field { + use super::*; -#[derive(From, PartialEq)] -enum AutoIgnore { - SmallInt(i32), - Uninteresting, - Uninteresting2, -} + #[derive(Debug, From, PartialEq)] + struct Tuple(i32); -#[test] -fn auto_ignore_variants() { - assert!(AutoIgnore::SmallInt(42) == 42i32.into()); -} + #[derive(Debug, From, PartialEq)] + struct Struct { + field: i32, + } -#[derive(From, PartialEq)] -enum AutoIgnoreWithDefaultTrue { - #[from(ignore)] - SmallInt(i32), - Uninteresting, - Uninteresting2, -} + #[test] + fn assert() { + assert_eq!(Tuple(42), 42.into()); + assert_eq!(Struct { field: 42 }, 42.into()); + } -#[derive(From, PartialEq)] -enum AutoIgnoreWithForwardFields2 { - #[from(forward)] - SmallInt(i32), - SmallIntIgnore(i32), -} + mod types { + use super::*; -#[test] -fn auto_ignore_with_forward_field2() { - assert!(AutoIgnoreWithForwardFields2::SmallInt(42) == 42i32.into()); - assert!(AutoIgnoreWithForwardFields2::SmallInt(42) == 42i16.into()); -} + #[derive(Debug, From, PartialEq)] + #[from(i8)] + #[from(i16)] + struct Tuple(i32); -#[derive(Debug, Eq, PartialEq)] -#[derive(From)] -#[from(types(u8, u16, u32))] -struct MyIntExplicit(u64); - -#[test] -fn explicit_types_struct() { - assert_eq!(MyIntExplicit(42), 42u8.into()); - assert_eq!(MyIntExplicit(42), 42u16.into()); - assert_eq!(MyIntExplicit(42), 42u32.into()); - assert_eq!(MyIntExplicit(42), 42u64.into()); -} + #[derive(Debug, From, PartialEq)] + #[from(&str, Cow<'_, str>)] + struct Struct { + field: String, + } -#[derive(Debug, Eq, PartialEq)] -#[derive(From)] -#[from(types(i8, i16))] -struct MyIntsExplicit(i32, i32); + #[test] + fn assert() { + assert_not_impl_any!(Tuple: From); + assert_not_impl_any!(Struct: From); -#[test] -fn explicit_types_struct_tupled() { - assert_eq!(MyIntsExplicit(42, 42), (42i32, 42i32).into()); - assert_eq!(MyIntsExplicit(42, 42), (42i8, 42i8).into()); - assert_eq!(MyIntsExplicit(42, 42), (42i16, 42i16).into()); -} + assert_eq!(Tuple(42), 42_i8.into()); + assert_eq!(Tuple(42), 42_i16.into()); + assert_eq!( + Struct { + field: "42".to_string(), + }, + "42".into(), + ); + assert_eq!( + Struct { + field: "42".to_string(), + }, + Cow::Borrowed("42").into(), + ); + } + } -#[derive(Debug, Eq, PartialEq)] -#[derive(From)] -enum MixedIntsExplicit { - #[from(types(i8))] - SmallInt(i32), - #[from(types(i16, i64))] - AnotherInt(i128), - NamedBigInt { - int: i64, - }, -} + mod forward { + use super::*; -#[test] -fn explicit_types_enum() { - assert_eq!(MixedIntsExplicit::SmallInt(42), 42i32.into()); - assert_eq!(MixedIntsExplicit::SmallInt(42), 42i8.into()); + #[derive(Debug, From, PartialEq)] + #[from(forward)] + struct Tuple(i32); - assert_eq!(MixedIntsExplicit::AnotherInt(42), 42i128.into()); - assert_eq!(MixedIntsExplicit::AnotherInt(42), 42i64.into()); - assert_eq!(MixedIntsExplicit::AnotherInt(42), 42i16.into()); -} + #[derive(Debug, From, PartialEq)] + #[from(forward)] + struct Struct { + field: String, + } -#[derive(Debug, Eq, PartialEq)] -#[derive(From)] -#[from(types(i8, i16))] -struct Point2DExplicit { - x: i32, - y: i32, -} + #[test] + fn assert() { + assert_eq!(Tuple(42), 42_i8.into()); + assert_eq!(Tuple(42), 42_i16.into()); + assert_eq!(Tuple(42), 42_i32.into()); + assert_eq!( + Struct { + field: "42".to_string(), + }, + "42".into(), + ); + assert_eq!( + Struct { + field: "42".to_string(), + }, + Cow::Borrowed("42").into(), + ); + } + } + + mod generic { + use super::*; + + #[derive(Debug, From, PartialEq)] + struct Tuple(T); + + #[derive(Debug, From, PartialEq)] + struct Struct { + field: T, + } + + #[test] + fn assert() { + assert_eq!(Tuple(42), 42.into()); + assert_eq!(Struct { field: 42 }, 42.into()); + } + + mod reference { + use super::*; + + #[derive(Debug, From, PartialEq)] + struct Tuple<'a, T>(&'a T); + + #[derive(Debug, From, PartialEq)] + struct Struct<'a, T> { + field: &'a T, + } + + #[test] + fn assert() { + assert_eq!(Tuple(&42), (&42).into()); + assert_eq!(Struct { field: &42 }, (&42).into()); + } + } + + mod indirect { + use super::*; + + #[derive(Debug, From, PartialEq)] + struct Tuple(&'static T); + + #[derive(Debug, From, PartialEq)] + struct Struct { + field: &'static T, + } + + #[test] + fn assert() { + assert_eq!(Tuple(&42), (&42).into()); + assert_eq!(Struct { field: &42 }, (&42).into()); + } + } + + mod bounded { + use super::*; + + #[derive(Debug, From, PartialEq)] + struct Tuple(T); + + #[derive(Debug, From, PartialEq)] + struct Struct { + field: T, + } + + #[test] + fn assert() { + assert_eq!(Tuple(42), 42.into()); + assert_eq!(Struct { field: 42 }, 42.into()); + } + } + + mod r#const { + use super::*; + + #[derive(Debug, From, PartialEq)] + struct Tuple(T); + + #[derive(Debug, From, PartialEq)] + struct Struct { + field: T, + } + + #[test] + fn assert() { + assert_eq!(Tuple::<1, _>(1), 1.into()); + assert_eq!(Struct::<_, 1> { field: 1 }, 1.into()); + } + } + } + } + + mod multi_field { + use super::*; + + #[derive(Debug, From, PartialEq)] + struct Tuple(i32, i16); + + #[derive(Debug, From, PartialEq)] + struct Struct { + field1: i32, + field2: i16, + } + + #[test] + fn assert() { + assert_eq!(Tuple(0, 1), (0, 1_i16).into()); + assert_eq!( + Struct { + field1: 0, + field2: 1, + }, + (0, 1_i16).into(), + ); + } + + mod types { + use super::*; + + #[derive(Debug, From, PartialEq)] + #[from((i16, i16))] + struct Tuple(i32, i16); + + #[derive(Debug, From, PartialEq)] + #[from((i16, i16))] + struct Struct { + field1: i32, + field2: i16, + } + + #[test] + fn assert() { + assert_not_impl_any!(Tuple: From<(i32, i16)>); + assert_not_impl_any!(Struct: From<(i32, i16)>); + + assert_eq!(Tuple(0, 1), (0_i16, 1_i16).into()); + assert_eq!( + Struct { + field1: 0, + field2: 1 + }, + (0_i16, 1_i16).into(), + ); + } + } + + mod forward { + use super::*; + + #[derive(Debug, From, PartialEq)] + #[from(forward)] + struct Tuple(i32, i16); + + #[derive(Debug, From, PartialEq)] + #[from(forward)] + struct Struct { + field1: i32, + field2: i16, + } + + #[test] + fn assert() { + assert_eq!(Tuple(0, 1), (0_i8, 1_i8).into()); + assert_eq!(Tuple(0, 1), (0_i8, 1_i16).into()); + assert_eq!(Tuple(0, 1), (0_i16, 1_i8).into()); + assert_eq!(Tuple(0, 1), (0_i16, 1_i16).into()); + assert_eq!(Tuple(0, 1), (0_i32, 1_i8).into()); + assert_eq!(Tuple(0, 1), (0_i32, 1_i16).into()); + assert_eq!( + Struct { + field1: 0, + field2: 1 + }, + (0_i8, 1_i8).into(), + ); + assert_eq!( + Struct { + field1: 0, + field2: 1 + }, + (0_i8, 1_i16).into(), + ); + assert_eq!( + Struct { + field1: 0, + field2: 1 + }, + (0_i16, 1_i8).into(), + ); + assert_eq!( + Struct { + field1: 0, + field2: 1 + }, + (0_i16, 1_i16).into(), + ); + assert_eq!( + Struct { + field1: 0, + field2: 1 + }, + (0_i32, 1_i8).into(), + ); + assert_eq!( + Struct { + field1: 0, + field2: 1 + }, + (0_i32, 1_i16).into(), + ); + } + } + + mod generic { + use super::*; + + #[derive(Debug, From, PartialEq)] + struct Tuple(A, B); + + #[derive(Debug, From, PartialEq)] + struct Struct { + field1: A, + field2: B, + } + + #[test] + fn assert() { + assert_eq!(Tuple(1, 2_i8), (1, 2_i8).into()); + assert_eq!( + Struct { + field1: 1, + field2: 2_i8, + }, + (1, 2_i8).into(), + ); + } + + mod reference { + use super::*; + + #[derive(Debug, From, PartialEq)] + struct Tuple<'a, A, B>(&'a A, &'a B); + + #[derive(Debug, From, PartialEq)] + struct Struct<'a, A, B> { + field1: &'a A, + field2: &'a B, + } + + #[test] + fn assert() { + assert_eq!(Tuple(&1, &2_i8), (&1, &2_i8).into()); + assert_eq!( + Struct { + field1: &1, + field2: &2_i8, + }, + (&1, &2_i8).into(), + ); + } + } + + mod bounded { + use super::*; + + #[derive(Debug, From, PartialEq)] + struct Tuple(A, B); + + #[derive(Debug, From, PartialEq)] + struct Struct { + field1: A, + field2: B, + } + + #[test] + fn assert() { + assert_eq!(Tuple(1, 2_i8), (1, 2_i8).into()); + assert_eq!( + Struct { + field1: 1, + field2: 2_i8, + }, + (1, 2_i8).into(), + ); + } + } -#[test] -fn explicit_types_point_2d() { - let expected = Point2DExplicit { x: 42, y: 42 }; - assert_eq!(expected, (42i32, 42i32).into()); - assert_eq!(expected, (42i8, 42i8).into()); - assert_eq!(expected, (42i16, 42i16).into()); + mod r#const { + use super::*; + + #[derive(Debug, From, PartialEq)] + struct ConstTuple(A, B); + + #[derive(Debug, From, PartialEq)] + struct ConstStruct { + field1: A, + field2: B, + } + + #[test] + fn assert() { + assert_eq!(ConstTuple::<1, _, _>(1, 2_i8), (1, 2_i8).into()); + assert_eq!( + ConstStruct::<1, _, _> { + field1: 1, + field2: 2_i8, + }, + (1, 2_i8).into(), + ); + } + } + } + } } -#[derive(Debug, Eq, PartialEq)] -#[derive(From)] -#[from(types("Cow<'_, str>", "&str"))] -struct Name(String); - -#[test] -fn explicit_complex_types_name() { - let name = "EƤrendil"; - let expected = Name(name.into()); - assert_eq!(expected, name.to_string().into()); - assert_eq!(expected, name.into()); - assert_eq!(expected, Cow::Borrowed(name).into()); +mod enums { + use super::*; + + mod unit_variant { + use super::*; + + #[derive(Debug, From, PartialEq)] + enum Enum { + #[from] + Unit, + Unnamed(), + Named {}, + } + + #[test] + fn assert() { + assert_eq!(Enum::Unit, ().into()); + } + + mod generic { + use super::*; + + #[derive(Debug, From, PartialEq)] + enum Unit { + Variant, + } + + #[derive(Debug, From, PartialEq)] + enum Tuple { + Variant(), + } + + #[derive(Debug, From, PartialEq)] + enum Struct { + Variant {}, + } + + assert_not_impl_any!(Unit: From<()>); + assert_not_impl_any!(Tuple: From<()>); + assert_not_impl_any!(Struct: From<()>); + + mod r#const { + use super::*; + + #[derive(Debug, From, PartialEq)] + enum Unit { + Variant, + } + + #[derive(Debug, From, PartialEq)] + enum Tuple { + Variant(), + } + + #[derive(Debug, From, PartialEq)] + enum Struct { + Variant {}, + } + + assert_not_impl_any!(Unit<0>: From<()>); + assert_not_impl_any!(Tuple<0>: From<()>); + assert_not_impl_any!(Struct<0>: From<()>); + } + } + + mod from { + use super::*; + + #[derive(Debug, From, PartialEq)] + enum Unit { + #[from] + Variant, + AutomaticallySkipped, + } + + #[derive(Debug, From, PartialEq)] + enum Tuple { + #[from] + Variant(), + AutomaticallySkipped(), + } + + #[derive(Debug, From, PartialEq)] + enum Struct { + #[from] + Variant {}, + AutomaticallySkipped {}, + } + + #[test] + fn assert() { + assert_eq!(Unit::Variant, ().into()); + assert_eq!(Tuple::Variant(), ().into()); + assert_eq!(Struct::Variant {}, ().into()); + } + + mod r#const { + use super::*; + + #[derive(Debug, From, PartialEq)] + enum Unit { + #[from] + Variant, + } + + #[derive(Debug, From, PartialEq)] + enum Tuple { + #[from] + Variant(), + } + + #[derive(Debug, From, PartialEq)] + enum Struct { + #[from] + Variant {}, + } + + #[test] + fn assert() { + assert_eq!(Unit::<0>::Variant, ().into()); + assert_eq!(Tuple::<0>::Variant(), ().into()); + assert_eq!(Struct::<0>::Variant {}, ().into()); + } + } + } + } + + mod single_field_variant { + use super::*; + + #[derive(Debug, From, PartialEq)] + enum Enum { + Unnamed(i8), + Named { field: i16 }, + } + + #[test] + fn assert() { + assert_eq!(Enum::Unnamed(1), 1_i8.into()); + assert_eq!(Enum::Named { field: 1 }, 1_i16.into()); + } + + mod generic { + use super::*; + + #[derive(Debug, From, PartialEq)] + enum Tuple { + Variant(T), + } + + #[derive(Debug, From, PartialEq)] + enum Struct { + Variant { field: T }, + } + + #[test] + fn assert() { + assert_eq!(Tuple::Variant(1), 1.into()); + assert_eq!(Struct::Variant { field: 1 }, 1.into()); + } + + mod reference { + use super::*; + + #[derive(Debug, From, PartialEq)] + enum Tuple<'a, T> { + Variant(&'a T), + } + + #[derive(Debug, From, PartialEq)] + enum Struct<'a, T> { + Variant { field: &'a T }, + } + + #[test] + fn assert() { + assert_eq!(Tuple::Variant(&1), (&1).into()); + assert_eq!(Struct::Variant { field: &1 }, (&1).into()); + } + } + + mod indirect { + use super::*; + + #[derive(Debug, From, PartialEq)] + enum Tuple { + Variant(&'static T), + } + + #[derive(Debug, From, PartialEq)] + enum Struct { + Variant { field: &'static T }, + } + + #[test] + fn assert() { + assert_eq!(Tuple::Variant(&1), (&1).into()); + assert_eq!(Struct::Variant { field: &1 }, (&1).into()); + } + } + + mod bounded { + use super::*; + + #[derive(Debug, From, PartialEq)] + enum Tuple { + Variant(T), + } + + #[derive(Debug, From, PartialEq)] + enum Struct { + Variant { field: T }, + } + + #[test] + fn assert() { + assert_eq!(Tuple::Variant(1), 1.into()); + assert_eq!(Struct::Variant { field: 1 }, 1.into()); + } + } + + mod r#const { + use super::*; + + #[derive(Debug, From, PartialEq)] + enum Tuple { + Variant(T), + } + + #[derive(Debug, From, PartialEq)] + enum Struct { + Variant { field: T }, + } + + #[test] + fn assert() { + assert_eq!(Tuple::Variant::<_, 1>(1), 1.into()); + assert_eq!(Struct::Variant::<1, _> { field: 1 }, 1.into()); + } + } + } + + mod from { + use super::*; + + #[derive(Debug, From, PartialEq)] + enum Tuple { + #[from] + Variant(i8), + AutomaticallySkipped(i8), + } + + #[derive(Debug, From, PartialEq)] + enum Struct { + #[from] + Variant { + field: i8, + }, + AutomaticallySkipped { + field: i8, + }, + } + + #[test] + fn assert() { + assert_eq!(Tuple::Variant(1), 1_i8.into()); + assert_eq!(Struct::Variant { field: 1 }, 1_i8.into()); + } + } + + mod skip { + use super::*; + + #[derive(Debug, From, PartialEq)] + enum Enum { + Unnamed(i8), + #[from(skip)] + UnnamedSkipped(i8), + Named { + field: i16, + }, + #[from(skip)] + NamedSkipped { + field: i16, + }, + } + + #[test] + fn assert() { + assert_eq!(Enum::Unnamed(1), 1_i8.into()); + assert_eq!(Enum::Named { field: 1 }, 1_i16.into()); + } + + mod generic { + use super::*; + + #[derive(Debug, From, PartialEq)] + enum Tuple { + Variant(T), + #[from(skip)] + Skipped(T), + } + + #[derive(Debug, From, PartialEq)] + enum Struct { + Variant { + field: T, + }, + #[from(skip)] + Skipped { + field: T, + }, + } + + #[test] + fn assert() { + assert_eq!(Tuple::Variant(1), 1.into()); + assert_eq!(Struct::Variant { field: 1 }, 1.into()); + } + + mod reference { + use super::*; + + #[derive(Debug, From, PartialEq)] + enum Tuple<'a, T> { + Variant(&'a T), + #[from(skip)] + Skipped(&'a T), + } + + #[derive(Debug, From, PartialEq)] + enum Struct<'a, T> { + Variant { + field: &'a T, + }, + #[from(skip)] + Skipped { + field: &'a T, + }, + } + + #[test] + fn assert() { + assert_eq!(Tuple::Variant(&1), (&1).into()); + assert_eq!(Struct::Variant { field: &1 }, (&1).into()); + } + } + + mod indirect { + use super::*; + + #[derive(Debug, From, PartialEq)] + enum Tuple { + Variant(&'static T), + #[from(skip)] + Skipped(&'static T), + } + + #[derive(Debug, From, PartialEq)] + enum Struct { + Variant { + field: &'static T, + }, + #[from(skip)] + Skipped { + field: &'static T, + }, + } + + #[test] + fn assert() { + assert_eq!(Tuple::Variant(&1), (&1).into()); + assert_eq!(Struct::Variant { field: &1 }, (&1).into()); + } + } + + mod bounded { + use super::*; + + #[derive(Debug, From, PartialEq)] + enum Tuple { + Variant(T), + #[from(skip)] + Skipped(T), + } + + #[derive(Debug, From, PartialEq)] + enum Struct { + Variant { + field: T, + }, + #[from(skip)] + Skipped { + field: T, + }, + } + + #[test] + fn assert() { + assert_eq!(Tuple::Variant(1), 1.into()); + assert_eq!(Struct::Variant { field: 1 }, 1.into()); + } + } + + mod r#const { + use super::*; + + #[derive(Debug, From, PartialEq)] + enum Tuple { + Variant(T), + #[from(skip)] + Skipped(T), + } + + #[derive(Debug, From, PartialEq)] + enum Struct { + Variant { + field: T, + }, + #[from(skip)] + Skipped { + field: T, + }, + } + + #[test] + fn assert() { + assert_eq!(Tuple::Variant::<_, 1>(1), 1.into()); + assert_eq!(Struct::Variant::<1, _> { field: 1 }, 1.into()); + } + } + + mod concrete { + use super::*; + + #[derive(Debug, From, PartialEq)] + enum Tuple { + Variant(i32), + #[from(skip)] + Skipped(T), + } + + #[derive(Debug, From, PartialEq)] + enum Struct { + Variant { + field: i32, + }, + #[from(skip)] + Skipped { + field: T, + }, + } + + #[test] + fn assert() { + assert_eq!(Tuple::Variant::(1), 1.into()); + assert_eq!(Struct::Variant:: { field: 1 }, 1.into()); + } + + mod reference { + use super::*; + + #[derive(Debug, From, PartialEq)] + enum Tuple<'a, T> { + Variant(&'a i32), + #[from(skip)] + Skipped(&'a T), + } + + #[derive(Debug, From, PartialEq)] + enum Struct<'a, T> { + Variant { + field: &'a i32, + }, + #[from(skip)] + Skipped { + field: &'a T, + }, + } + + #[test] + fn assert() { + assert_eq!(Tuple::Variant::(&1), (&1).into()); + assert_eq!( + Struct::Variant:: { field: &1 }, + (&1).into() + ); + } + } + + mod indirect { + use super::*; + + #[derive(Debug, From, PartialEq)] + enum Tuple { + Variant(&'static i32), + #[from(skip)] + Skipped(&'static T), + } + + #[derive(Debug, From, PartialEq)] + enum Struct { + Variant { + field: &'static i32, + }, + #[from(skip)] + Skipped { + field: &'static T, + }, + } + + #[test] + fn assert() { + assert_eq!(Tuple::Variant::(&1), (&1).into()); + assert_eq!( + Struct::Variant:: { field: &1 }, + (&1).into() + ); + } + } + + mod bounded { + use super::*; + + #[derive(Debug, From, PartialEq)] + enum Tuple { + Variant(i32), + #[from(skip)] + Skipped(T), + } + + #[derive(Debug, From, PartialEq)] + enum Struct { + Variant { + field: i32, + }, + #[from(skip)] + Skipped { + field: T, + }, + } + + #[test] + fn assert() { + assert_eq!(Tuple::Variant::(1), 1.into()); + assert_eq!(Struct::Variant:: { field: 1 }, 1.into()); + } + } + + mod r#const { + use super::*; + + #[derive(Debug, From, PartialEq)] + enum Tuple { + Variant(i32), + #[from(skip)] + Skipped(T), + } + + #[derive(Debug, From, PartialEq)] + enum Struct { + Variant { + field: i32, + }, + #[from(skip)] + Skipped { + field: T, + }, + } + + #[test] + fn assert() { + assert_eq!(Tuple::Variant::(1), 1.into()); + assert_eq!( + Struct::Variant::<1, i32> { field: 1 }, + 1.into() + ); + } + } + } + } + } + + mod types { + use super::*; + + #[derive(Debug, From, PartialEq)] + enum Enum { + #[from(i8)] + Unnamed(i16), + #[from(i16)] + Named { + field: i32, + }, + AutomaticallySkipped(i32), + } + + #[test] + fn assert() { + assert_not_impl_any!(Enum: From); + assert_eq!(Enum::Unnamed(1), 1_i8.into()); + assert_eq!(Enum::Named { field: 1 }, 1_i16.into()); + } + } + + mod forward { + use super::*; + + #[derive(Debug, From, PartialEq)] + enum Unnamed { + #[from(forward)] + Variant(i32), + AutomaticallyIgnored(i32), + } + + #[derive(Debug, From, PartialEq)] + enum Named { + #[from(forward)] + Variant { + field: i32, + }, + AutomaticallyIgnored { + field: i32, + }, + } + + #[test] + fn assert() { + assert_eq!(Unnamed::Variant(1), 1_i8.into()); + assert_eq!(Unnamed::Variant(1), 1_i16.into()); + assert_eq!(Unnamed::Variant(1), 1_i32.into()); + assert_eq!(Named::Variant { field: 1 }, 1_i8.into()); + assert_eq!(Named::Variant { field: 1 }, 1_i16.into()); + assert_eq!(Named::Variant { field: 1 }, 1_i32.into()); + } + } + } + + mod multi_field_variant { + use super::*; + + #[derive(Debug, From, PartialEq)] + enum Enum { + Tuple(i8, i8), + Struct { field1: i16, field2: i16 }, + } + + #[test] + fn assert() { + assert_eq!(Enum::Tuple(0, 1), (0_i8, 1_i8).into()); + assert_eq!( + Enum::Struct { + field1: 0, + field2: 1 + }, + (0_i16, 1_i16).into(), + ); + } + + mod generic { + use super::*; + + #[derive(Debug, From, PartialEq)] + enum Tuple { + Variant(A, B), + } + + #[derive(Debug, From, PartialEq)] + enum Struct { + Variant { field1: A, field2: B }, + } + + #[test] + fn assert() { + assert_eq!(Tuple::Variant(1, 2_i16), (1, 2_i16).into()); + assert_eq!( + Struct::Variant { + field1: 1, + field2: 2_i16, + }, + (1, 2_i16).into(), + ); + } + + mod reference { + use super::*; + + #[derive(Debug, From, PartialEq)] + enum Tuple<'a, A, B> { + Variant(&'a A, &'a B), + } + + #[derive(Debug, From, PartialEq)] + enum Struct<'a, A, B> { + Variant { field1: &'a A, field2: &'a B }, + } + + #[test] + fn assert() { + assert_eq!(Tuple::Variant(&1, &2_i16), (&1, &2_i16).into()); + assert_eq!( + Struct::Variant { + field1: &1, + field2: &2_i16, + }, + (&1, &2_i16).into(), + ); + } + } + + mod indirect { + use super::*; + + #[derive(Debug, From, PartialEq)] + enum Tuple { + Variant(&'static A, &'static B), + } + + #[derive(Debug, From, PartialEq)] + enum Struct { + Variant { + field1: &'static A, + field2: &'static B, + }, + } + + #[test] + fn assert() { + assert_eq!(Tuple::Variant(&1, &2_i16), (&1, &2_i16).into()); + assert_eq!( + Struct::Variant { + field1: &1, + field2: &2_i16, + }, + (&1, &2_i16).into(), + ); + } + } + + mod bounded { + use super::*; + + #[derive(Debug, From, PartialEq)] + enum Tuple { + Variant(A, B), + } + + #[derive(Debug, From, PartialEq)] + enum Struct { + Variant { field1: A, field2: B }, + } + + #[test] + fn assert() { + assert_eq!(Tuple::Variant(1, 2_i16), (1, 2_i16).into()); + assert_eq!( + Struct::Variant { + field1: 1, + field2: 2_i16, + }, + (1, 2_i16).into(), + ); + } + } + + mod r#const { + use super::*; + + #[derive(Debug, From, PartialEq)] + enum Tuple { + Variant(A, B), + } + + #[derive(Debug, From, PartialEq)] + enum Struct { + Variant { field1: A, field2: B }, + } + + #[test] + fn assert() { + assert_eq!(Tuple::Variant::<0, _, _>(1, 2_i16), (1, 2_i16).into()); + assert_eq!( + Struct::<_, 1, _>::Variant { + field1: 1, + field2: 2_i16, + }, + (1, 2_i16).into(), + ); + } + } + } + + mod from { + use super::*; + + #[derive(Debug, From, PartialEq)] + enum Tuple { + #[from] + Variant(i8, i16), + AutomaticallySkipped(i8, i16), + } + + #[derive(Debug, From, PartialEq)] + enum Struct { + #[from] + Variant { + field1: i8, + field2: i16, + }, + AutomaticallySkipped { + field1: i8, + field2: i16, + }, + } + + #[test] + fn assert() { + assert_eq!(Tuple::Variant(1, 2), (1_i8, 2_i16).into()); + assert_eq!( + Struct::Variant { + field1: 1, + field2: 2, + }, + (1_i8, 2_i16).into(), + ); + } + } + + mod skip { + use super::*; + + #[derive(Debug, From, PartialEq)] + enum Enum { + Tuple(i8, i8), + #[from(skip)] + TupleSkipped(i8, i8), + Struct { + field1: i16, + field2: i16, + }, + #[from(skip)] + StructSkipped { + field1: i16, + field2: i16, + }, + } + + #[test] + fn assert() { + assert_eq!(Enum::Tuple(0, 1), (0_i8, 1_i8).into()); + assert_eq!( + Enum::Struct { + field1: 0, + field2: 1 + }, + (0_i16, 1_i16).into(), + ); + } + + mod generic { + use super::*; + + #[derive(Debug, From, PartialEq)] + enum Tuple { + Variant(A, B), + #[from(skip)] + Skipped(A, B), + } + + #[derive(Debug, From, PartialEq)] + enum Struct { + Variant { + field1: A, + field2: B, + }, + #[from(skip)] + Skipped { + field1: A, + field2: B, + }, + } + + #[test] + fn assert() { + assert_eq!(Tuple::Variant(1, 2_i16), (1, 2_i16).into()); + assert_eq!( + Struct::Variant { + field1: 1, + field2: 2_i16, + }, + (1, 2_i16).into(), + ); + } + + mod reference { + use super::*; + + #[derive(Debug, From, PartialEq)] + enum Tuple<'a, A, B> { + Variant(&'a A, &'a B), + #[from(skip)] + Skipped(&'a A, &'a B), + } + + #[derive(Debug, From, PartialEq)] + enum Struct<'a, A, B> { + Variant { + field1: &'a A, + field2: &'a B, + }, + #[from(skip)] + Skipped { + field1: &'a A, + field2: &'a B, + }, + } + + #[test] + fn assert() { + assert_eq!(Tuple::Variant(&1, &2_i16), (&1, &2_i16).into()); + assert_eq!( + Struct::Variant { + field1: &1, + field2: &2_i16, + }, + (&1, &2_i16).into(), + ); + } + } + + mod indirect { + use super::*; + + #[derive(Debug, From, PartialEq)] + enum Tuple { + Variant(&'static A, &'static B), + #[from(skip)] + Skipped(&'static A, &'static B), + } + + #[derive(Debug, From, PartialEq)] + enum Struct { + Variant { + field1: &'static A, + field2: &'static B, + }, + #[from(skip)] + Skipped { + field1: &'static A, + field2: &'static B, + }, + } + + #[test] + fn assert() { + assert_eq!(Tuple::Variant(&1, &2_i16), (&1, &2_i16).into()); + assert_eq!( + Struct::Variant { + field1: &1, + field2: &2_i16, + }, + (&1, &2_i16).into(), + ); + } + } + + mod bounded { + use super::*; + + #[derive(Debug, From, PartialEq)] + enum Tuple { + Variant(A, B), + #[from(skip)] + Skipped(A, B), + } + + #[derive(Debug, From, PartialEq)] + enum Struct { + Variant { + field1: A, + field2: B, + }, + #[from(skip)] + Skipped { + field1: A, + field2: B, + }, + } + + #[test] + fn assert() { + assert_eq!(Tuple::Variant(1, 2_i16), (1, 2_i16).into()); + assert_eq!( + Struct::Variant { + field1: 1, + field2: 2_i16, + }, + (1, 2_i16).into(), + ); + } + } + + mod r#const { + use super::*; + + #[derive(Debug, From, PartialEq)] + enum Tuple { + Variant(A, B), + #[from(skip)] + Skipped(A, B), + } + + #[derive(Debug, From, PartialEq)] + enum Struct { + Variant { + field1: A, + field2: B, + }, + #[from(skip)] + Skipped { + field1: A, + field2: B, + }, + } + + #[test] + fn assert() { + assert_eq!( + Tuple::Variant::<0, _, _>(1, 2_i16), + (1, 2_i16).into() + ); + assert_eq!( + Struct::<_, 1, _>::Variant { + field1: 1, + field2: 2_i16, + }, + (1, 2_i16).into(), + ); + } + } + + mod concrete { + use super::*; + + #[derive(Debug, From, PartialEq)] + enum Tuple { + Variant(i32, i16), + #[from(skip)] + Skipped(A, B), + } + + #[derive(Debug, From, PartialEq)] + enum Struct { + Variant { + field1: i32, + field2: i16, + }, + #[from(skip)] + Skipped { + field1: A, + field2: B, + }, + } + + #[test] + fn assert() { + assert_eq!( + Tuple::Variant::(1, 2_i16), + (1, 2_i16).into(), + ); + assert_eq!( + Struct::Variant:: { + field1: 1, + field2: 2_i16, + }, + (1, 2_i16).into(), + ); + } + + mod reference { + use super::*; + + #[derive(Debug, From, PartialEq)] + enum Tuple<'a, A, B> { + Variant(&'a i32, &'a i16), + #[from(skip)] + Skipped(&'a A, &'a B), + } + + #[derive(Debug, From, PartialEq)] + enum Struct<'a, A, B> { + Variant { + field1: &'a i32, + field2: &'a i16, + }, + #[from(skip)] + Skipped { + field1: &'a A, + field2: &'a B, + }, + } + + #[test] + fn assert() { + assert_eq!( + Tuple::Variant::(&1, &2_i16), + (&1, &2_i16).into(), + ); + assert_eq!( + Struct::Variant:: { + field1: &1, + field2: &2_i16, + }, + (&1, &2_i16).into(), + ); + } + } + + mod indirect { + use super::*; + + #[derive(Debug, From, PartialEq)] + enum Tuple { + Variant(&'static i32, &'static i16), + #[from(skip)] + Skipped(&'static A, &'static B), + } + + #[derive(Debug, From, PartialEq)] + enum Struct { + Variant { + field1: &'static i32, + field2: &'static i16, + }, + #[from(skip)] + Skipped { + field1: &'static A, + field2: &'static B, + }, + } + + #[test] + fn assert() { + assert_eq!( + Tuple::Variant::(&1, &2_i16), + (&1, &2_i16).into(), + ); + assert_eq!( + Struct::Variant:: { + field1: &1, + field2: &2_i16, + }, + (&1, &2_i16).into(), + ); + } + } + + mod bounded { + use super::*; + + #[derive(Debug, From, PartialEq)] + enum Tuple { + Variant(i32, i16), + #[from(skip)] + Skipped(A, B), + } + + #[derive(Debug, From, PartialEq)] + enum Struct { + Variant { + field1: i32, + field2: i16, + }, + #[from(skip)] + Skipped { + field1: A, + field2: B, + }, + } + + #[test] + fn assert() { + assert_eq!( + Tuple::Variant::(1, 2_i16), + (1, 2_i16).into(), + ); + assert_eq!( + Struct::Variant:: { + field1: 1, + field2: 2_i16, + }, + (1, 2_i16).into(), + ); + } + } + + mod r#const { + use super::*; + + #[derive(Debug, From, PartialEq)] + enum Tuple { + Variant(i32, i16), + #[from(skip)] + Skipped(A, B), + } + + #[derive(Debug, From, PartialEq)] + enum Struct { + Variant { + field1: i32, + field2: i16, + }, + #[from(skip)] + Skipped { + field1: A, + field2: B, + }, + } + + #[test] + fn assert() { + assert_eq!( + Tuple::Variant::<0, i32, i16>(1, 2_i16), + (1, 2_i16).into() + ); + assert_eq!( + Struct::::Variant { + field1: 1, + field2: 2_i16, + }, + (1, 2_i16).into(), + ); + } + } + } + } + } + + mod types { + use super::*; + + #[derive(Debug, From, PartialEq)] + enum Enum { + #[from((i8, i8))] + Tuple(i16, i16), + #[from((i16, i16))] + Struct { + field1: i32, + field2: i32, + }, + AutomaticallySkipped { + field1: i32, + field2: i32, + }, + } + + #[test] + fn assert() { + assert_not_impl_any!(Enum: From<(i32, i32)>); + assert_eq!(Enum::Tuple(0, 1), (0_i8, 1_i8).into()); + assert_eq!( + Enum::Struct { + field1: 0, + field2: 1 + }, + (0_i16, 1_i16).into(), + ); + } + } + + mod forward { + use super::*; + + #[derive(Debug, From, PartialEq)] + enum Unnamed { + #[from(forward)] + Variant(i16, i16), + AutomaticallyIgnored(i16, i16), + } + + #[derive(Debug, From, PartialEq)] + enum Named { + #[from(forward)] + Variant { + field1: i16, + field2: i16, + }, + AutomaticallyIgnored { + field1: i16, + field2: i16, + }, + } + + #[test] + fn assert() { + assert_eq!(Unnamed::Variant(0, 1), (0_i8, 1_i8).into()); + assert_eq!(Unnamed::Variant(0, 1), (0_i8, 1_i16).into()); + assert_eq!(Unnamed::Variant(0, 1), (0_i16, 1_i8).into()); + assert_eq!(Unnamed::Variant(0, 1), (0_i16, 1_i16).into()); + assert_eq!( + Named::Variant { + field1: 0, + field2: 1 + }, + (0_i8, 1_i8).into(), + ); + assert_eq!( + Named::Variant { + field1: 0, + field2: 1 + }, + (0_i8, 1_i16).into(), + ); + assert_eq!( + Named::Variant { + field1: 0, + field2: 1 + }, + (0_i16, 1_i8).into(), + ); + assert_eq!( + Named::Variant { + field1: 0, + field2: 1 + }, + (0_i16, 1_i16).into(), + ); + } + } + } }