diff --git a/README.md b/README.md index 4c5b76a..6617754 100644 --- a/README.md +++ b/README.md @@ -100,6 +100,7 @@ enum Target { use enum_convert::EnumFrom; enum Source { + Tuple(String, u8), Record { name: String, value: i32, @@ -109,6 +110,12 @@ enum Source { #[derive(EnumFrom)] #[enum_from(Source)] enum Target { + #[enum_from] + Tuple( + // We effectively re-order fields + #[enum_from(Source::Tuple.1)] u8, + #[enum_from(Source::Tuple.0)] String, + ), #[enum_from] Record { #[enum_from(Source::Record.name)] // Maps Source::Record.name to Target::Record.title diff --git a/src/enum_from/generator.rs b/src/enum_from/generator.rs index c662877..adebfd9 100644 --- a/src/enum_from/generator.rs +++ b/src/enum_from/generator.rs @@ -1,12 +1,14 @@ -use std::collections::HashMap; +use std::collections::{BTreeMap, HashMap}; use proc_macro2::{Span, TokenStream}; use quote::quote; use syn::{Fields, Variant}; use crate::{ - enum_from::parser::{ContainerAnnotation, FieldAnnotations, ParsedEnumFrom, VariantAnnotation}, - idents::{ContainerIdent, FieldIdent, VariantIdent}, + enum_from::parser::{ + ContainerAnnotation, FieldAnnotation, FieldAnnotations, ParsedEnumFrom, VariantAnnotation, + }, + idents::{ContainerIdent, FieldIdent, FieldRef, VariantIdent}, }; /// A struct holding all the data necessary to generate a TokenStream. @@ -19,9 +21,28 @@ pub struct EnumFromGenerator { struct VariantsMapping(HashMap); -struct VariantMapping { - target_variant: VariantIdent, - fields_mapping: HashMap, +enum VariantMapping { + Unit { + target_variant: VariantIdent, + }, + Tuple { + target_variant: VariantIdent, + fields_mapping: HashMap, + }, + Struct { + target_variant: VariantIdent, + fields_mapping: HashMap, + }, +} + +impl VariantMapping { + fn target_variant(&self) -> &VariantIdent { + match self { + VariantMapping::Unit { target_variant } => target_variant, + VariantMapping::Tuple { target_variant, .. } => target_variant, + VariantMapping::Struct { target_variant, .. } => target_variant, + } + } } impl EnumFromGenerator { @@ -49,22 +70,23 @@ fn generate_from_impl( target_enum: &ContainerIdent, target_variants: &HashMap, ) -> TokenStream { - let match_arms = variants_mapping - .0 - .into_iter() - .map(|(source_variant, variant_mapping)| { - let target_variant = target_variants.get(&variant_mapping.target_variant).expect( - "All target variants in variant_mapping should be present in target_variants", - ); - generate_match_arm( - source_variant, - variant_mapping, - &source_enum, - target_enum, - target_variant, - ) - }) - .collect::>(); + let match_arms = + variants_mapping + .0 + .into_iter() + .map(|(source_variant, variant_mapping)| { + let target_variant = target_variants.get(variant_mapping.target_variant()).expect( + "All target variants in variant_mapping should be present in target_variants", + ); + generate_match_arm( + source_variant, + variant_mapping, + &source_enum, + target_enum, + target_variant, + ) + }) + .collect::>(); quote! { impl From<#source_enum> for #target_enum { @@ -84,26 +106,41 @@ fn generate_match_arm( target_enum: &ContainerIdent, variant: &Variant, ) -> TokenStream { - let target_variant = &variant.ident; - - match &variant.fields { - Fields::Unit => quote! { - #source_enum::#source_variant => #target_enum::#target_variant, - }, - Fields::Unnamed(fields) => { - let field_names: Vec<_> = (0..fields.unnamed.len()) - .map(|i| quote::format_ident!("field_{}", i)) - .collect(); - let field_conversions: Vec<_> = field_names - .iter() - .map(|name| quote! { #name.into() }) - .collect(); + match (&variant.fields, variant_mapping) { + (Fields::Unit, VariantMapping::Unit { target_variant }) => { + quote! { #source_enum::#source_variant => #target_enum::#target_variant, } + } + ( + Fields::Unnamed(fields), + VariantMapping::Tuple { + target_variant, + fields_mapping, + }, + ) => { + let (source_fields, target_fields): (Vec<_>, Vec<_>) = (0..fields.unnamed.len()) + .map(|field_target_pos| { + let field_source_pos = fields_mapping + .get(&field_target_pos) + .unwrap_or(&field_target_pos); + let target_field_name = quote::format_ident!("field_{field_target_pos}"); + ( + quote::format_ident!("field_{field_source_pos}"), + quote! { #target_field_name.into() }, + ) + }) + .unzip(); quote! { - #source_enum::#source_variant(#(#field_names),*) => - #target_enum::#target_variant(#(#field_conversions),*), + #source_enum::#source_variant(#(#source_fields),*) => + #target_enum::#target_variant(#(#target_fields),*), } } - Fields::Named(fields) => { + ( + Fields::Named(fields), + VariantMapping::Struct { + target_variant, + fields_mapping, + }, + ) => { let (source_fields, target_fields): (Vec<_>, Vec<_>) = fields .named .iter() @@ -115,10 +152,7 @@ fn generate_match_arm( .expect("A named field should always have an ident") .clone(), ); - let source_field = &variant_mapping - .fields_mapping - .get(&target_field) - .unwrap_or(&target_field); + let source_field = &fields_mapping.get(&target_field).unwrap_or(&target_field); ( quote! { #source_field }, quote! { #target_field: #source_field.into() }, @@ -131,6 +165,7 @@ fn generate_match_arm( #target_enum::#target_variant { #(#target_fields),* }, } } + (_, _) => panic!("Unexpected mixing of variant types"), } } @@ -173,7 +208,7 @@ impl TryFrom for EnumFromGenerator { variant_annotation, )?; - let VariantsMapping(variant_mapping) = source_enums.get_mut(&source_enum).ok_or_else(|| { + let VariantsMapping(variants_mapping) = source_enums.get_mut(&source_enum).ok_or_else(|| { syn::Error::new( span, format!( @@ -187,14 +222,51 @@ impl TryFrom for EnumFromGenerator { &source_enum, &source_variant, )?; - - variant_mapping.insert( - source_variant, - VariantMapping { - target_variant: VariantIdent(target_variant.ident.clone()), - fields_mapping, + let fields = &target_variant.fields; + let target_variant = VariantIdent(target_variant.ident.clone()); + let variant_mapping = match fields { + Fields::Unit => VariantMapping::Unit { target_variant }, + Fields::Unnamed(_) => VariantMapping::Tuple { + target_variant, + fields_mapping: fields_mapping + .into_iter() + .map(|target_to_source| match target_to_source { + ( + FieldRef::FieldPos(target_pos), + FieldAnnotation { + source_field: FieldRef::FieldPos(source_pos), + .. + }, + ) => Ok((target_pos, source_pos)), + (_, FieldAnnotation { field_span, .. }) => Err(syn::Error::new( + field_span, + "Unexpected mapping to named field for tuple variant", + )), + }) + .collect::>()?, }, - ); + Fields::Named(_) => VariantMapping::Struct { + target_variant, + fields_mapping: fields_mapping + .into_iter() + .map(|target_to_source| match target_to_source { + ( + FieldRef::FieldIdent(target_ident), + FieldAnnotation { + source_field: FieldRef::FieldIdent(source_ident), + .. + }, + ) => Ok((target_ident, source_ident)), + (_, FieldAnnotation { field_span, .. }) => Err(syn::Error::new( + field_span, + "Unexpected mapping to positional field for struct variant", + )), + }) + .collect::>()?, + }, + }; + + variants_mapping.insert(source_variant, variant_mapping); } target_variants.insert(VariantIdent(target_variant.ident.clone()), target_variant); } @@ -208,13 +280,13 @@ impl TryFrom for EnumFromGenerator { } fn get_fields_mapping( - fields_annotations: &HashMap, + fields_annotations: &HashMap, source_enum: &ContainerIdent, source_variant: &VariantIdent, -) -> syn::Result> { +) -> syn::Result> { Ok(fields_annotations .iter() - .map(|(target_field, field_annotations)| { + .filter_map(|(target_field, field_annotations)| { let annotations = field_annotations .fields_annotations .iter() @@ -223,16 +295,14 @@ fn get_fields_mapping( && field_annotation.source_variant == *source_variant }) .collect::>(); - let source_field = match annotations.len() { - 0 => target_field.clone(), - 1 => annotations[0].source_field.clone(), - _ => Err(syn::Error::new( + match annotations.len() { + 0 => None, + 1 => Some(Ok((target_field.clone(), annotations[0].clone()))), + _ => Some(Err(syn::Error::new( field_annotations.field_span, format!("Multiple mapping found for source enum `{source_enum}`"), - ))?, - }; - - Ok((target_field.clone(), source_field)) + ))), + } }) .collect::>>()? .into_iter() diff --git a/src/enum_from/parser.rs b/src/enum_from/parser.rs index 33067b0..e22c0fa 100644 --- a/src/enum_from/parser.rs +++ b/src/enum_from/parser.rs @@ -3,13 +3,13 @@ use std::collections::HashMap; use proc_macro::TokenStream; use proc_macro2::Span; use syn::{ - Attribute, Data, DataEnum, DeriveInput, Field, Ident, Meta, Path, Token, Variant, + Attribute, Data, DataEnum, DeriveInput, Field, Ident, LitInt, Meta, Path, Token, Variant, parse::{Parse, ParseStream}, punctuated::Punctuated, spanned::Spanned, }; -use crate::idents::{ContainerIdent, FieldIdent, VariantIdent}; +use crate::idents::{ContainerIdent, FieldIdent, FieldRef, VariantIdent}; /// A "dumb" parser of the EnumFrom annotations /// There is no check of consistency between annotations here. @@ -47,7 +47,7 @@ pub struct ContainerAnnotation(pub ContainerIdent); pub struct VariantAnnotations { pub variant_annotations: Vec, - pub fields_annotations: HashMap, + pub fields_annotations: HashMap, } pub enum VariantAnnotation { @@ -94,10 +94,12 @@ pub struct FieldAnnotations { pub field_span: Span, } +#[derive(Clone)] pub struct FieldAnnotation { pub source_enum: ContainerIdent, pub source_variant: VariantIdent, - pub source_field: FieldIdent, + pub source_field: FieldRef, + pub field_span: Span, } impl Parse for FieldAnnotation { @@ -107,11 +109,22 @@ impl Parse for FieldAnnotation { let source_enum = ContainerIdent(path.segments[0].ident.clone()); let source_variant = VariantIdent(path.segments[1].ident.clone()); input.parse::()?; - let source_field = FieldIdent(input.parse()?); + let field_span = input.span(); + let source_field = if let Ok(ident) = input.parse::() { + FieldRef::FieldIdent(FieldIdent(ident)) + } else if let Ok(lit) = input.parse::() { + FieldRef::FieldPos(lit.base10_parse()?) + } else { + Err(syn::Error::new( + field_span, + "Expected either a field identifier or a field position", + ))? + }; Ok(FieldAnnotation { source_enum, source_variant, source_field, + field_span, }) } else { Err(syn::Error::new_spanned( @@ -212,11 +225,19 @@ fn extract_variant_annotations(variant: &Variant) -> syn::Result annotations.map(|field_annotations| { + ( + FieldRef::FieldIdent(FieldIdent(field_ident.clone())), + field_annotations, + ) + }), + None => annotations + .map(|field_annotations| (FieldRef::FieldPos(pos), field_annotations)), + } }) .collect::>>()? .into_iter() diff --git a/src/enum_into/generator.rs b/src/enum_into/generator.rs index 9017eef..71399b7 100644 --- a/src/enum_into/generator.rs +++ b/src/enum_into/generator.rs @@ -1,12 +1,14 @@ -use std::collections::HashMap; +use std::collections::{BTreeMap, HashMap}; use proc_macro2::{Span, TokenStream}; use quote::quote; use syn::{Fields, Variant}; use crate::{ - enum_into::parser::{ContainerAnnotation, FieldAnnotations, ParsedEnumInto, VariantAnnotation}, - idents::{ContainerIdent, FieldIdent, VariantIdent}, + enum_into::parser::{ + ContainerAnnotation, FieldAnnotation, FieldAnnotations, ParsedEnumInto, VariantAnnotation, + }, + idents::{ContainerIdent, FieldIdent, FieldRef, VariantIdent}, }; /// A struct holding all the data necessary to generate a TokenStream. @@ -19,9 +21,28 @@ pub struct EnumIntoGenerator { struct VariantsMapping(HashMap>); -struct VariantMapping { - source_variant: VariantIdent, - fields_mapping: HashMap, +enum VariantMapping { + Unit { + source_variant: VariantIdent, + }, + Tuple { + source_variant: VariantIdent, + fields_mapping: HashMap, + }, + Struct { + source_variant: VariantIdent, + fields_mapping: HashMap, + }, +} + +impl VariantMapping { + fn source_variant(&self) -> &VariantIdent { + match self { + VariantMapping::Unit { source_variant } => source_variant, + VariantMapping::Tuple { source_variant, .. } => source_variant, + VariantMapping::Struct { source_variant, .. } => source_variant, + } + } } impl EnumIntoGenerator { @@ -54,7 +75,7 @@ fn generate_from_impl( .into_iter() .flat_map(|(target_variant, variant_mappings)| { variant_mappings.into_iter().map(|variant_mapping| { - let source_variant = source_variants.get(&variant_mapping.source_variant).expect( + let source_variant = source_variants.get(variant_mapping.source_variant()).expect( "All source variants in variant_mapping should be present in source_variants", ); generate_match_arm( @@ -86,26 +107,41 @@ fn generate_match_arm( source_enum: &ContainerIdent, variant: &Variant, ) -> TokenStream { - let source_variant = &variant.ident; - - match &variant.fields { - Fields::Unit => quote! { + match (&variant.fields, variant_mapping) { + (Fields::Unit, VariantMapping::Unit { source_variant }) => quote! { #source_enum::#source_variant => #target_enum::#target_variant, }, - Fields::Unnamed(fields) => { - let field_names: Vec<_> = (0..fields.unnamed.len()) - .map(|i| quote::format_ident!("field_{}", i)) - .collect(); - let field_conversions: Vec<_> = field_names - .iter() - .map(|name| quote! { #name.into() }) - .collect(); + ( + Fields::Unnamed(fields), + VariantMapping::Tuple { + source_variant, + fields_mapping, + }, + ) => { + let (source_fields, target_fields): (Vec<_>, Vec<_>) = (0..fields.unnamed.len()) + .map(|field_source_pos| { + let field_target_pos = fields_mapping + .get(&field_source_pos) + .unwrap_or(&field_source_pos); + let target_field_name = quote::format_ident!("field_{field_target_pos}"); + ( + quote::format_ident!("field_{field_source_pos}"), + quote! { #target_field_name.into() }, + ) + }) + .unzip(); quote! { - #source_enum::#source_variant(#(#field_names),*) => - #target_enum::#target_variant(#(#field_conversions),*), + #source_enum::#source_variant(#(#source_fields),*) => + #target_enum::#target_variant(#(#target_fields),*), } } - Fields::Named(fields) => { + ( + Fields::Named(fields), + VariantMapping::Struct { + source_variant, + fields_mapping, + }, + ) => { let (source_fields, target_fields): (Vec<_>, Vec<_>) = fields .named .iter() @@ -117,10 +153,7 @@ fn generate_match_arm( .expect("A named field should always have an ident") .clone(), ); - let target_field = &variant_mapping - .fields_mapping - .get(&source_field) - .unwrap_or(&source_field); + let target_field = &fields_mapping.get(&source_field).unwrap_or(&source_field); ( quote! { #source_field }, quote! { #target_field: #source_field.into() }, @@ -133,6 +166,7 @@ fn generate_match_arm( #target_enum::#target_variant { #(#target_fields),* }, } } + (_, _) => panic!("Unexpected mixing of variant types"), } } @@ -188,14 +222,54 @@ impl TryFrom for EnumIntoGenerator { target_enum, &target_variant, )?; + let fields = &source_variant.fields; + let source_variant = VariantIdent(source_variant.ident.clone()); + let variant_mapping = match fields { + Fields::Unit => VariantMapping::Unit { source_variant }, + Fields::Unnamed(_) => VariantMapping::Tuple { + source_variant, + fields_mapping: fields_mapping + .into_iter() + .map(|source_to_target| match source_to_target { + ( + FieldRef::FieldPos(source_pos), + FieldAnnotation { + target_field: FieldRef::FieldPos(target_pos), + .. + }, + ) => Ok((source_pos, target_pos)), + (_, FieldAnnotation { field_span, .. }) => Err(syn::Error::new( + field_span, + "Unexpected mapping to named field for tuple variant", + )), + }) + .collect::>()?, + }, + Fields::Named(_) => VariantMapping::Struct { + source_variant, + fields_mapping: fields_mapping + .into_iter() + .map(|source_to_target| match source_to_target { + ( + FieldRef::FieldIdent(source_ident), + FieldAnnotation { + target_field: FieldRef::FieldIdent(target_ident), + .. + }, + ) => Ok((source_ident, target_ident)), + (_, FieldAnnotation { field_span, .. }) => Err(syn::Error::new( + field_span, + "Unexpected mapping to positional field for struct variant", + )), + }) + .collect::>()?, + }, + }; let mut variant_mappings = variants_mapping .remove(&target_variant) .unwrap_or_else(Vec::new); - variant_mappings.push(VariantMapping { - source_variant: VariantIdent(source_variant.ident.clone()), - fields_mapping, - }); + variant_mappings.push(variant_mapping); variants_mapping.insert(target_variant, variant_mappings); } @@ -220,13 +294,13 @@ impl TryFrom for EnumIntoGenerator { } fn get_fields_mapping( - fields_annotations: &HashMap, + fields_annotations: &HashMap, target_enum: &ContainerIdent, target_variant: &VariantIdent, -) -> syn::Result> { +) -> syn::Result> { Ok(fields_annotations .iter() - .map(|(source_field, field_annotations)| { + .filter_map(|(source_field, field_annotations)| { let annotations = field_annotations .fields_annotations .iter() @@ -235,16 +309,14 @@ fn get_fields_mapping( && field_annotation.target_variant == *target_variant }) .collect::>(); - let target_field = match annotations.len() { - 0 => source_field.clone(), - 1 => annotations[0].target_field.clone(), - _ => Err(syn::Error::new( + match annotations.len() { + 0 => None, + 1 => Some(Ok((source_field.clone(), annotations[0].clone()))), + _ => Some(Err(syn::Error::new( field_annotations.field_span, format!("Multiple mapping found for target enum `{target_enum}`"), - ))?, - }; - - Ok((source_field.clone(), target_field)) + ))), + } }) .collect::>>()? .into_iter() diff --git a/src/enum_into/parser.rs b/src/enum_into/parser.rs index 6e10cfe..9c00b50 100644 --- a/src/enum_into/parser.rs +++ b/src/enum_into/parser.rs @@ -3,13 +3,13 @@ use std::collections::HashMap; use proc_macro::TokenStream; use proc_macro2::Span; use syn::{ - Attribute, Data, DataEnum, DeriveInput, Field, Ident, Meta, Path, Token, Variant, + Attribute, Data, DataEnum, DeriveInput, Field, Ident, LitInt, Meta, Path, Token, Variant, parse::{Parse, ParseStream}, punctuated::Punctuated, spanned::Spanned, }; -use crate::idents::{ContainerIdent, FieldIdent, VariantIdent}; +use crate::idents::{ContainerIdent, FieldIdent, FieldRef, VariantIdent}; /// A "dumb" parser of the EnumInto annotations /// There is no check of consistency between annotations here. @@ -47,7 +47,7 @@ pub struct ContainerAnnotation(pub ContainerIdent); pub struct VariantAnnotations { pub variant_annotations: Vec, - pub fields_annotations: HashMap, + pub fields_annotations: HashMap, } pub enum VariantAnnotation { @@ -92,10 +92,12 @@ pub struct FieldAnnotations { pub field_span: Span, } +#[derive(Clone)] pub struct FieldAnnotation { pub target_enum: ContainerIdent, pub target_variant: VariantIdent, - pub target_field: FieldIdent, + pub target_field: FieldRef, + pub field_span: Span, } impl Parse for FieldAnnotation { @@ -105,11 +107,22 @@ impl Parse for FieldAnnotation { let target_enum = ContainerIdent(path.segments[0].ident.clone()); let target_variant = VariantIdent(path.segments[1].ident.clone()); input.parse::()?; - let target_field = FieldIdent(input.parse()?); + let field_span = input.span(); + let target_field = if let Ok(ident) = input.parse::() { + FieldRef::FieldIdent(FieldIdent(ident)) + } else if let Ok(lit) = input.parse::() { + FieldRef::FieldPos(lit.base10_parse()?) + } else { + Err(syn::Error::new( + field_span, + "Expected either a field identifier or a field position", + ))? + }; Ok(FieldAnnotation { target_enum, target_variant, target_field, + field_span, }) } else { Err(syn::Error::new_spanned( @@ -211,11 +224,19 @@ fn extract_variant_annotations(variant: &Variant) -> syn::Result annotations.map(|field_annotations| { + ( + FieldRef::FieldIdent(FieldIdent(field_ident.clone())), + field_annotations, + ) + }), + None => annotations + .map(|field_annotations| (FieldRef::FieldPos(pos), field_annotations)), + } }) .collect::>>()? .into_iter() diff --git a/src/idents.rs b/src/idents.rs index 6486fd8..d232f81 100644 --- a/src/idents.rs +++ b/src/idents.rs @@ -3,7 +3,7 @@ use std::fmt::Display; use quote::ToTokens; use syn::Ident; -#[derive(Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct ContainerIdent(pub Ident); impl Display for ContainerIdent { @@ -18,7 +18,7 @@ impl ToTokens for ContainerIdent { } } -#[derive(Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct VariantIdent(pub Ident); impl ToTokens for VariantIdent { @@ -27,11 +27,23 @@ impl ToTokens for VariantIdent { } } -#[derive(Clone, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct FieldIdent(pub Ident); +impl Display for FieldIdent { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.0.fmt(f) + } +} + impl ToTokens for FieldIdent { fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { self.0.to_tokens(tokens); } } + +#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub enum FieldRef { + FieldPos(usize), + FieldIdent(FieldIdent), +} diff --git a/src/lib.rs b/src/lib.rs index 625a44e..1dd9114 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -56,7 +56,7 @@ mod idents; /// /// enum FirstSource { /// Unit, -/// Tuple(i32, &'static str), +/// Tuple(&'static str, i32), /// DifferentName { /// alpha: f64, /// y: f64, @@ -75,7 +75,10 @@ mod idents; /// #[enum_from(FirstSource, SecondSource::Empty)] /// Unit, /// #[enum_from(FirstSource)] -/// Tuple(i64, String), +/// Tuple( +/// #[enum_from(FirstSource::Tuple.1)] i64, +/// #[enum_from(FirstSource::Tuple.0)] String, +/// ), /// #[enum_from(FirstSource::DifferentName, SecondSource)] /// Struct { /// #[enum_from(FirstSource::DifferentName.alpha, SecondSource::Struct.a)] @@ -96,7 +99,7 @@ mod idents; /// let target: Target = second_source.into(); /// assert!(matches!(target, Target::Unit)); /// -/// let first_source = FirstSource::Tuple(42, "hello"); +/// let first_source = FirstSource::Tuple("hello", 42); /// let target: Target = first_source.into(); /// assert!(matches!(target, Target::Tuple(42, ref s) if s == "hello")); /// @@ -166,6 +169,11 @@ pub fn derive_enum_from(input: TokenStream) -> TokenStream { /// #[enum_into(FirstTarget, SecondTarget)] /// enum Source { /// Unit, // Goes to both FirstTarget::Unit and SecondTarget::Unit +/// Tuple( +/// // Reorder fields for SecondTarget only +/// #[enum_into(SecondTarget::Tuple.1)] &'static str, +/// #[enum_into(SecondTarget::Tuple.0)] i32, +/// ), /// #[enum_into(FirstTarget::Data, SecondTarget::Info)] // Maps to different variants /// Record { /// #[enum_into(FirstTarget::Data.name, SecondTarget::Info.title)] // Maps fields differently @@ -176,11 +184,13 @@ pub fn derive_enum_from(input: TokenStream) -> TokenStream { /// /// enum FirstTarget { /// Unit, +/// Tuple(String, i32), /// Data { name: String, value: i64 } /// } /// /// enum SecondTarget { /// Unit, +/// Tuple(i32, String), /// Info { title: String, value: i64 } /// } /// @@ -193,6 +203,15 @@ pub fn derive_enum_from(input: TokenStream) -> TokenStream { /// let second_target: SecondTarget = source.into(); /// assert!(matches!(second_target, SecondTarget::Unit)); /// +/// let source = Source::Tuple("hello", 42); +/// let first_target: FirstTarget = source.into(); +/// assert!(matches!(first_target, FirstTarget::Tuple(ref s, value) if s == "hello" && value == 42)); +/// +/// // Source::Tuple can also go to SecondTarget::Into with different fields order +/// let source = Source::Tuple("hello", 42); +/// let second_target: SecondTarget = source.into(); +/// assert!(matches!(second_target, SecondTarget::Tuple(value, ref s) if s == "hello" && value == 42)); +/// /// let source = Source::Record { label: "test".to_string(), value: 42 }; /// let first_target: FirstTarget = source.into(); /// assert!(matches!(first_target, FirstTarget::Data { name, value } if name == "test" && value == 42)); diff --git a/tests/enum_from/compile_fail/field/mapping_struct_to_tuple_fields.rs b/tests/enum_from/compile_fail/field/mapping_struct_to_tuple_fields.rs new file mode 100644 index 0000000..66610b4 --- /dev/null +++ b/tests/enum_from/compile_fail/field/mapping_struct_to_tuple_fields.rs @@ -0,0 +1,20 @@ +use enum_convert::EnumFrom; + +enum Source { + Struct { + aa: i32, + bb: i32, + }, +} + +#[derive(EnumFrom)] +#[enum_from(Source)] +enum Target { + #[enum_from(Source::Struct)] + Tuple( + #[enum_from(Source::Struct.aa)] i32, + #[enum_from(Source::Struct.bb)] i32, + ), +} + +fn main() {} diff --git a/tests/enum_from/compile_fail/field/mapping_struct_to_tuple_fields.stderr b/tests/enum_from/compile_fail/field/mapping_struct_to_tuple_fields.stderr new file mode 100644 index 0000000..6b74638 --- /dev/null +++ b/tests/enum_from/compile_fail/field/mapping_struct_to_tuple_fields.stderr @@ -0,0 +1,5 @@ +error: Unexpected mapping to named field for tuple variant + --> tests/enum_from/compile_fail/field/mapping_struct_to_tuple_fields.rs:15:36 + | +15 | #[enum_from(Source::Struct.aa)] i32, + | ^^ diff --git a/tests/enum_from/compile_fail/field/mapping_tuple_to_struct_fields.rs b/tests/enum_from/compile_fail/field/mapping_tuple_to_struct_fields.rs new file mode 100644 index 0000000..bb762bb --- /dev/null +++ b/tests/enum_from/compile_fail/field/mapping_tuple_to_struct_fields.rs @@ -0,0 +1,19 @@ +use enum_convert::EnumFrom; + +enum Source { + Tuple(i32, i32), +} + +#[derive(EnumFrom)] +#[enum_from(Source)] +enum Target { + #[enum_from(Source::Tuple)] + Struct { + #[enum_from(Source::Tuple.0)] + a: i32, + #[enum_from(Source::Tuple.1)] + b: i32, + }, +} + +fn main() {} diff --git a/tests/enum_from/compile_fail/field/mapping_tuple_to_struct_fields.stderr b/tests/enum_from/compile_fail/field/mapping_tuple_to_struct_fields.stderr new file mode 100644 index 0000000..37180f3 --- /dev/null +++ b/tests/enum_from/compile_fail/field/mapping_tuple_to_struct_fields.stderr @@ -0,0 +1,5 @@ +error: Unexpected mapping to positional field for struct variant + --> tests/enum_from/compile_fail/field/mapping_tuple_to_struct_fields.rs:12:35 + | +12 | #[enum_from(Source::Tuple.0)] + | ^ diff --git a/tests/enum_from/compile_fail/field/mixing_source_variant_kind.rs b/tests/enum_from/compile_fail/field/mixing_source_variant_kind.rs new file mode 100644 index 0000000..58516fa --- /dev/null +++ b/tests/enum_from/compile_fail/field/mixing_source_variant_kind.rs @@ -0,0 +1,19 @@ +use enum_convert::EnumFrom; + +enum Source { + Struct { a: i32, b: i32 }, +} + +#[derive(EnumFrom)] +#[enum_from(Source)] +enum Target { + #[enum_from(Source::Tuple)] + Struct { + #[enum_from(Source::Tuple.0)] // .0 Does not make sense for a Struct source + a: i32, + #[enum_from(Source::Tuple.b)] + b: i32, + }, +} + +fn main() {} diff --git a/tests/enum_from/compile_fail/field/mixing_source_variant_kind.stderr b/tests/enum_from/compile_fail/field/mixing_source_variant_kind.stderr new file mode 100644 index 0000000..97a266c --- /dev/null +++ b/tests/enum_from/compile_fail/field/mixing_source_variant_kind.stderr @@ -0,0 +1,5 @@ +error: Unexpected mapping to positional field for struct variant + --> tests/enum_from/compile_fail/field/mixing_source_variant_kind.rs:12:35 + | +12 | #[enum_from(Source::Tuple.0)] // .0 Does not make sense for a Struct source + | ^ diff --git a/tests/enum_from/pass/field_rename.rs b/tests/enum_from/pass/field_rename.rs index 2106cae..db4a8cb 100644 --- a/tests/enum_from/pass/field_rename.rs +++ b/tests/enum_from/pass/field_rename.rs @@ -1,12 +1,18 @@ use enum_convert::EnumFrom; enum Source { + Tuple(i32, i32), Struct { x: i32, y: i32 }, } #[derive(EnumFrom)] #[enum_from(Source)] enum Target { + #[enum_from] + Tuple( + #[enum_from(Source::Tuple.1)] i32, + #[enum_from(Source::Tuple.0)] i32, + ), #[enum_from] Struct { #[enum_from(Source::Struct.x)] @@ -18,6 +24,10 @@ enum Target { } fn main() { + assert!(matches!( + Target::from(Source::Tuple(1, 2)), + Target::Tuple(a, b) if a == 2 && b == 1, + )); assert!(matches!( Target::from(Source::Struct { x: 1, y: 2}), Target::Struct { a, b } if a == 1 && b == 2, diff --git a/tests/enum_into/compile_fail/field/mapping_struct_to_tuple_fields.rs b/tests/enum_into/compile_fail/field/mapping_struct_to_tuple_fields.rs new file mode 100644 index 0000000..6854e93 --- /dev/null +++ b/tests/enum_into/compile_fail/field/mapping_struct_to_tuple_fields.rs @@ -0,0 +1,19 @@ +use enum_convert::EnumInto; + +#[derive(EnumInto)] +#[enum_into(Target)] +enum Source { + #[enum_into(Target::Tuple)] + Struct { + #[enum_into(Target::Tuple.0)] + a: i32, + #[enum_into(Target::Tuple.1)] + b: i32, + }, +} + +enum Target { + Tuple(i32, i32), +} + +fn main() {} diff --git a/tests/enum_into/compile_fail/field/mapping_struct_to_tuple_fields.stderr b/tests/enum_into/compile_fail/field/mapping_struct_to_tuple_fields.stderr new file mode 100644 index 0000000..e6993dc --- /dev/null +++ b/tests/enum_into/compile_fail/field/mapping_struct_to_tuple_fields.stderr @@ -0,0 +1,5 @@ +error: Unexpected mapping to positional field for struct variant + --> tests/enum_into/compile_fail/field/mapping_struct_to_tuple_fields.rs:8:35 + | +8 | #[enum_into(Target::Tuple.0)] + | ^ diff --git a/tests/enum_into/compile_fail/field/mapping_tuple_to_struct_fields.rs b/tests/enum_into/compile_fail/field/mapping_tuple_to_struct_fields.rs new file mode 100644 index 0000000..44dd08c --- /dev/null +++ b/tests/enum_into/compile_fail/field/mapping_tuple_to_struct_fields.rs @@ -0,0 +1,17 @@ +use enum_convert::EnumInto; + +#[derive(EnumInto)] +#[enum_into(Target)] +enum Source { + #[enum_into(Target::Struct)] + Tuple( + #[enum_into(Target::Struct.aa)] i32, + #[enum_into(Target::Struct.bb)] i32, + ), +} + +enum Target { + Struct { aa: i32, bb: i32 }, +} + +fn main() {} diff --git a/tests/enum_into/compile_fail/field/mapping_tuple_to_struct_fields.stderr b/tests/enum_into/compile_fail/field/mapping_tuple_to_struct_fields.stderr new file mode 100644 index 0000000..36ed48d --- /dev/null +++ b/tests/enum_into/compile_fail/field/mapping_tuple_to_struct_fields.stderr @@ -0,0 +1,5 @@ +error: Unexpected mapping to named field for tuple variant + --> tests/enum_into/compile_fail/field/mapping_tuple_to_struct_fields.rs:8:36 + | +8 | #[enum_into(Target::Struct.aa)] i32, + | ^^ diff --git a/tests/enum_into/compile_fail/field/mixing_target_variant_kind.rs b/tests/enum_into/compile_fail/field/mixing_target_variant_kind.rs new file mode 100644 index 0000000..de7d569 --- /dev/null +++ b/tests/enum_into/compile_fail/field/mixing_target_variant_kind.rs @@ -0,0 +1,19 @@ +use enum_convert::EnumInto; + +#[derive(EnumInto)] +#[enum_into(Target)] +enum Source { + #[enum_into(Target::Struct)] + Struct { + #[enum_into(Target::Struct.0)] // .0 Does not make sense for a Struct target + a: i32, + #[enum_into(Target::Struct.b)] + b: i32, + }, +} + +enum Target { + Struct { a: i32, b: i32 }, +} + +fn main() {} diff --git a/tests/enum_into/compile_fail/field/mixing_target_variant_kind.stderr b/tests/enum_into/compile_fail/field/mixing_target_variant_kind.stderr new file mode 100644 index 0000000..1b3f4d3 --- /dev/null +++ b/tests/enum_into/compile_fail/field/mixing_target_variant_kind.stderr @@ -0,0 +1,5 @@ +error: Unexpected mapping to positional field for struct variant + --> tests/enum_into/compile_fail/field/mixing_target_variant_kind.rs:8:36 + | +8 | #[enum_into(Target::Struct.0)] // .0 Does not make sense for a Struct target + | ^ diff --git a/tests/enum_into/pass/field_rename.rs b/tests/enum_into/pass/field_rename.rs index 233285f..ae23622 100644 --- a/tests/enum_into/pass/field_rename.rs +++ b/tests/enum_into/pass/field_rename.rs @@ -3,6 +3,10 @@ use enum_convert::EnumInto; #[derive(EnumInto)] #[enum_into(Target)] enum Source { + Tuple( + #[enum_into(Target::Tuple.1)] i32, + #[enum_into(Target::Tuple.0)] i32, + ), Struct { #[enum_into(Target::Struct.a)] x: i32, @@ -12,11 +16,16 @@ enum Source { } enum Target { + Tuple(i32, i32), Struct { a: i64, b: i64 }, Extra, } fn main() { + assert!(matches!( + Target::from(Source::Tuple(1, 2)), + Target::Tuple(a, b) if a == 2 && b == 1, + )); assert!(matches!( Target::from(Source::Struct { x: 1, y: 2}), Target::Struct { a, b } if a == 1 && b == 2,