Skip to content

Commit 6340647

Browse files
authored
Merge pull request #10 from avandecreme/error_reporting
Report error for invalid field annotations
2 parents 22804c6 + 8391683 commit 6340647

18 files changed

Lines changed: 217 additions & 65 deletions

src/enum_from/generator.rs

Lines changed: 41 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ impl TryFrom<ParsedEnumFrom> for EnumFromGenerator {
200200
})
201201
.collect::<HashMap<_, _>>();
202202

203-
for (target_variant, variant_annotations) in variants_annotations {
203+
for (target_variant, mut variant_annotations) in variants_annotations {
204204
for variant_annotation in variant_annotations.variant_annotations {
205205
let (source_enum, source_variant, span) = get_source_enum_and_variant(
206206
&target_variant,
@@ -217,8 +217,8 @@ impl TryFrom<ParsedEnumFrom> for EnumFromGenerator {
217217
)
218218
})?;
219219

220-
let fields_mapping = get_fields_mapping(
221-
&variant_annotations.fields_annotations,
220+
let fields_annotations = extract_fields_annotations(
221+
&mut variant_annotations.fields_annotations,
222222
&source_enum,
223223
&source_variant,
224224
)?;
@@ -228,7 +228,7 @@ impl TryFrom<ParsedEnumFrom> for EnumFromGenerator {
228228
Fields::Unit => VariantMapping::Unit { target_variant },
229229
Fields::Unnamed(_) => VariantMapping::Tuple {
230230
target_variant,
231-
fields_mapping: fields_mapping
231+
fields_mapping: fields_annotations
232232
.into_iter()
233233
.map(|target_to_source| match target_to_source {
234234
(
@@ -247,7 +247,7 @@ impl TryFrom<ParsedEnumFrom> for EnumFromGenerator {
247247
},
248248
Fields::Named(_) => VariantMapping::Struct {
249249
target_variant,
250-
fields_mapping: fields_mapping
250+
fields_mapping: fields_annotations
251251
.into_iter()
252252
.map(|target_to_source| match target_to_source {
253253
(
@@ -268,6 +268,8 @@ impl TryFrom<ParsedEnumFrom> for EnumFromGenerator {
268268

269269
variants_mapping.insert(source_variant, variant_mapping);
270270
}
271+
272+
check_unused_fields_annotations(&source_enums, variant_annotations.fields_annotations)?;
271273
target_variants.insert(VariantIdent(target_variant.ident.clone()), target_variant);
272274
}
273275

@@ -279,29 +281,52 @@ impl TryFrom<ParsedEnumFrom> for EnumFromGenerator {
279281
}
280282
}
281283

282-
fn get_fields_mapping(
283-
fields_annotations: &HashMap<FieldRef, FieldAnnotations>,
284+
fn check_unused_fields_annotations(
285+
source_enums: &HashMap<ContainerIdent, VariantsMapping>,
286+
fields_annotations: HashMap<FieldRef, FieldAnnotations>,
287+
) -> syn::Result<()> {
288+
for field_annotations in fields_annotations.into_values() {
289+
for field_annotation in field_annotations.fields_annotations {
290+
if source_enums.contains_key(&field_annotation.source_enum) {
291+
Err(syn::Error::new(
292+
field_annotation.variant_span,
293+
"Field mapping for unexpected enum and variant combination",
294+
))?
295+
} else {
296+
Err(syn::Error::new(
297+
field_annotation.enum_span,
298+
"Field mapping for unknown enum",
299+
))?
300+
}
301+
}
302+
}
303+
304+
Ok(())
305+
}
306+
307+
fn extract_fields_annotations(
308+
fields_annotations: &mut HashMap<FieldRef, FieldAnnotations>,
284309
source_enum: &ContainerIdent,
285310
source_variant: &VariantIdent,
286311
) -> syn::Result<BTreeMap<FieldRef, FieldAnnotation>> {
287312
Ok(fields_annotations
288-
.iter()
313+
.iter_mut()
289314
.filter_map(|(target_field, field_annotations)| {
290-
let annotations = field_annotations
315+
let mut annotations = field_annotations
291316
.fields_annotations
292-
.iter()
293-
.filter(|field_annotation| {
317+
.extract_if(.., |field_annotation| {
294318
field_annotation.source_enum == *source_enum
295319
&& field_annotation.source_variant == *source_variant
296320
})
297321
.collect::<Vec<_>>();
298-
match annotations.len() {
299-
0 => None,
300-
1 => Some(Ok((target_field.clone(), annotations[0].clone()))),
301-
_ => Some(Err(syn::Error::new(
322+
let annotation = annotations.pop();
323+
if annotations.pop().is_some() {
324+
Some(Err(syn::Error::new(
302325
field_annotations.field_span,
303326
format!("Multiple mapping found for source enum `{source_enum}`"),
304-
))),
327+
)))
328+
} else {
329+
annotation.map(|annotation| Ok((target_field.clone(), annotation)))
305330
}
306331
})
307332
.collect::<syn::Result<Vec<_>>>()?

src/enum_from/parser.rs

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -99,15 +99,15 @@ pub struct FieldAnnotation {
9999
pub source_enum: ContainerIdent,
100100
pub source_variant: VariantIdent,
101101
pub source_field: FieldRef,
102+
pub enum_span: Span,
103+
pub variant_span: Span,
102104
pub field_span: Span,
103105
}
104106

105107
impl Parse for FieldAnnotation {
106108
fn parse(input: ParseStream) -> syn::Result<Self> {
107-
let path: Path = input.parse()?;
109+
let mut path: Path = input.parse()?;
108110
if path.segments.len() == 2 {
109-
let source_enum = ContainerIdent(path.segments[0].ident.clone());
110-
let source_variant = VariantIdent(path.segments[1].ident.clone());
111111
input.parse::<Token![.]>()?;
112112
let field_span = input.span();
113113
let source_field = if let Ok(ident) = input.parse::<Ident>() {
@@ -120,11 +120,15 @@ impl Parse for FieldAnnotation {
120120
"Expected either a field identifier or a field position",
121121
))?
122122
};
123+
let variant_segment = path.segments.pop().unwrap().into_value();
124+
let enum_segment = path.segments.pop().unwrap().into_value();
123125
Ok(FieldAnnotation {
124-
source_enum,
125-
source_variant,
126-
source_field,
126+
enum_span: enum_segment.span(),
127+
variant_span: variant_segment.span(),
127128
field_span,
129+
source_enum: ContainerIdent(enum_segment.ident),
130+
source_variant: VariantIdent(variant_segment.ident),
131+
source_field,
128132
})
129133
} else {
130134
Err(syn::Error::new_spanned(

src/enum_into/generator.rs

Lines changed: 57 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ impl TryFrom<ParsedEnumInto> for EnumIntoGenerator {
194194
.map(|ContainerAnnotation(target_enum)| (target_enum, VariantsMapping(HashMap::new())))
195195
.collect::<HashMap<_, _>>();
196196

197-
for (source_variant, variant_annotations) in variants_annotations {
197+
for (source_variant, mut variant_annotations) in variants_annotations {
198198
let mut target_variants = variant_annotations
199199
.variant_annotations
200200
.into_iter()
@@ -217,8 +217,8 @@ impl TryFrom<ParsedEnumInto> for EnumIntoGenerator {
217217
.map(|(target_variant, _span)| target_variant)
218218
.unwrap_or_else(|| VariantIdent(source_variant.ident.clone()));
219219

220-
let fields_mapping = get_fields_mapping(
221-
&variant_annotations.fields_annotations,
220+
let fields_annotations = extract_fields_annotations(
221+
&mut variant_annotations.fields_annotations,
222222
target_enum,
223223
&target_variant,
224224
)?;
@@ -228,7 +228,7 @@ impl TryFrom<ParsedEnumInto> for EnumIntoGenerator {
228228
Fields::Unit => VariantMapping::Unit { source_variant },
229229
Fields::Unnamed(_) => VariantMapping::Tuple {
230230
source_variant,
231-
fields_mapping: fields_mapping
231+
fields_mapping: fields_annotations
232232
.into_iter()
233233
.map(|source_to_target| match source_to_target {
234234
(
@@ -247,7 +247,7 @@ impl TryFrom<ParsedEnumInto> for EnumIntoGenerator {
247247
},
248248
Fields::Named(_) => VariantMapping::Struct {
249249
source_variant,
250-
fields_mapping: fields_mapping
250+
fields_mapping: fields_annotations
251251
.into_iter()
252252
.map(|source_to_target| match source_to_target {
253253
(
@@ -273,16 +273,11 @@ impl TryFrom<ParsedEnumInto> for EnumIntoGenerator {
273273

274274
variants_mapping.insert(target_variant, variant_mappings);
275275
}
276-
source_variants.insert(VariantIdent(source_variant.ident.clone()), source_variant);
277276

278-
for (target_enum, (_, span)) in target_variants {
279-
Err(syn::Error::new(
280-
span,
281-
format!(
282-
"target enum `{target_enum}` is not specified in this enum's #[enum_into] annotation"
283-
),
284-
))?
285-
}
277+
check_unused_variants_annotations(target_variants)?;
278+
check_unused_fields_annotations(&target_enums, variant_annotations.fields_annotations)?;
279+
280+
source_variants.insert(VariantIdent(source_variant.ident.clone()), source_variant);
286281
}
287282

288283
Ok(EnumIntoGenerator {
@@ -293,29 +288,66 @@ impl TryFrom<ParsedEnumInto> for EnumIntoGenerator {
293288
}
294289
}
295290

296-
fn get_fields_mapping(
297-
fields_annotations: &HashMap<FieldRef, FieldAnnotations>,
291+
fn check_unused_variants_annotations(
292+
target_variants: HashMap<ContainerIdent, (VariantIdent, Span)>,
293+
) -> syn::Result<()> {
294+
for (target_enum, (_, span)) in target_variants {
295+
Err(syn::Error::new(
296+
span,
297+
format!(
298+
"target enum `{target_enum}` is not specified in this enum's #[enum_into] annotation"
299+
),
300+
))?
301+
}
302+
Ok(())
303+
}
304+
305+
fn check_unused_fields_annotations(
306+
target_enums: &HashMap<ContainerIdent, VariantsMapping>,
307+
fields_annotations: HashMap<FieldRef, FieldAnnotations>,
308+
) -> syn::Result<()> {
309+
for field_annotations in fields_annotations.into_values() {
310+
for field_annotation in field_annotations.fields_annotations {
311+
if target_enums.contains_key(&field_annotation.target_enum) {
312+
Err(syn::Error::new(
313+
field_annotation.variant_span,
314+
"Field mapping for unexpected enum and variant combination",
315+
))?
316+
} else {
317+
Err(syn::Error::new(
318+
field_annotation.enum_span,
319+
"Field mapping for unknown enum",
320+
))?
321+
}
322+
}
323+
}
324+
325+
Ok(())
326+
}
327+
328+
fn extract_fields_annotations(
329+
fields_annotations: &mut HashMap<FieldRef, FieldAnnotations>,
298330
target_enum: &ContainerIdent,
299331
target_variant: &VariantIdent,
300332
) -> syn::Result<BTreeMap<FieldRef, FieldAnnotation>> {
301333
Ok(fields_annotations
302-
.iter()
334+
.iter_mut()
303335
.filter_map(|(source_field, field_annotations)| {
304-
let annotations = field_annotations
336+
let mut annotations = field_annotations
305337
.fields_annotations
306-
.iter()
307-
.filter(|field_annotation| {
338+
.extract_if(.., |field_annotation| {
308339
field_annotation.target_enum == *target_enum
309340
&& field_annotation.target_variant == *target_variant
310341
})
311342
.collect::<Vec<_>>();
312-
match annotations.len() {
313-
0 => None,
314-
1 => Some(Ok((source_field.clone(), annotations[0].clone()))),
315-
_ => Some(Err(syn::Error::new(
343+
let annotation = annotations.pop();
344+
if annotations.pop().is_some() {
345+
Some(Err(syn::Error::new(
316346
field_annotations.field_span,
317347
format!("Multiple mapping found for target enum `{target_enum}`"),
318-
))),
348+
)))
349+
} else {
350+
annotation.map(|annotation| Ok((source_field.clone(), annotation)))
319351
}
320352
})
321353
.collect::<syn::Result<Vec<_>>>()?

src/enum_into/parser.rs

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -97,15 +97,15 @@ pub struct FieldAnnotation {
9797
pub target_enum: ContainerIdent,
9898
pub target_variant: VariantIdent,
9999
pub target_field: FieldRef,
100+
pub enum_span: Span,
101+
pub variant_span: Span,
100102
pub field_span: Span,
101103
}
102104

103105
impl Parse for FieldAnnotation {
104106
fn parse(input: ParseStream) -> syn::Result<Self> {
105-
let path: Path = input.parse()?;
107+
let mut path: Path = input.parse()?;
106108
if path.segments.len() == 2 {
107-
let target_enum = ContainerIdent(path.segments[0].ident.clone());
108-
let target_variant = VariantIdent(path.segments[1].ident.clone());
109109
input.parse::<Token![.]>()?;
110110
let field_span = input.span();
111111
let target_field = if let Ok(ident) = input.parse::<Ident>() {
@@ -118,11 +118,15 @@ impl Parse for FieldAnnotation {
118118
"Expected either a field identifier or a field position",
119119
))?
120120
};
121+
let variant_segment = path.segments.pop().unwrap().into_value();
122+
let enum_segment = path.segments.pop().unwrap().into_value();
121123
Ok(FieldAnnotation {
122-
target_enum,
123-
target_variant,
124-
target_field,
124+
enum_span: enum_segment.span(),
125+
variant_span: variant_segment.span(),
125126
field_span,
127+
target_enum: ContainerIdent(enum_segment.ident),
128+
target_variant: VariantIdent(variant_segment.ident),
129+
target_field,
126130
})
127131
} else {
128132
Err(syn::Error::new_spanned(

tests/enum_from/compile_fail/field/invalid_attribute_syntax.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ enum Source {
99
enum Target {
1010
#[enum_from]
1111
Struct {
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)]
1313
a: i32,
1414
},
1515
}
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
error: Expected SourceEnum::SourceVariant.field_name
22
--> tests/enum_from/compile_fail/field/invalid_attribute_syntax.rs:12:21
33
|
4-
12 | #[enum_from(Source.x)] // Should be #[enum_from(Source::Struct.x)]
5-
| ^^^^^^
4+
12 | #[enum_from(Source::Struct::x)] // Should be #[enum_from(Source::Struct.x)]
5+
| ^^^^^^^^^^^^^^^^^
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
use enum_convert::EnumFrom;
2+
3+
enum Source {
4+
Struct { x: i32 },
5+
}
6+
7+
#[derive(EnumFrom)]
8+
#[enum_from(Source)]
9+
enum Target {
10+
#[enum_from]
11+
Struct {
12+
// Should be #[enum_from(Source::Struct.x)]
13+
#[enum_from(NonExistent::Struct.x)]
14+
a: i32,
15+
},
16+
}
17+
18+
fn main() {}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
error: Field mapping for unknown enum
2+
--> tests/enum_from/compile_fail/field/invalid_source_enum.rs:13:21
3+
|
4+
13 | #[enum_from(NonExistent::Struct.x)]
5+
| ^^^^^^^^^^^
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
use enum_convert::EnumFrom;
2+
3+
enum Source {
4+
Struct { x: i32 },
5+
}
6+
7+
#[derive(EnumFrom)]
8+
#[enum_from(Source)]
9+
enum Target {
10+
#[enum_from]
11+
Struct {
12+
// Should be #[enum_from(Source::Struct.x)]
13+
#[enum_from(Source::NonExistent.x)]
14+
a: i32,
15+
},
16+
}
17+
18+
fn main() {}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
error: Field mapping for unexpected enum and variant combination
2+
--> tests/enum_from/compile_fail/field/invalid_source_enum_variant.rs:13:29
3+
|
4+
13 | #[enum_from(Source::NonExistent.x)]
5+
| ^^^^^^^^^^^

0 commit comments

Comments
 (0)