diff --git a/CHANGELOG.md b/CHANGELOG.md index 413d1968..17eedee1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/). with an associated item called `Error` or `Err` respectively. ([#410](https://github.com/JelteF/derive_more/pull/410)) +### Added + +- Add support for custom types in `TryFrom`. So now you can use: + `#[try_from(T)]`, `#[try_from(T, E)]` and `#[try_from(T, E, ErrorValue)]` + alongside and not only `#[try_from(repr)]`. ## 1.0.0 - 2024-08-07 diff --git a/impl/doc/try_from.md b/impl/doc/try_from.md index b2b25b7a..6f90b136 100644 --- a/impl/doc/try_from.md +++ b/impl/doc/try_from.md @@ -7,9 +7,17 @@ Derive `TryFrom` allows you to convert enum discriminants into their correspondi ## Enums -By default, a `TryFrom` is generated, matching the [type of the discriminant](https://doc.rust-lang.org/reference/items/enumerations.html#discriminants). -The type can be changed with a `#[repr(u/i*)]` attribute, e.g., `#[repr(u8)]` or `#[repr(i32)]`. -Only field-less variants can be constructed from their variant, therefore the `TryFrom` implementation will return an error for a discriminant representing a variant with fields. +Enums can be generated either from a `repr` discriminant value or a custom type. + +### Repr + +In the `repr` mode, by default, a `TryFrom` is generated, matching the +[type of the +discriminant](https://doc.rust-lang.org/reference/items/enumerations.html#discriminants). +The type can be changed with a `#[repr(u/i*)]` attribute, e.g., `#[repr(u8)]` or +`#[repr(i32)]`. Only field-less variants can be constructed from their variant, +therefore the `TryFrom` implementation will return an error for a discriminant +representing a variant with fields. ```rust # use derive_more::TryFrom; @@ -31,3 +39,73 @@ assert_eq!(Enum::EmptySeven{}, Enum::try_from(7).unwrap()); // Variants with fields are not supported, as the value for their fields would be undefined. assert!(Enum::try_from(6).is_err()); ``` + +### Custom Types ("non-repr") + +Rather situationally, `TryFrom` can be implemented if all the variant types +have `TryFrom`. + +```rust +# use derive_more::TryFrom; +# +/// A custom error can be defined or not. +#[derive(Debug, PartialEq, Eq)] +enum Error { + FromEnum, + FromVariant, +} + +#[derive(Debug, PartialEq, Eq)] +struct F1; + +impl TryFrom for F1 { + type Error = Error; + + fn try_from(value: usize) -> Result { + if value == 1 { + return Ok(Self); + } + Err(Error::FromVariant) + } +} + +#[derive(Debug, PartialEq, Eq)] +struct F2; + +impl TryFrom for F2 { + type Error = Error; + + fn try_from(value: usize) -> Result { + if value == 2 { + return Ok(Self); + } + Err(Error::FromVariant) + } +} + +assert_eq!(Err(Error::FromVariant), F2::try_from(3)); + +#[derive(TryFrom, Debug, PartialEq, Eq)] +#[try_from( + // the type for which all variants have `TryFrom` + usize, + // optional: the error type (default is `()`). + Error, + // optional: the constructor of the type (optional if err is as `struct E;`) + Error::FromEnum +)] +enum Enum { + Field(F1), + Field2 { x: F2 }, +} + +assert_eq!(Enum::Field(F1), Enum::try_from(1).unwrap()); +assert_eq!(Enum::Field2 { x: F2 }, Enum::try_from(2).unwrap()); +assert_eq!(Err(Error::FromEnum), Enum::try_from(3)); +``` + +Multi-field variants as in `Enum::Field(F1, F2)` are also supported however may +rarely be used. + +Since `TryFrom for ()` is too universal, non-repr conversions do not support +enums with empty (unit or fieldless) variants. diff --git a/impl/src/try_from.rs b/impl/src/try_from.rs index 38654ec0..27caecfc 100644 --- a/impl/src/try_from.rs +++ b/impl/src/try_from.rs @@ -2,63 +2,232 @@ use proc_macro2::{Literal, TokenStream}; use quote::{format_ident, quote, ToTokens}; -use syn::spanned::Spanned as _; +use syn::{ + parse::{Parse, ParseStream}, + spanned::Spanned as _, + Token, +}; use crate::utils::{ - attr::{self, ParseMultiple as _}, + attr::{self, ParseMultiple}, Spanning, }; /// Expands a [`TryFrom`] derive macro. pub fn expand(input: &syn::DeriveInput, _: &'static str) -> syn::Result { - match &input.data { - syn::Data::Struct(data) => Err(syn::Error::new( - data.struct_token.span(), - "`TryFrom` cannot be derived for structs", - )), - syn::Data::Enum(data) => Ok(Expansion { - repr: attr::ReprInt::parse_attrs(&input.attrs, &format_ident!("repr"))? - .map(Spanning::into_inner) - .unwrap_or_default(), - attr: ItemAttribute::parse_attrs(&input.attrs, &format_ident!("try_from"))? - .map(|attr| { - if matches!(attr.item, ItemAttribute::Types(_)) { - Err(syn::Error::new( - attr.span, - "`#[try_from(repr(...))]` attribute is not supported yet", - )) - } else { - Ok(attr.item) - } - }) - .transpose()?, - ident: input.ident.clone(), - generics: input.generics.clone(), - variants: data.variants.clone().into_iter().collect(), + Ok(Expansion::expand(input)?.into_token_stream()) +} + +/// Optional Repr detail for conversion to a repr. +/// +/// Parsed with a single attr, provides a default int repr and if parsed with multiple attrs, +/// requires the `repr` explicitly. +struct ReprArgument { + /// Repr type to convert to. + repr: attr::ReprInt, + /// Repr argument given to the attribute. + conversion: attr::ReprConversion, +} + +impl Parse for ReprArgument { + /// Assuming default `#[repr]`, this parses the arguments of `#[try_from]` accepting `repr` only. + fn parse(input: ParseStream) -> syn::Result { + match input.parse()? { + attr::ReprConversion::Types(_) => Err(syn::Error::new( + input.span(), + "`#[{name}(repr(...))]` attribute is not supported yet", + )), + conversion => Ok(Self { + conversion, + repr: Default::default(), + }), } - .into_token_stream()), - syn::Data::Union(data) => Err(syn::Error::new( - data.union_token.span(), - "`TryFrom` cannot be derived for unions", - )), } } -/// Representation of a [`TryFrom`] derive macro struct item attribute. -/// -/// ```rust,ignore -/// #[try_from(repr)] -/// #[try_from(repr())] -/// ``` -type ItemAttribute = attr::ReprConversion; +impl ParseMultiple for ReprArgument { + fn parse_attrs( + attrs: impl AsRef<[syn::Attribute]>, + name: &syn::Ident, + ) -> syn::Result>> { + let Some(mut conv) = Self::parse_attrs_with(&attrs, name, &())? else { + return Ok(None); + }; + + // if a repr is given explicitly replace the values + if let Some(repr) = attr::ReprInt::parse_attrs(&attrs, &format_ident!("repr"))? + { + conv.repr = repr.item; + conv.span = conv.span.join(repr.span).unwrap_or(conv.span); + } + + Ok(Some(conv)) + } + + /// Use each field's `merge_attrs` for more specific error messages. + fn merge_attrs( + mut prev: Spanning, + new: Spanning, + name: &syn::Ident, + ) -> syn::Result> { + let prev_span = prev.span(); + let new_span = new.span(); + + prev.item.conversion = attr::ReprConversion::merge_attrs( + Spanning::new(prev.item.conversion, prev_span), + Spanning::new(new.item.conversion, new_span), + name, + )? + .item; + + prev.item.repr = attr::ReprInt::merge_attrs( + Spanning::new(prev.item.repr, prev_span), + Spanning::new(new.item.repr, new_span), + name, + )? + .item; + + prev.span = prev.span.join(new.span).unwrap_or(prev.span); + + Ok(prev) + } +} + +/// Inputs given to the macro when converting to a non-repr. +struct TypeArgument { + /// What will be the result of the conversion (`T` in `TryFrom`). + from_type: syn::Type, + /// The type of the error returned (`TryFrom::::Error`). + err_type: syn::Type, + /// The expression which creates the error. If not given, [`Self::err_type`] is used. + err_constructor: Option, +} + +impl TypeArgument { + /// Return a valid error constructor ready to be wrapped in an `Err`. + fn err_constructor(&self) -> TokenStream { + self.err_constructor + .as_ref() + .map(ToTokens::to_token_stream) + .unwrap_or_else(|| self.err_type.to_token_stream()) + } +} + +impl Parse for TypeArgument { + fn parse(input: ParseStream<'_>) -> syn::Result { + let from_type = input.parse()?; + + let err_type = if input.peek(Token![,]) { + input.parse::()?; + input.parse()? + } else { + // "()" + syn::Type::Tuple(syn::TypeTuple { + elems: Default::default(), + paren_token: Default::default(), + }) + }; + + let err_constructor = if input.peek(Token![,]) { + input.parse::()?; + Some(input.parse()?) + } else { + None + }; + + Ok(Self { + from_type, + err_type, + err_constructor, + }) + } +} + +struct Targets { + /// Implement `TryFrom` when a `#[repr(T)]` is given and input is `repr`. + /// + /// Can be only one for each enum. + repr: Option, + /// Implement `TryFrom` when a `T` is directly given via the input for every call. + /// + /// This is validated to be uniquely determinable. See `Self::are_fields_unqiue`. + types: Vec, +} + +impl Parse for Targets { + /// Parse a single `#[try_from]` either repr or type. + fn parse(input: ParseStream) -> syn::Result { + if input.cursor().ident().is_some_and(|(i, _)| i == "repr") { + return Ok(Self { + repr: Some(input.parse()?), + types: Default::default(), + }); + } + + Ok(Self { + types: vec![input.parse()?], + repr: Default::default(), + }) + } +} + +impl ParseMultiple for Targets { + // Try to parse normally (parse only try_from using the default parser) and add the related + // information from other attrs if relevatent. + fn parse_attrs( + attrs: impl AsRef<[syn::Attribute]>, + name: &syn::Ident, + ) -> syn::Result>> { + let mut candidate = Self::parse_attrs_with(&attrs, name, &())?; + + if let Some(ref mut target) = candidate { + if let Some(ref mut repr_arg) = target.repr { + // try to add the repr type to the repr arg if possible since parse ignores it. + // This is basically a shared behavior with the default parser of the `ReprArgument` + // type + if let Some(repr) = + attr::ReprInt::parse_attrs(&attrs, &format_ident!("repr"))? + { + repr_arg.repr = repr.item; + target.span = target.span.join(repr.span).unwrap_or(target.span); + } + } + } + + Ok(candidate) + } + + fn merge_attrs( + mut prev: Spanning, + mut new: Spanning, + name: &syn::Ident, + ) -> syn::Result> { + let prev_span = prev.span(); + let new_span = new.span(); + + prev.item.repr = match (prev.item.repr, new.item.repr) { + (Some(p), Some(n)) => Some( + ReprArgument::merge_attrs( + Spanning::new(p, prev_span), + Spanning::new(n, new_span), + name, + )? + .item, + ), + (Some(v), None) | (None, Some(v)) => Some(v), + (None, None) => None, + }; + + prev.item.types.append(&mut new.item.types); + + Ok(prev) + } +} /// Expansion of a macro for generating [`TryFrom`] implementation of an enum. struct Expansion { - /// `#[repr(u/i*)]` of the enum. - repr: attr::ReprInt, - - /// [`ItemAttribute`] of the enum. - attr: Option, + /// The `TryFrom` implementations. + targets: Option, /// [`syn::Ident`] of the enum. /// @@ -72,17 +241,139 @@ struct Expansion { variants: Vec, } -impl ToTokens for Expansion { - /// Expands [`TryFrom`] implementations for a struct. - fn to_tokens(&self, tokens: &mut TokenStream) { - if self.attr.is_none() { - return; +impl Expansion { + pub fn expand(input: &syn::DeriveInput) -> syn::Result { + match &input.data { + syn::Data::Struct(data) => Err(syn::Error::new( + data.struct_token.span(), + "`TryFrom` cannot be derived for structs", + )), + syn::Data::Enum(data) => { + let targets = + Targets::parse_attrs(&input.attrs, &format_ident!("try_from"))? + .map(Spanning::into_inner); + + let variants = data.variants.clone().into_iter().collect::>(); + + // When types are requested, the enum cannot have duplicate field types + if let Some(Targets { types, .. }) = &targets { + if !types.is_empty() { + // TODO collect errors and return for all units + for var in variants.iter() { + if matches!(var.fields, syn::Fields::Unit) { + return Err( + syn::Error::new( + var.span(), + "empty variant not supported when using non-repr `try_from`" + ) + ); + } + } + + Self::are_fields_unique(&variants)?; + } + } + + Ok(Expansion { + targets, + ident: input.ident.clone(), + generics: input.generics.clone(), + variants, + } + .into_token_stream()) + } + syn::Data::Union(data) => Err(syn::Error::new( + data.union_token.span(), + "`TryFrom` cannot be derived for unions", + )), } + } - let ident = &self.ident; + /// Try to convert every field of this variant in preserved order and return `Ok`. + /// + /// Generates a `if let Ok(v) = V::try_from(field1) { if ... { ... { return Ok(v); } }}`. + /// + /// Assumes the fields are already unique in any other case, generates error-prone code. + fn try_from_variant( + &self, + syn::Variant { + ident: var_ident, + fields, + .. + }: &syn::Variant, + ) -> TokenStream { + let enum_ident = &self.ident; + + let ok = quote! { derive_more::core::result::Result::Ok }; + + let bindings = (0..fields.len()).map(|i| format_ident!("__binding_{i}")); + + let bindings_types = bindings.clone().zip(match fields { + syn::Fields::Named(syn::FieldsNamed { named: fields, .. }) + | syn::Fields::Unnamed(syn::FieldsUnnamed { + unnamed: fields, .. + }) => fields.into_iter().map(|i| &i.ty), + syn::Fields::Unit => unreachable!("units are already filtered out"), + }); + + let nested_result = match fields { + syn::Fields::Unnamed(_) => quote! { + return #ok(#enum_ident::#var_ident(#(#bindings,)*)); + }, + syn::Fields::Named(syn::FieldsNamed { named, .. }) => { + let names = named.into_iter().map(|i| i.ident.as_ref().unwrap()); + quote! { return #ok(#enum_ident::#var_ident { #(#names: #bindings,)* }); } + } + syn::Fields::Unit => unreachable!("units are already filtered out"), + }; + + bindings_types + .rev() + .fold(nested_result, |tokens, (binding, ty)| { + quote! { + if let #ok(#binding) = + #ty::try_from(value) { + #tokens + } + } + }) + } + + /// Generate the `impl TryFrom for Ident where C: Criterion` for a given type. + fn generate_tokens( + &self, + from_type: &TokenStream, + err_type: &TokenStream, + body: &TokenStream, + ) -> TokenStream { let (impl_generics, ty_generics, where_clause) = self.generics.split_for_impl(); + let ident = &self.ident; + + quote! { + #[automatically_derived] + impl #impl_generics derive_more::core::convert::TryFrom<#from_type #ty_generics> + for #ident #where_clause { + type Error = #err_type; + + #[allow(non_upper_case_globals)] + #[inline] + fn try_from(value: #from_type) -> + derive_more::core::result::Result { + #body + } + } + } + } - let repr_ty = &self.repr.ty(); + /// If the try_from is done using a repr, expand the code. + fn repr_to_tokens(&self, tokens: &mut TokenStream) { + let Some(Targets { + repr: Some(arg), .. + }) = &self.targets + else { + return; + }; + let repr_ty = arg.repr.ty(); let mut last_discriminant = quote! { 0 }; let mut inc = 0usize; @@ -120,25 +411,120 @@ impl ToTokens for Expansion { .unzip(); let error = quote! { derive_more::TryFromReprError<#repr_ty> }; + let ident = &self.ident; + let body = quote! { + #( const #consts: #repr_ty = #discriminants; )* + match value { + #(#consts => derive_more::core::result::Result::Ok(#ident::#variants),)* + _ => derive_more::core::result::Result::Err( + derive_more::TryFromReprError::new(value) + ), + } + }; - quote! { - #[automatically_derived] - impl #impl_generics derive_more::core::convert::TryFrom<#repr_ty #ty_generics> - for #ident #where_clause { - type Error = #error; + self.generate_tokens(&repr_ty.to_token_stream(), &error, &body) + .to_tokens(tokens); + } - #[allow(non_upper_case_globals)] - #[inline] - fn try_from(val: #repr_ty) -> derive_more::core::result::Result { - #( const #consts: #repr_ty = #discriminants; )* - match val { - #(#consts => derive_more::core::result::Result::Ok(#ident::#variants),)* - _ => derive_more::core::result::Result::Err( - derive_more::TryFromReprError::new(val) + /// If the try_from is done using a type other than reprs, expand the code. + fn type_to_tokens(&self, tokens: &mut TokenStream) { + let Some(Targets { types, .. }) = &self.targets else { + return; + }; + if types.is_empty() { + return; + } + + // since the function is a trait/generic function a single body will do for every type in + // targets. + let body = + self.variants + .iter() + .fold(TokenStream::default(), |mut tokens, i| { + tokens.extend(self.try_from_variant(i)); + tokens + }); + + types + .iter() + .fold(TokenStream::default(), |mut tokens, args| { + let default_return = args.err_constructor(); + tokens.extend(self.generate_tokens( + &args.from_type.to_token_stream(), + &args.err_type.to_token_stream(), + "e! { + #body + derive_more::core::result::Result::Err(#default_return) + }, + )); + tokens + }) + .to_tokens(tokens); + } + + /// Return variant types in the order of declaration if all are unique else throws syn error. + /// + /// Checks for being unique in the order of declaration means the function checks if: + /// - order is unique meaning `E::T1(u32, u16)` is not the same as `E::T2(u16, u32)`. + /// - unit (empty variants) is, at most, provided only once (see [`syn::Fields::Unit`]). + /// - the type of fields in each variant (regardless of its type) is the same or not. In other + /// words, of [`syn::Fields`] types are checked, meaning `E::T1(u32, u16)` is the same as + /// `E::T2 { t1: u32, t2: u16 }`. + // + // "The complexity" factor is not taken into account since rarely enums have more than a few + // variants. So the most crude way to implement it yet, but it is preferred for simplicity and not + // using no_std. + // + // This could use a sorted approach (for example by doing ty.to_token_stream().to_string() for all), + // that is not used again for the same reason + fn are_fields_unique(variants: &[syn::Variant]) -> syn::Result<()> { + let mut unique_types = Vec::new(); + + for variant in variants.iter() { + // since the comparison function is string based and the ident field changes the + // representation, this conversion to all "unnamed" is required. + let types = match &variant.fields { + syn::Fields::Named(syn::FieldsNamed { named: fields, .. }) + | syn::Fields::Unnamed(syn::FieldsUnnamed { + unnamed: fields, .. + }) => fields + .into_iter() + .map(|i| { + let mut i = i.to_owned(); + i.ident = None; + i.colon_token = None; + i.to_token_stream().to_string() + ", " + }) + .collect(), + syn::Fields::Unit => String::new(), + }; + + match unique_types.iter().position(|i| i == &types) { + Some(dup_i) => { + return Err(syn::Error::new( + variant.fields.span(), + format!( + "`{}` types collection is used for more than one variant (`{}`, `{}`), \ + non-repr `try_from` cannot be implemented (try changing the order of \ + fields as a workaround)", + &types[..types.len().saturating_sub(2)], // remove trailing ", " + variants[dup_i].ident, + variant.ident, ), - } + )); } - } - }.to_tokens(tokens); + None => unique_types.push(types), + }; + } + + Ok(()) + } +} + +impl ToTokens for Expansion { + /// Expands [`TryFrom`] implementations for a struct. + fn to_tokens(&self, tokens: &mut TokenStream) { + self.repr_to_tokens(tokens); + self.type_to_tokens(tokens); } } diff --git a/tests/compile_fail/try_from/duplicate_empty_type.rs b/tests/compile_fail/try_from/duplicate_empty_type.rs new file mode 100644 index 00000000..f865a21b --- /dev/null +++ b/tests/compile_fail/try_from/duplicate_empty_type.rs @@ -0,0 +1,8 @@ +#[derive(derive_more::TryFrom)] +#[try_from(usize)] +enum Enum { + Field, + Field2, +} + +fn main() {} diff --git a/tests/compile_fail/try_from/duplicate_empty_type.stderr b/tests/compile_fail/try_from/duplicate_empty_type.stderr new file mode 100644 index 00000000..43c2392d --- /dev/null +++ b/tests/compile_fail/try_from/duplicate_empty_type.stderr @@ -0,0 +1,5 @@ +error: empty variant not supported when using non-repr `try_from` + --> tests/compile_fail/try_from/duplicate_empty_type.rs:4:5 + | +4 | Field, + | ^^^^^ diff --git a/tests/compile_fail/try_from/duplicate_mixed.rs b/tests/compile_fail/try_from/duplicate_mixed.rs new file mode 100644 index 00000000..8e7b6ca9 --- /dev/null +++ b/tests/compile_fail/try_from/duplicate_mixed.rs @@ -0,0 +1,10 @@ +struct F1; + +#[derive(derive_more::TryFrom)] +#[try_from(usize)] +enum Enum { + Field { x: F1 }, + Field2(F1), +} + +fn main() {} diff --git a/tests/compile_fail/try_from/duplicate_mixed.stderr b/tests/compile_fail/try_from/duplicate_mixed.stderr new file mode 100644 index 00000000..4eda191f --- /dev/null +++ b/tests/compile_fail/try_from/duplicate_mixed.stderr @@ -0,0 +1,5 @@ +error: `F1` types collection is used for more than one variant (`Field`, `Field2`), non-repr `try_from` cannot be implemented (try changing the order of fields as a workaround) + --> tests/compile_fail/try_from/duplicate_mixed.rs:7:11 + | +7 | Field2(F1), + | ^^^^ diff --git a/tests/compile_fail/try_from/duplicate_named.rs b/tests/compile_fail/try_from/duplicate_named.rs new file mode 100644 index 00000000..b964a98f --- /dev/null +++ b/tests/compile_fail/try_from/duplicate_named.rs @@ -0,0 +1,10 @@ +struct F1; + +#[derive(derive_more::TryFrom)] +#[try_from(usize)] +enum Enum { + Field { x: F1 }, + Field2 { y: F1 }, +} + +fn main() {} diff --git a/tests/compile_fail/try_from/duplicate_named.stderr b/tests/compile_fail/try_from/duplicate_named.stderr new file mode 100644 index 00000000..86f99465 --- /dev/null +++ b/tests/compile_fail/try_from/duplicate_named.stderr @@ -0,0 +1,5 @@ +error: `F1` types collection is used for more than one variant (`Field`, `Field2`), non-repr `try_from` cannot be implemented (try changing the order of fields as a workaround) + --> tests/compile_fail/try_from/duplicate_named.rs:7:12 + | +7 | Field2 { y: F1 }, + | ^^^^^^^^^ diff --git a/tests/compile_fail/try_from/duplicate_unnamed.rs b/tests/compile_fail/try_from/duplicate_unnamed.rs new file mode 100644 index 00000000..b482018e --- /dev/null +++ b/tests/compile_fail/try_from/duplicate_unnamed.rs @@ -0,0 +1,10 @@ +struct F1; + +#[derive(derive_more::TryFrom)] +#[try_from(usize)] +enum Enum { + Field(F1), + Field2(F1), +} + +fn main() {} diff --git a/tests/compile_fail/try_from/duplicate_unnamed.stderr b/tests/compile_fail/try_from/duplicate_unnamed.stderr new file mode 100644 index 00000000..1cd6b55b --- /dev/null +++ b/tests/compile_fail/try_from/duplicate_unnamed.stderr @@ -0,0 +1,5 @@ +error: `F1` types collection is used for more than one variant (`Field`, `Field2`), non-repr `try_from` cannot be implemented (try changing the order of fields as a workaround) + --> tests/compile_fail/try_from/duplicate_unnamed.rs:7:11 + | +7 | Field2(F1), + | ^^^^ diff --git a/tests/compile_fail/try_from/empty_type.rs b/tests/compile_fail/try_from/empty_type.rs new file mode 100644 index 00000000..259f4842 --- /dev/null +++ b/tests/compile_fail/try_from/empty_type.rs @@ -0,0 +1,7 @@ +#[derive(derive_more::TryFrom)] +#[try_from(usize)] +enum Enum { + Field, +} + +fn main() {} diff --git a/tests/compile_fail/try_from/empty_type.stderr b/tests/compile_fail/try_from/empty_type.stderr new file mode 100644 index 00000000..df279a45 --- /dev/null +++ b/tests/compile_fail/try_from/empty_type.stderr @@ -0,0 +1,5 @@ +error: empty variant not supported when using non-repr `try_from` + --> tests/compile_fail/try_from/empty_type.rs:4:5 + | +4 | Field, + | ^^^^^ diff --git a/tests/compile_fail/try_from/invalid_repr.stderr b/tests/compile_fail/try_from/invalid_repr.stderr index 90e7dc7f..d4078b51 100644 --- a/tests/compile_fail/try_from/invalid_repr.stderr +++ b/tests/compile_fail/try_from/invalid_repr.stderr @@ -1,9 +1,3 @@ -error: expected `,` - --> tests/compile_fail/try_from/invalid_repr.rs:2:10 - | -2 | #[repr(a + b)] - | ^ - error: expected one of `(`, `,`, `::`, or `=`, found `+` --> tests/compile_fail/try_from/invalid_repr.rs:2:10 | diff --git a/tests/try_from.rs b/tests/try_from.rs index 294e5b82..99d2afe8 100644 --- a/tests/try_from.rs +++ b/tests/try_from.rs @@ -85,3 +85,188 @@ fn test_discriminants_on_enum_with_fields() { assert!(Enum::try_from(-14).is_err()); assert_eq!(Enum::EmptyTuple(), Enum::try_from(-13).unwrap()); } + +#[test] +fn test_try_from_types() { + #[derive(Debug, PartialEq, Eq)] + struct F1; + + impl TryFrom for F1 { + type Error = (); + + fn try_from(value: usize) -> Result { + if value == 1 { + return Ok(Self); + } + Err(()) + } + } + + #[derive(Debug, PartialEq, Eq)] + struct F2; + + impl TryFrom for F2 { + type Error = (); + + fn try_from(value: usize) -> Result { + if value == 2 { + return Ok(Self); + } + Err(()) + } + } + + #[derive(TryFrom, Debug, PartialEq, Eq)] + #[try_from(usize)] + enum Enum { + Field(F1), + Field2 { x: F2 }, + } + + assert_eq!(Enum::Field(F1), Enum::try_from(1).unwrap()); + assert_eq!(Enum::Field2 { x: F2 }, Enum::try_from(2).unwrap()); + assert!(Enum::try_from(3).is_err()); +} + +#[test] +fn test_try_from_types_custom_unit_error() { + #[derive(Debug, PartialEq, Eq)] + struct Error; + + #[derive(Debug, PartialEq, Eq)] + struct F1; + + impl TryFrom for F1 { + type Error = Error; + + fn try_from(value: usize) -> Result { + if value == 1 { + return Ok(Self); + } + Err(Error) + } + } + + #[derive(Debug, PartialEq, Eq)] + struct F2; + + impl TryFrom for F2 { + type Error = Error; + + fn try_from(value: usize) -> Result { + if value == 2 { + return Ok(Self); + } + Err(Error) + } + } + + #[derive(TryFrom, Debug, PartialEq, Eq)] + #[try_from(usize, Error)] + enum Enum { + Field(F1), + Field2 { x: F2 }, + } + + assert_eq!(Enum::Field(F1), Enum::try_from(1).unwrap()); + assert_eq!(Enum::Field2 { x: F2 }, Enum::try_from(2).unwrap()); + assert_eq!(Err(Error), Enum::try_from(3)); +} + +#[test] +fn test_try_from_types_custom_error() { + #[derive(Debug, PartialEq, Eq)] + enum Error { + FromEnum, + FromVariant, + } + + #[derive(Debug, PartialEq, Eq)] + struct F1; + + impl TryFrom for F1 { + type Error = Error; + + fn try_from(value: usize) -> Result { + if value == 1 { + return Ok(Self); + } + Err(Error::FromVariant) + } + } + + #[derive(Debug, PartialEq, Eq)] + struct F2; + + impl TryFrom for F2 { + type Error = Error; + + fn try_from(value: usize) -> Result { + if value == 2 { + return Ok(Self); + } + Err(Error::FromVariant) + } + } + + assert_eq!(Err(Error::FromVariant), F2::try_from(3)); + + #[derive(TryFrom, Debug, PartialEq, Eq)] + #[try_from(usize, Error, Error::FromEnum)] + enum Enum { + Field(F1), + Field2 { x: F2 }, + } + + assert_eq!(Enum::Field(F1), Enum::try_from(1).unwrap()); + assert_eq!(Enum::Field2 { x: F2 }, Enum::try_from(2).unwrap()); + assert_eq!(Err(Error::FromEnum), Enum::try_from(3)); +} + +#[test] +fn test_try_from_multiple_types_custom_error() { + #[derive(Debug, PartialEq, Eq)] + enum Error { + FromEnum, + FromVariant, + } + + #[derive(Debug, PartialEq, Eq)] + struct F1; + + impl TryFrom for F1 { + type Error = Error; + + fn try_from(value: usize) -> Result { + if value == 1 { + return Ok(Self); + } + Err(Error::FromVariant) + } + } + + #[derive(Debug, PartialEq, Eq)] + struct F2; + + impl TryFrom for F2 { + type Error = Error; + + fn try_from(value: usize) -> Result { + if value == 1 { + return Ok(Self); + } + Err(Error::FromVariant) + } + } + + assert_eq!(Err(Error::FromVariant), F2::try_from(3)); + + #[derive(TryFrom, Debug, PartialEq, Eq)] + #[try_from(usize, Error, Error::FromEnum)] + enum Enum { + Field(F1, F2), + } + + assert_eq!(Enum::Field(F1, F2), Enum::try_from(1).unwrap()); + assert_eq!(Err(Error::FromEnum), Enum::try_from(2)); +}