|
| 1 | +use proc_macro2::TokenStream; |
1 | 2 | use syn::punctuated::Pair;
|
| 3 | +use syn::spanned::Spanned; |
2 | 4 | use syn::Attribute;
|
3 | 5 | use syn::{Expr, ExprLit, Fields, Lit, Variant};
|
4 | 6 |
|
5 | 7 | use super::{Case, Tag, TagData, TaggedUnion};
|
6 | 8 |
|
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> { |
8 | 13 | let name = ast.ident.clone();
|
9 |
| - let variants = traverse_enum(ast); |
| 14 | + let variants = traverse_enum(ast)?; |
10 | 15 |
|
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 | + } |
15 | 21 |
|
16 |
| - let tags = traverse_variants(variants); |
| 22 | + let tags = traverse_variants(variants)?; |
17 | 23 |
|
18 |
| - TaggedUnion { name, tags } |
| 24 | + Ok(TaggedUnion { name, tags }) |
19 | 25 | }
|
20 | 26 |
|
21 |
| -fn traverse_enum(ast: syn::DeriveInput) -> Vec<Variant> { |
| 27 | +fn traverse_enum(ast: syn::DeriveInput) -> Result<Vec<Variant>, Error> { |
22 | 28 | match ast.data {
|
23 |
| - syn::Data::Enum(enum_data) => enum_data |
| 29 | + syn::Data::Enum(enum_data) => Ok(enum_data |
24 | 30 | .variants
|
25 | 31 | .into_pairs()
|
26 | 32 | .into_iter()
|
27 | 33 | .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 | + )), |
30 | 41 | }
|
31 | 42 | }
|
32 | 43 |
|
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) |
54 | 64 | }
|
55 | 65 |
|
56 |
| -fn traverse_attribute(variant: &Variant) -> Case { |
| 66 | +fn traverse_attribute(variant: &Variant) -> Result<Case, Error> { |
57 | 67 | if variant.attrs.is_empty() {
|
58 |
| - return Case::Unchanged; |
| 68 | + return Ok(Case::Unchanged); |
59 | 69 | }
|
60 | 70 | let tag_attribute = variant.attrs.iter().find(|attr| attr.path.is_ident("tag"));
|
61 | 71 |
|
62 | 72 | if tag_attribute.is_none() {
|
63 |
| - return Case::Unchanged; |
| 73 | + return Ok(Case::Unchanged); |
64 | 74 | }
|
65 | 75 |
|
66 | 76 | traverse_tag_attribute_argument(tag_attribute.unwrap())
|
67 | 77 | }
|
68 | 78 |
|
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 | + })?; |
73 | 83 |
|
74 | 84 | let assign_expr = match expr {
|
75 | 85 | Expr::Assign(assign_expr) => assign_expr,
|
76 | 86 | _ => {
|
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 | + )); |
80 | 90 | }
|
81 | 91 | };
|
82 | 92 |
|
83 | 93 | 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 | + } |
86 | 101 | }
|
87 | 102 |
|
88 | 103 | match *assign_expr.right {
|
89 | 104 | Expr::Lit(ExprLit {
|
90 | 105 | lit: Lit::Str(s),
|
91 | 106 | attrs: _,
|
92 | 107 | }) => 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 | + )), |
96 | 113 | },
|
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 | + )), |
100 | 117 | }
|
101 | 118 | }
|
0 commit comments