Skip to content

Commit 810738a

Browse files
committed
feat: improve error messages by using span info
Improve error messages for incorrect use of the API. Take advantage spans of `compiler_error!` macro. Move from: ``` error: proc-macro derive panicked --> src/main.rs:25:17 | 25 | #[derive(Debug, TagName)] | ^^^^^^^ | = help: message: `tag` attribute expectes an assignment expression `[tag(case = "lower" | "upper">]` ``` To this: ``` error: right handside of the assignment expression is expected to be a string literal "lower" or "upper" --> src/main.rs:27:18 | 27 | #[tag(case = 1)] | ^ ```
1 parent 96d0a89 commit 810738a

13 files changed

+121
-96
lines changed

tagname_derive/src/lib.rs

+6-1
Original file line numberDiff line numberDiff line change
@@ -47,5 +47,10 @@ struct TaggedUnion {
4747
pub fn tagname_derive(input: TokenStream) -> TokenStream {
4848
let ast = syn::parse(input).unwrap();
4949

50-
generation::generate_code(traversal::traverse_ast(ast))
50+
let traversed = traversal::traverse_ast(ast);
51+
52+
if let Err(e) = traversed {
53+
return e.0.into();
54+
}
55+
generation::generate_code(traversed.unwrap())
5156
}

tagname_derive/src/traversal.rs

+68-51
Original file line numberDiff line numberDiff line change
@@ -1,101 +1,118 @@
1+
use proc_macro2::TokenStream;
12
use syn::punctuated::Pair;
3+
use syn::spanned::Spanned;
24
use syn::Attribute;
35
use syn::{Expr, ExprLit, Fields, Lit, Variant};
46

57
use super::{Case, Tag, TagData, TaggedUnion};
68

7-
pub(crate) fn traverse_ast(ast: syn::DeriveInput) -> TaggedUnion {
9+
#[derive(Debug)]
10+
pub struct Error(pub TokenStream);
11+
12+
pub(crate) fn traverse_ast(ast: syn::DeriveInput) -> Result<TaggedUnion, Error> {
813
let name = ast.ident.clone();
9-
let variants = traverse_enum(ast);
14+
let variants = traverse_enum(ast)?;
1015

11-
assert!(
12-
!variants.is_empty(),
13-
"cannot derive TagName for empty enum types"
14-
);
16+
if variants.is_empty() {
17+
return Err(Error(
18+
quote::quote_spanned! {name.span()=> compile_error!("cannot derive `TagName` for empty enum types"); },
19+
));
20+
}
1521

16-
let tags = traverse_variants(variants);
22+
let tags = traverse_variants(variants)?;
1723

18-
TaggedUnion { name, tags }
24+
Ok(TaggedUnion { name, tags })
1925
}
2026

21-
fn traverse_enum(ast: syn::DeriveInput) -> Vec<Variant> {
27+
fn traverse_enum(ast: syn::DeriveInput) -> Result<Vec<Variant>, Error> {
2228
match ast.data {
23-
syn::Data::Enum(enum_data) => enum_data
29+
syn::Data::Enum(enum_data) => Ok(enum_data
2430
.variants
2531
.into_pairs()
2632
.into_iter()
2733
.map(Pair::into_value)
28-
.collect(),
29-
_ => panic!("cannot derive TagName for non-enum types"),
34+
.collect()),
35+
syn::Data::Struct(s) => Err(Error(
36+
quote::quote_spanned! {s.struct_token.span()=> compile_error!("cannot derive `TagName` for struct types"); },
37+
)),
38+
syn::Data::Union(u) => Err(Error(
39+
quote::quote_spanned! {u.union_token.span()=> compile_error!("cannot derive `TagName` for union types"); },
40+
)),
3041
}
3142
}
3243

33-
fn traverse_variants(variants: Vec<Variant>) -> Vec<Tag> {
34-
variants
35-
.into_iter()
36-
.map(|v| {
37-
if !v.attrs.is_empty() {}
38-
match v.fields {
39-
Fields::Unit => Tag::Unit(TagData {
40-
ident: v.ident.clone(),
41-
case: traverse_attribute(&v),
42-
}),
43-
Fields::Unnamed(_) => Tag::Unnamed(TagData {
44-
ident: v.ident.clone(),
45-
case: traverse_attribute(&v),
46-
}),
47-
Fields::Named(_) => Tag::Named(TagData {
48-
ident: v.ident.clone(),
49-
case: traverse_attribute(&v),
50-
}),
51-
}
52-
})
53-
.collect()
44+
fn traverse_variants(variants: Vec<Variant>) -> Result<Vec<Tag>, Error> {
45+
let mut tags = Vec::new();
46+
for v in variants {
47+
if !v.attrs.is_empty() {}
48+
tags.push(match v.fields {
49+
Fields::Unit => Tag::Unit(TagData {
50+
ident: v.ident.clone(),
51+
case: traverse_attribute(&v)?,
52+
}),
53+
Fields::Unnamed(_) => Tag::Unnamed(TagData {
54+
ident: v.ident.clone(),
55+
case: traverse_attribute(&v)?,
56+
}),
57+
Fields::Named(_) => Tag::Named(TagData {
58+
ident: v.ident.clone(),
59+
case: traverse_attribute(&v)?,
60+
}),
61+
});
62+
}
63+
Ok(tags)
5464
}
5565

56-
fn traverse_attribute(variant: &Variant) -> Case {
66+
fn traverse_attribute(variant: &Variant) -> Result<Case, Error> {
5767
if variant.attrs.is_empty() {
58-
return Case::Unchanged;
68+
return Ok(Case::Unchanged);
5969
}
6070
let tag_attribute = variant.attrs.iter().find(|attr| attr.path.is_ident("tag"));
6171

6272
if tag_attribute.is_none() {
63-
return Case::Unchanged;
73+
return Ok(Case::Unchanged);
6474
}
6575

6676
traverse_tag_attribute_argument(tag_attribute.unwrap())
6777
}
6878

69-
fn traverse_tag_attribute_argument(attribute: &Attribute) -> Case {
70-
let expr: Expr = attribute.parse_args().expect(
71-
r#"`tag` attribute expectes an assignment expression `[tag(case = "lower" | "upper">]`"#,
72-
);
79+
fn traverse_tag_attribute_argument(attribute: &Attribute) -> Result<Case, Error> {
80+
let expr: Expr = attribute.parse_args().map_err(move |_| {
81+
Error(quote::quote_spanned! {attribute.span()=> compile_error!(r#"`tag` attribute expects an assignment expression `[tag(case = "lower" | "upper")]`"#); })
82+
})?;
7383

7484
let assign_expr = match expr {
7585
Expr::Assign(assign_expr) => assign_expr,
7686
_ => {
77-
panic!(
78-
r#"`tag` attribute expectes an assignment expression `[tag(case = "lower" | "upper">]`"#
79-
);
87+
return Err(Error(
88+
quote::quote_spanned! {expr.span()=> compile_error!(r#"`tag` attribute expects an assignment expression `[tag(case = "lower" | "upper")]`"#); },
89+
));
8090
}
8191
};
8292

8393
match *assign_expr.left {
84-
Expr::Path(_) => {}
85-
_ => panic!("left handside of the assignme in tag attribute must be `case`"),
94+
Expr::Path(ref p)
95+
if !p.path.segments.is_empty() && p.path.segments.first().unwrap().ident == "case" => {}
96+
_ => {
97+
return Err(Error(
98+
quote::quote_spanned! {assign_expr.span()=> compile_error!("left handside of the assignment in `tag` attribute must be `case`");},
99+
))
100+
}
86101
}
87102

88103
match *assign_expr.right {
89104
Expr::Lit(ExprLit {
90105
lit: Lit::Str(s),
91106
attrs: _,
92107
}) => match s.value().as_str() {
93-
"lower" => Case::Lower,
94-
"upper" => Case::Upper,
95-
_ => panic!("case value must be either \"upper\" or \"lower\""),
108+
"lower" => Ok(Case::Lower),
109+
"upper" => Ok(Case::Upper),
110+
_ => Err(Error(
111+
quote::quote_spanned! {s.span()=> compile_error!("`case` value must be either \"upper\" or \"lower\"");},
112+
)),
96113
},
97-
_ => panic!(
98-
r#"`tag` attribute expectes an assignment expression `[tag(case = "lower" | "upper">]`"#
99-
),
114+
_ => Err(Error(
115+
quote::quote_spanned! {assign_expr.right.span()=> compile_error!(r#"right handside of the assignment expression is expected to be a string literal "lower" or "upper""#);},
116+
)),
100117
}
101118
}
+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use tagname::TagName;
22

33
#[derive(TagName)]
4-
pub struct ArbitraryStruct{
4+
pub struct ArbitraryStruct {
55
}
66

77
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
1-
error: proc-macro derive panicked
2-
--> tests/compile_time_tests/bad-type-struct.rs:3:10
1+
error: cannot derive `TagName` for struct types
2+
--> tests/compile_time_tests/bad-type-struct.rs:4:5
33
|
4-
3 | #[derive(TagName)]
5-
| ^^^^^^^
6-
|
7-
= help: message: cannot derive TagName for non-enum types
4+
4 | pub struct ArbitraryStruct {
5+
| ^^^^^^
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
use tagname::TagName;
2+
3+
#[derive(TagName)]
4+
pub union ArbitraryUnion {
5+
f1: u32,
6+
f2: f32,
7+
}
8+
9+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
error: cannot derive `TagName` for union types
2+
--> tests/compile_time_tests/bad-type-union.rs:4:5
3+
|
4+
4 | pub union ArbitraryUnion {
5+
| ^^^^^
+4-6
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
1-
error: proc-macro derive panicked
2-
--> tests/compile_time_tests/empty-enum.rs:3:10
1+
error: cannot derive `TagName` for empty enum types
2+
--> tests/compile_time_tests/empty-enum.rs:4:6
33
|
4-
3 | #[derive(TagName)]
5-
| ^^^^^^^
6-
|
7-
= help: message: cannot derive TagName for empty enum types
4+
4 | enum EmptyEnum {
5+
| ^^^^^^^^^
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
1-
error: proc-macro derive panicked
2-
--> tests/compile_time_tests/tag_attribute_argument_empty.rs:3:10
1+
error: `tag` attribute expects an assignment expression `[tag(case = "lower" | "upper")]`
2+
--> tests/compile_time_tests/tag_attribute_argument_empty.rs:5:5
33
|
4-
3 | #[derive(TagName)]
5-
| ^^^^^^^
6-
|
7-
= help: message: `tag` attribute expectes an assignment expression `[tag(case = "lower" | "upper">]`: Error("expected attribute arguments in parentheses: #[tag(...)]")
4+
5 | #[tag]
5+
| ^
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
1-
error: proc-macro derive panicked
2-
--> tests/compile_time_tests/tag_attribute_argument_is_not_assignment.rs:3:10
1+
error: `tag` attribute expects an assignment expression `[tag(case = "lower" | "upper")]`
2+
--> tests/compile_time_tests/tag_attribute_argument_is_not_assignment.rs:5:11
33
|
4-
3 | #[derive(TagName)]
5-
| ^^^^^^^
6-
|
7-
= help: message: `tag` attribute expectes an assignment expression `[tag(case = "lower" | "upper">]`
4+
5 | #[tag(1)]
5+
| ^
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
1-
error: proc-macro derive panicked
2-
--> tests/compile_time_tests/tag_attribute_argument_lhs_not_case.rs:3:10
1+
error: left handside of the assignment in `tag` attribute must be `case`
2+
--> tests/compile_time_tests/tag_attribute_argument_lhs_not_case.rs:5:11
33
|
4-
3 | #[derive(TagName)]
5-
| ^^^^^^^
6-
|
7-
= help: message: case value must be either "upper" or "lower"
4+
5 | #[tag(something = "something")]
5+
| ^^^^^^^^^
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
1-
error: proc-macro derive panicked
2-
--> tests/compile_time_tests/tag_attribute_case_argument_is_not_string_literal.rs:3:10
1+
error: right handside of the assignment expression is expected to be a string literal "lower" or "upper"
2+
--> tests/compile_time_tests/tag_attribute_case_argument_is_not_string_literal.rs:5:18
33
|
4-
3 | #[derive(TagName)]
5-
| ^^^^^^^
6-
|
7-
= help: message: `tag` attribute expectes an assignment expression `[tag(case = "lower" | "upper">]`
4+
5 | #[tag(case = 1)]
5+
| ^
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
1-
error: proc-macro derive panicked
2-
--> tests/compile_time_tests/tag_attribute_case_value_not_lower.rs:3:10
1+
error: `case` value must be either "upper" or "lower"
2+
--> tests/compile_time_tests/tag_attribute_case_value_not_lower.rs:5:18
33
|
4-
3 | #[derive(TagName)]
5-
| ^^^^^^^
6-
|
7-
= help: message: case value must be either "upper" or "lower"
4+
5 | #[tag(case = "something")]
5+
| ^^^^^^^^^^^

tests/tagname.rs

+4-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,10 @@ enum ComplexUnion {
2424
Maybe(usize),
2525
Maybe2(usize, usize),
2626
Maybe3(Arbitrary),
27-
Maybe4 { x: Option<String> },
27+
#[allow(unused)]
28+
Maybe4 {
29+
x: Option<String>,
30+
},
2831
}
2932

3033
#[derive(TagName)]

0 commit comments

Comments
 (0)