diff --git a/src/enum_from/generator.rs b/src/enum_from/generator.rs index adebfd9..4e68d2e 100644 --- a/src/enum_from/generator.rs +++ b/src/enum_from/generator.rs @@ -200,7 +200,7 @@ impl TryFrom for EnumFromGenerator { }) .collect::>(); - for (target_variant, variant_annotations) in variants_annotations { + for (target_variant, mut variant_annotations) in variants_annotations { for variant_annotation in variant_annotations.variant_annotations { let (source_enum, source_variant, span) = get_source_enum_and_variant( &target_variant, @@ -217,8 +217,8 @@ impl TryFrom for EnumFromGenerator { ) })?; - let fields_mapping = get_fields_mapping( - &variant_annotations.fields_annotations, + let fields_annotations = extract_fields_annotations( + &mut variant_annotations.fields_annotations, &source_enum, &source_variant, )?; @@ -228,7 +228,7 @@ impl TryFrom for EnumFromGenerator { Fields::Unit => VariantMapping::Unit { target_variant }, Fields::Unnamed(_) => VariantMapping::Tuple { target_variant, - fields_mapping: fields_mapping + fields_mapping: fields_annotations .into_iter() .map(|target_to_source| match target_to_source { ( @@ -247,7 +247,7 @@ impl TryFrom for EnumFromGenerator { }, Fields::Named(_) => VariantMapping::Struct { target_variant, - fields_mapping: fields_mapping + fields_mapping: fields_annotations .into_iter() .map(|target_to_source| match target_to_source { ( @@ -268,6 +268,8 @@ impl TryFrom for EnumFromGenerator { variants_mapping.insert(source_variant, variant_mapping); } + + check_unused_fields_annotations(&source_enums, variant_annotations.fields_annotations)?; target_variants.insert(VariantIdent(target_variant.ident.clone()), target_variant); } @@ -279,29 +281,52 @@ impl TryFrom for EnumFromGenerator { } } -fn get_fields_mapping( - fields_annotations: &HashMap, +fn check_unused_fields_annotations( + source_enums: &HashMap, + fields_annotations: HashMap, +) -> syn::Result<()> { + for field_annotations in fields_annotations.into_values() { + for field_annotation in field_annotations.fields_annotations { + if source_enums.contains_key(&field_annotation.source_enum) { + Err(syn::Error::new( + field_annotation.variant_span, + "Field mapping for unexpected enum and variant combination", + ))? + } else { + Err(syn::Error::new( + field_annotation.enum_span, + "Field mapping for unknown enum", + ))? + } + } + } + + Ok(()) +} + +fn extract_fields_annotations( + fields_annotations: &mut HashMap, source_enum: &ContainerIdent, source_variant: &VariantIdent, ) -> syn::Result> { Ok(fields_annotations - .iter() + .iter_mut() .filter_map(|(target_field, field_annotations)| { - let annotations = field_annotations + let mut annotations = field_annotations .fields_annotations - .iter() - .filter(|field_annotation| { + .extract_if(.., |field_annotation| { field_annotation.source_enum == *source_enum && field_annotation.source_variant == *source_variant }) .collect::>(); - match annotations.len() { - 0 => None, - 1 => Some(Ok((target_field.clone(), annotations[0].clone()))), - _ => Some(Err(syn::Error::new( + let annotation = annotations.pop(); + if annotations.pop().is_some() { + Some(Err(syn::Error::new( field_annotations.field_span, format!("Multiple mapping found for source enum `{source_enum}`"), - ))), + ))) + } else { + annotation.map(|annotation| Ok((target_field.clone(), annotation))) } }) .collect::>>()? diff --git a/src/enum_from/parser.rs b/src/enum_from/parser.rs index e22c0fa..1b043d1 100644 --- a/src/enum_from/parser.rs +++ b/src/enum_from/parser.rs @@ -99,15 +99,15 @@ pub struct FieldAnnotation { pub source_enum: ContainerIdent, pub source_variant: VariantIdent, pub source_field: FieldRef, + pub enum_span: Span, + pub variant_span: Span, pub field_span: Span, } impl Parse for FieldAnnotation { fn parse(input: ParseStream) -> syn::Result { - let path: Path = input.parse()?; + let mut path: Path = input.parse()?; if path.segments.len() == 2 { - let source_enum = ContainerIdent(path.segments[0].ident.clone()); - let source_variant = VariantIdent(path.segments[1].ident.clone()); input.parse::()?; let field_span = input.span(); let source_field = if let Ok(ident) = input.parse::() { @@ -120,11 +120,15 @@ impl Parse for FieldAnnotation { "Expected either a field identifier or a field position", ))? }; + let variant_segment = path.segments.pop().unwrap().into_value(); + let enum_segment = path.segments.pop().unwrap().into_value(); Ok(FieldAnnotation { - source_enum, - source_variant, - source_field, + enum_span: enum_segment.span(), + variant_span: variant_segment.span(), field_span, + source_enum: ContainerIdent(enum_segment.ident), + source_variant: VariantIdent(variant_segment.ident), + source_field, }) } else { Err(syn::Error::new_spanned( diff --git a/src/enum_into/generator.rs b/src/enum_into/generator.rs index 71399b7..547a677 100644 --- a/src/enum_into/generator.rs +++ b/src/enum_into/generator.rs @@ -194,7 +194,7 @@ impl TryFrom for EnumIntoGenerator { .map(|ContainerAnnotation(target_enum)| (target_enum, VariantsMapping(HashMap::new()))) .collect::>(); - for (source_variant, variant_annotations) in variants_annotations { + for (source_variant, mut variant_annotations) in variants_annotations { let mut target_variants = variant_annotations .variant_annotations .into_iter() @@ -217,8 +217,8 @@ impl TryFrom for EnumIntoGenerator { .map(|(target_variant, _span)| target_variant) .unwrap_or_else(|| VariantIdent(source_variant.ident.clone())); - let fields_mapping = get_fields_mapping( - &variant_annotations.fields_annotations, + let fields_annotations = extract_fields_annotations( + &mut variant_annotations.fields_annotations, target_enum, &target_variant, )?; @@ -228,7 +228,7 @@ impl TryFrom for EnumIntoGenerator { Fields::Unit => VariantMapping::Unit { source_variant }, Fields::Unnamed(_) => VariantMapping::Tuple { source_variant, - fields_mapping: fields_mapping + fields_mapping: fields_annotations .into_iter() .map(|source_to_target| match source_to_target { ( @@ -247,7 +247,7 @@ impl TryFrom for EnumIntoGenerator { }, Fields::Named(_) => VariantMapping::Struct { source_variant, - fields_mapping: fields_mapping + fields_mapping: fields_annotations .into_iter() .map(|source_to_target| match source_to_target { ( @@ -273,16 +273,11 @@ impl TryFrom for EnumIntoGenerator { variants_mapping.insert(target_variant, variant_mappings); } - source_variants.insert(VariantIdent(source_variant.ident.clone()), source_variant); - for (target_enum, (_, span)) in target_variants { - Err(syn::Error::new( - span, - format!( - "target enum `{target_enum}` is not specified in this enum's #[enum_into] annotation" - ), - ))? - } + check_unused_variants_annotations(target_variants)?; + check_unused_fields_annotations(&target_enums, variant_annotations.fields_annotations)?; + + source_variants.insert(VariantIdent(source_variant.ident.clone()), source_variant); } Ok(EnumIntoGenerator { @@ -293,29 +288,66 @@ impl TryFrom for EnumIntoGenerator { } } -fn get_fields_mapping( - fields_annotations: &HashMap, +fn check_unused_variants_annotations( + target_variants: HashMap, +) -> syn::Result<()> { + for (target_enum, (_, span)) in target_variants { + Err(syn::Error::new( + span, + format!( + "target enum `{target_enum}` is not specified in this enum's #[enum_into] annotation" + ), + ))? + } + Ok(()) +} + +fn check_unused_fields_annotations( + target_enums: &HashMap, + fields_annotations: HashMap, +) -> syn::Result<()> { + for field_annotations in fields_annotations.into_values() { + for field_annotation in field_annotations.fields_annotations { + if target_enums.contains_key(&field_annotation.target_enum) { + Err(syn::Error::new( + field_annotation.variant_span, + "Field mapping for unexpected enum and variant combination", + ))? + } else { + Err(syn::Error::new( + field_annotation.enum_span, + "Field mapping for unknown enum", + ))? + } + } + } + + Ok(()) +} + +fn extract_fields_annotations( + fields_annotations: &mut HashMap, target_enum: &ContainerIdent, target_variant: &VariantIdent, ) -> syn::Result> { Ok(fields_annotations - .iter() + .iter_mut() .filter_map(|(source_field, field_annotations)| { - let annotations = field_annotations + let mut annotations = field_annotations .fields_annotations - .iter() - .filter(|field_annotation| { + .extract_if(.., |field_annotation| { field_annotation.target_enum == *target_enum && field_annotation.target_variant == *target_variant }) .collect::>(); - match annotations.len() { - 0 => None, - 1 => Some(Ok((source_field.clone(), annotations[0].clone()))), - _ => Some(Err(syn::Error::new( + let annotation = annotations.pop(); + if annotations.pop().is_some() { + Some(Err(syn::Error::new( field_annotations.field_span, format!("Multiple mapping found for target enum `{target_enum}`"), - ))), + ))) + } else { + annotation.map(|annotation| Ok((source_field.clone(), annotation))) } }) .collect::>>()? diff --git a/src/enum_into/parser.rs b/src/enum_into/parser.rs index 9c00b50..b3b9baa 100644 --- a/src/enum_into/parser.rs +++ b/src/enum_into/parser.rs @@ -97,15 +97,15 @@ pub struct FieldAnnotation { pub target_enum: ContainerIdent, pub target_variant: VariantIdent, pub target_field: FieldRef, + pub enum_span: Span, + pub variant_span: Span, pub field_span: Span, } impl Parse for FieldAnnotation { fn parse(input: ParseStream) -> syn::Result { - let path: Path = input.parse()?; + let mut path: Path = input.parse()?; if path.segments.len() == 2 { - let target_enum = ContainerIdent(path.segments[0].ident.clone()); - let target_variant = VariantIdent(path.segments[1].ident.clone()); input.parse::()?; let field_span = input.span(); let target_field = if let Ok(ident) = input.parse::() { @@ -118,11 +118,15 @@ impl Parse for FieldAnnotation { "Expected either a field identifier or a field position", ))? }; + let variant_segment = path.segments.pop().unwrap().into_value(); + let enum_segment = path.segments.pop().unwrap().into_value(); Ok(FieldAnnotation { - target_enum, - target_variant, - target_field, + enum_span: enum_segment.span(), + variant_span: variant_segment.span(), field_span, + target_enum: ContainerIdent(enum_segment.ident), + target_variant: VariantIdent(variant_segment.ident), + target_field, }) } else { Err(syn::Error::new_spanned( diff --git a/tests/enum_from/compile_fail/field/invalid_attribute_syntax.rs b/tests/enum_from/compile_fail/field/invalid_attribute_syntax.rs index c0f82d2..bdb7380 100644 --- a/tests/enum_from/compile_fail/field/invalid_attribute_syntax.rs +++ b/tests/enum_from/compile_fail/field/invalid_attribute_syntax.rs @@ -9,7 +9,7 @@ enum Source { enum Target { #[enum_from] Struct { - #[enum_from(Source.x)] // Should be #[enum_from(Source::Struct.x)] + #[enum_from(Source::Struct::x)] // Should be #[enum_from(Source::Struct.x)] a: i32, }, } diff --git a/tests/enum_from/compile_fail/field/invalid_attribute_syntax.stderr b/tests/enum_from/compile_fail/field/invalid_attribute_syntax.stderr index f7be91a..94ba817 100644 --- a/tests/enum_from/compile_fail/field/invalid_attribute_syntax.stderr +++ b/tests/enum_from/compile_fail/field/invalid_attribute_syntax.stderr @@ -1,5 +1,5 @@ error: Expected SourceEnum::SourceVariant.field_name --> tests/enum_from/compile_fail/field/invalid_attribute_syntax.rs:12:21 | -12 | #[enum_from(Source.x)] // Should be #[enum_from(Source::Struct.x)] - | ^^^^^^ +12 | #[enum_from(Source::Struct::x)] // Should be #[enum_from(Source::Struct.x)] + | ^^^^^^^^^^^^^^^^^ diff --git a/tests/enum_from/compile_fail/field/invalid_source_enum.rs b/tests/enum_from/compile_fail/field/invalid_source_enum.rs new file mode 100644 index 0000000..e54ef36 --- /dev/null +++ b/tests/enum_from/compile_fail/field/invalid_source_enum.rs @@ -0,0 +1,18 @@ +use enum_convert::EnumFrom; + +enum Source { + Struct { x: i32 }, +} + +#[derive(EnumFrom)] +#[enum_from(Source)] +enum Target { + #[enum_from] + Struct { + // Should be #[enum_from(Source::Struct.x)] + #[enum_from(NonExistent::Struct.x)] + a: i32, + }, +} + +fn main() {} diff --git a/tests/enum_from/compile_fail/field/invalid_source_enum.stderr b/tests/enum_from/compile_fail/field/invalid_source_enum.stderr new file mode 100644 index 0000000..edc171f --- /dev/null +++ b/tests/enum_from/compile_fail/field/invalid_source_enum.stderr @@ -0,0 +1,5 @@ +error: Field mapping for unknown enum + --> tests/enum_from/compile_fail/field/invalid_source_enum.rs:13:21 + | +13 | #[enum_from(NonExistent::Struct.x)] + | ^^^^^^^^^^^ diff --git a/tests/enum_from/compile_fail/field/invalid_source_enum_variant.rs b/tests/enum_from/compile_fail/field/invalid_source_enum_variant.rs new file mode 100644 index 0000000..0ea5e06 --- /dev/null +++ b/tests/enum_from/compile_fail/field/invalid_source_enum_variant.rs @@ -0,0 +1,18 @@ +use enum_convert::EnumFrom; + +enum Source { + Struct { x: i32 }, +} + +#[derive(EnumFrom)] +#[enum_from(Source)] +enum Target { + #[enum_from] + Struct { + // Should be #[enum_from(Source::Struct.x)] + #[enum_from(Source::NonExistent.x)] + a: i32, + }, +} + +fn main() {} diff --git a/tests/enum_from/compile_fail/field/invalid_source_enum_variant.stderr b/tests/enum_from/compile_fail/field/invalid_source_enum_variant.stderr new file mode 100644 index 0000000..d533455 --- /dev/null +++ b/tests/enum_from/compile_fail/field/invalid_source_enum_variant.stderr @@ -0,0 +1,5 @@ +error: Field mapping for unexpected enum and variant combination + --> tests/enum_from/compile_fail/field/invalid_source_enum_variant.rs:13:29 + | +13 | #[enum_from(Source::NonExistent.x)] + | ^^^^^^^^^^^ 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 index 66610b4..d011958 100644 --- 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 @@ -1,10 +1,7 @@ use enum_convert::EnumFrom; enum Source { - Struct { - aa: i32, - bb: i32, - }, + Struct { aa: i32, bb: i32 }, } #[derive(EnumFrom)] 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 index 6b74638..838a5da 100644 --- 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 @@ -1,5 +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 + --> tests/enum_from/compile_fail/field/mapping_struct_to_tuple_fields.rs:12:36 | -15 | #[enum_from(Source::Struct.aa)] i32, +12 | #[enum_from(Source::Struct.aa)] i32, | ^^ diff --git a/tests/enum_into/compile_fail/field/invalid_attribute_syntax.rs b/tests/enum_into/compile_fail/field/invalid_attribute_syntax.rs index 23e2cfd..9e550c9 100644 --- a/tests/enum_into/compile_fail/field/invalid_attribute_syntax.rs +++ b/tests/enum_into/compile_fail/field/invalid_attribute_syntax.rs @@ -4,7 +4,7 @@ use enum_convert::EnumInto; #[enum_into(Target)] enum Source { Struct { - #[enum_into(Target.a)] // Should be #[enum_into(Target::Struct.a)] + #[enum_into(Target::Struct::a)] // Should be #[enum_into(Target::Struct.a)] x: i32, }, } diff --git a/tests/enum_into/compile_fail/field/invalid_attribute_syntax.stderr b/tests/enum_into/compile_fail/field/invalid_attribute_syntax.stderr index 5907dad..ed8b6c0 100644 --- a/tests/enum_into/compile_fail/field/invalid_attribute_syntax.stderr +++ b/tests/enum_into/compile_fail/field/invalid_attribute_syntax.stderr @@ -1,5 +1,5 @@ error: Expected TargetEnum::TargetVariant.field_name --> tests/enum_into/compile_fail/field/invalid_attribute_syntax.rs:7:21 | -7 | #[enum_into(Target.a)] // Should be #[enum_into(Target::Struct.a)] - | ^^^^^^ +7 | #[enum_into(Target::Struct::a)] // Should be #[enum_into(Target::Struct.a)] + | ^^^^^^^^^^^^^^^^^ diff --git a/tests/enum_into/compile_fail/field/invalid_target_enum.rs b/tests/enum_into/compile_fail/field/invalid_target_enum.rs new file mode 100644 index 0000000..d13d4ba --- /dev/null +++ b/tests/enum_into/compile_fail/field/invalid_target_enum.rs @@ -0,0 +1,17 @@ +use enum_convert::EnumInto; + +#[derive(EnumInto)] +#[enum_into(Target)] +enum Source { + Struct { + // Should be #[enum_into(Target::Struct.a)] + #[enum_into(NonExistent::Struct.a)] + x: i32, + }, +} + +enum Target { + Struct { a: i32 }, +} + +fn main() {} diff --git a/tests/enum_into/compile_fail/field/invalid_target_enum.stderr b/tests/enum_into/compile_fail/field/invalid_target_enum.stderr new file mode 100644 index 0000000..5fd3755 --- /dev/null +++ b/tests/enum_into/compile_fail/field/invalid_target_enum.stderr @@ -0,0 +1,5 @@ +error: Field mapping for unknown enum + --> tests/enum_into/compile_fail/field/invalid_target_enum.rs:8:21 + | +8 | #[enum_into(NonExistent::Struct.a)] + | ^^^^^^^^^^^ diff --git a/tests/enum_into/compile_fail/field/invalid_target_enum_variant.rs b/tests/enum_into/compile_fail/field/invalid_target_enum_variant.rs new file mode 100644 index 0000000..85b0c7e --- /dev/null +++ b/tests/enum_into/compile_fail/field/invalid_target_enum_variant.rs @@ -0,0 +1,17 @@ +use enum_convert::EnumInto; + +#[derive(EnumInto)] +#[enum_into(Target)] +enum Source { + Struct { + // Should be #[enum_into(Target::Struct.a)] + #[enum_into(Target::NonExistent.a)] + x: i32, + }, +} + +enum Target { + Struct { a: i32 }, +} + +fn main() {} diff --git a/tests/enum_into/compile_fail/field/invalid_target_enum_variant.stderr b/tests/enum_into/compile_fail/field/invalid_target_enum_variant.stderr new file mode 100644 index 0000000..386f0b7 --- /dev/null +++ b/tests/enum_into/compile_fail/field/invalid_target_enum_variant.stderr @@ -0,0 +1,5 @@ +error: Field mapping for unexpected enum and variant combination + --> tests/enum_into/compile_fail/field/invalid_target_enum_variant.rs:8:29 + | +8 | #[enum_into(Target::NonExistent.a)] + | ^^^^^^^^^^^