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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 41 additions & 16 deletions src/enum_from/generator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ impl TryFrom<ParsedEnumFrom> for EnumFromGenerator {
})
.collect::<HashMap<_, _>>();

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,
Expand All @@ -217,8 +217,8 @@ impl TryFrom<ParsedEnumFrom> 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,
)?;
Expand All @@ -228,7 +228,7 @@ impl TryFrom<ParsedEnumFrom> 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 {
(
Expand All @@ -247,7 +247,7 @@ impl TryFrom<ParsedEnumFrom> 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 {
(
Expand All @@ -268,6 +268,8 @@ impl TryFrom<ParsedEnumFrom> 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);
}

Expand All @@ -279,29 +281,52 @@ impl TryFrom<ParsedEnumFrom> for EnumFromGenerator {
}
}

fn get_fields_mapping(
fields_annotations: &HashMap<FieldRef, FieldAnnotations>,
fn check_unused_fields_annotations(
source_enums: &HashMap<ContainerIdent, VariantsMapping>,
fields_annotations: HashMap<FieldRef, FieldAnnotations>,
) -> 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<FieldRef, FieldAnnotations>,
source_enum: &ContainerIdent,
source_variant: &VariantIdent,
) -> syn::Result<BTreeMap<FieldRef, FieldAnnotation>> {
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::<Vec<_>>();
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::<syn::Result<Vec<_>>>()?
Expand Down
16 changes: 10 additions & 6 deletions src/enum_from/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Self> {
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::<Token![.]>()?;
let field_span = input.span();
let source_field = if let Ok(ident) = input.parse::<Ident>() {
Expand All @@ -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(
Expand Down
82 changes: 57 additions & 25 deletions src/enum_into/generator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ impl TryFrom<ParsedEnumInto> for EnumIntoGenerator {
.map(|ContainerAnnotation(target_enum)| (target_enum, VariantsMapping(HashMap::new())))
.collect::<HashMap<_, _>>();

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()
Expand All @@ -217,8 +217,8 @@ impl TryFrom<ParsedEnumInto> 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,
)?;
Expand All @@ -228,7 +228,7 @@ impl TryFrom<ParsedEnumInto> 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 {
(
Expand All @@ -247,7 +247,7 @@ impl TryFrom<ParsedEnumInto> 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 {
(
Expand All @@ -273,16 +273,11 @@ impl TryFrom<ParsedEnumInto> 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 {
Expand All @@ -293,29 +288,66 @@ impl TryFrom<ParsedEnumInto> for EnumIntoGenerator {
}
}

fn get_fields_mapping(
fields_annotations: &HashMap<FieldRef, FieldAnnotations>,
fn check_unused_variants_annotations(
target_variants: HashMap<ContainerIdent, (VariantIdent, Span)>,
) -> 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<ContainerIdent, VariantsMapping>,
fields_annotations: HashMap<FieldRef, FieldAnnotations>,
) -> 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<FieldRef, FieldAnnotations>,
target_enum: &ContainerIdent,
target_variant: &VariantIdent,
) -> syn::Result<BTreeMap<FieldRef, FieldAnnotation>> {
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::<Vec<_>>();
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::<syn::Result<Vec<_>>>()?
Expand Down
16 changes: 10 additions & 6 deletions src/enum_into/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Self> {
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::<Token![.]>()?;
let field_span = input.span();
let target_field = if let Ok(ident) = input.parse::<Ident>() {
Expand All @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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)]
| ^^^^^^^^^^^^^^^^^
18 changes: 18 additions & 0 deletions tests/enum_from/compile_fail/field/invalid_source_enum.rs
Original file line number Diff line number Diff line change
@@ -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() {}
5 changes: 5 additions & 0 deletions tests/enum_from/compile_fail/field/invalid_source_enum.stderr
Original file line number Diff line number Diff line change
@@ -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)]
| ^^^^^^^^^^^
18 changes: 18 additions & 0 deletions tests/enum_from/compile_fail/field/invalid_source_enum_variant.rs
Original file line number Diff line number Diff line change
@@ -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() {}
Original file line number Diff line number Diff line change
@@ -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)]
| ^^^^^^^^^^^
Loading