|
| 1 | +use proc_macro2::{Ident, TokenStream}; |
| 2 | +use proc_macro_error::{abort, abort_call_site, proc_macro_error, ResultExt}; |
| 3 | +use quote::quote; |
| 4 | +use syn::{ |
| 5 | + parse_macro_input, parse_quote, punctuated::Punctuated, DataStruct, DeriveInput, Field, Fields, |
| 6 | + FieldsNamed, Path, Token, |
| 7 | +}; |
| 8 | + |
| 9 | +// TODO generally should use fully qualified names for trait function calls |
| 10 | + |
| 11 | +#[proc_macro_error] |
| 12 | +#[proc_macro_derive(Attribute, attributes(attribute))] |
| 13 | +pub fn attribute_derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream { |
| 14 | + let syn: Path = parse_quote!(::attribute_derive::__private::syn); |
| 15 | + let pm2: Path = parse_quote!(::attribute_derive::__private::proc_macro2); |
| 16 | + let none: Path = parse_quote!(::core::option::Option::None); |
| 17 | + let some: Path = parse_quote!(::core::option::Option::Some); |
| 18 | + let ok: Path = parse_quote!(::core::result::Result::Ok); |
| 19 | + let err: Path = parse_quote!(::core::result::Result::Err); |
| 20 | + |
| 21 | + let DeriveInput { |
| 22 | + attrs, |
| 23 | + ident, |
| 24 | + generics, |
| 25 | + data, |
| 26 | + .. |
| 27 | + } = parse_macro_input!(input as DeriveInput); |
| 28 | + |
| 29 | + let attribute_ident: String = attrs |
| 30 | + .into_iter() |
| 31 | + .find_map(|attribute| { |
| 32 | + if attribute.path.is_ident("attribute") { |
| 33 | + let path: Path = attribute.parse_args().unwrap_or_abort(); |
| 34 | + Some( |
| 35 | + path.get_ident() |
| 36 | + .unwrap_or_else(|| { |
| 37 | + abort_call_site!("Only single idents are currently supported") |
| 38 | + }) |
| 39 | + .to_string(), |
| 40 | + ) |
| 41 | + } else { |
| 42 | + None |
| 43 | + } |
| 44 | + }) |
| 45 | + .unwrap_or_else(|| { |
| 46 | + abort_call_site!( |
| 47 | + "You need to specify the attribute path via `#[attribute(name_of_your_attribute)]`" |
| 48 | + ); |
| 49 | + }); |
| 50 | + |
| 51 | + let (impl_generics, ty_generics, where_clause) = generics.split_for_impl(); |
| 52 | + |
| 53 | + let mut options: Punctuated<TokenStream, Token!(,)> = Punctuated::new(); |
| 54 | + let mut parsing: Punctuated<TokenStream, Token!(,)> = Punctuated::new(); |
| 55 | + let mut option_assignments: Punctuated<TokenStream, Token!(;)> = Punctuated::new(); |
| 56 | + let mut assignments: Punctuated<TokenStream, Token!(,)> = Punctuated::new(); |
| 57 | + let mut possible_variables: Vec<String> = Vec::new(); |
| 58 | + |
| 59 | + match data { |
| 60 | + syn::Data::Struct(DataStruct { |
| 61 | + fields: Fields::Named(FieldsNamed { named, .. }), |
| 62 | + .. |
| 63 | + }) => { |
| 64 | + for Field { |
| 65 | + attrs, |
| 66 | + ident, |
| 67 | + ty, |
| 68 | + .. |
| 69 | + } in named.into_iter() |
| 70 | + { |
| 71 | + let default: bool = |
| 72 | + attrs |
| 73 | + .into_iter() |
| 74 | + .find_map(|attribute| { |
| 75 | + if attribute.path.is_ident("attribute") { |
| 76 | + Some( |
| 77 | + attribute |
| 78 | + .parse_args() |
| 79 | + .ok() |
| 80 | + .and_then(|ident: Ident| { |
| 81 | + if ident == "default" { |
| 82 | + Some(true) |
| 83 | + } else { |
| 84 | + None |
| 85 | + } |
| 86 | + }) |
| 87 | + .unwrap_or_else(|| { |
| 88 | + abort!( |
| 89 | + attribute, |
| 90 | + "Only `#[attribute(default)]` is currently supported" |
| 91 | + ) |
| 92 | + }), |
| 93 | + ) |
| 94 | + } else { |
| 95 | + None |
| 96 | + } |
| 97 | + }) |
| 98 | + .unwrap_or_default(); |
| 99 | + let ident = ident.expect("named struct fields have idents"); |
| 100 | + let ident_str = ident.to_string(); |
| 101 | + |
| 102 | + options.push(quote!(#ident: Option<#ty>)); |
| 103 | + |
| 104 | + let error1 = format!("`{ident}` is specified multiple times"); |
| 105 | + let error2 = format!("`{ident}` was already specified"); |
| 106 | + option_assignments.push(quote! { |
| 107 | + match (&self.#ident, __other.#ident) { |
| 108 | + (#none, __value @ #some(_)) => self.#ident = __value, |
| 109 | + (#some(__first), #some(__second)) => { |
| 110 | + let mut __error = |
| 111 | + #syn::Error::new_spanned(__first, #error1); |
| 112 | + __error.combine(#syn::Error::new_spanned( |
| 113 | + __second, |
| 114 | + #error2, |
| 115 | + )); |
| 116 | + return #err(__error); |
| 117 | + } |
| 118 | + _ => {} |
| 119 | + } |
| 120 | + }); |
| 121 | + |
| 122 | + parsing.push(quote! { |
| 123 | + #ident_str => { |
| 124 | + __options.#ident = #some( |
| 125 | + ::attribute_derive::ConvertParsed::convert(__input.parse()?)? |
| 126 | + ); |
| 127 | + } |
| 128 | + }); |
| 129 | + |
| 130 | + let error = format!("Mandatory `{ident}` was not specified via the attributes."); |
| 131 | + assignments.push(if default { |
| 132 | + quote! { |
| 133 | + #ident: __options.#ident.unwrap_or_default() |
| 134 | + } |
| 135 | + } else { |
| 136 | + quote! { |
| 137 | + #ident: __options.#ident.ok_or_else(|| |
| 138 | + #syn::Error::new(#pm2::Span::call_site(), #error) |
| 139 | + )? |
| 140 | + } |
| 141 | + }); |
| 142 | + |
| 143 | + possible_variables.push(format!("`{ident_str}`")); |
| 144 | + } |
| 145 | + } |
| 146 | + _ => abort_call_site!("Only works on structs with named fields"), |
| 147 | + }; |
| 148 | + |
| 149 | + let error_invalid_name = if possible_variables.len() > 1 { |
| 150 | + let last = possible_variables.pop().unwrap(); |
| 151 | + format!( |
| 152 | + "Supported fields are {} and {}", |
| 153 | + possible_variables.join(", "), |
| 154 | + last |
| 155 | + ) |
| 156 | + } else { |
| 157 | + format!("Only `{}` is allowd field", possible_variables[0]) |
| 158 | + }; |
| 159 | + |
| 160 | + quote! { |
| 161 | + impl #impl_generics ::attribute_derive::Attribute for #ident #ty_generics #where_clause { |
| 162 | + fn from_attributes(__attrs: impl ::core::iter::IntoIterator<Item = #syn::Attribute>) -> #syn::Result<Self>{ |
| 163 | + #[derive(::core::default::Default)] |
| 164 | + struct __Options{ |
| 165 | + #options |
| 166 | + } |
| 167 | + impl __Options { |
| 168 | + fn extend_with(&mut self, __other:Self) -> #syn::Result<()>{ |
| 169 | + #option_assignments |
| 170 | + #ok(()) |
| 171 | + } |
| 172 | + } |
| 173 | + impl #syn::parse::Parse for __Options { |
| 174 | + fn parse(__input: #syn::parse::ParseStream<'_>) -> #syn::Result<Self> { |
| 175 | + let mut __options = Self::default(); |
| 176 | + loop { |
| 177 | + if __input.is_empty() { |
| 178 | + break; |
| 179 | + } |
| 180 | + |
| 181 | + let __variable = #syn::Ident::parse(__input)?; |
| 182 | + |
| 183 | + // Parse `=` |
| 184 | + __input.step(|__cursor| match __cursor.punct() { |
| 185 | + #some((__punct, __rest)) |
| 186 | + if __punct.as_char() == '=' && __punct.spacing() == #pm2::Spacing::Alone => |
| 187 | + { |
| 188 | + #ok(((), __rest)) |
| 189 | + } |
| 190 | + _ => #err(__cursor.error("Expected assignment `=`")), |
| 191 | + })?; |
| 192 | + |
| 193 | + match __variable.to_string().as_str() { |
| 194 | + #parsing |
| 195 | + _ => { |
| 196 | + return #err(#syn::Error::new( |
| 197 | + __variable.span(), |
| 198 | + #error_invalid_name |
| 199 | + )) |
| 200 | + } |
| 201 | + } |
| 202 | + |
| 203 | + if __input.is_empty() { |
| 204 | + break; |
| 205 | + } |
| 206 | + |
| 207 | + // Parse `,` |
| 208 | + __input.step(|__cursor| match __cursor.punct() { |
| 209 | + #some((__punct, __rest)) if __punct.as_char() == ',' => #ok(((), __rest)), |
| 210 | + _ => #err(__cursor.error("Expected assignment `=`")), |
| 211 | + })?; |
| 212 | + } |
| 213 | + Ok(__options) |
| 214 | + } |
| 215 | + } |
| 216 | + let mut __options = __Options::default(); |
| 217 | + for __attribute in __attrs { |
| 218 | + if __attribute.path.is_ident(#attribute_ident) { |
| 219 | + __options.extend_with(__attribute.parse_args()?)?; |
| 220 | + } |
| 221 | + } |
| 222 | + #ok(Self { |
| 223 | + #assignments |
| 224 | + }) |
| 225 | + } |
| 226 | + } |
| 227 | + } |
| 228 | + .into() |
| 229 | +} |
0 commit comments