parity_scale_codec_derive/
utils.rs1use std::str::FromStr;
21
22use proc_macro2::TokenStream;
23use quote::{ToTokens, quote};
24use syn::{
25 parse::Parse, punctuated::Punctuated, spanned::Spanned, token, Attribute, Data, DeriveInput,
26 Field, Fields, FieldsNamed, FieldsUnnamed, Lit, Meta, MetaNameValue, NestedMeta, Path, Variant,
27};
28
29fn find_meta_item<'a, F, R, I, M>(mut itr: I, mut pred: F) -> Option<R>
30where
31 F: FnMut(M) -> Option<R> + Clone,
32 I: Iterator<Item = &'a Attribute>,
33 M: Parse,
34{
35 itr.find_map(|attr| {
36 attr.path.is_ident("codec").then(|| pred(attr.parse_args().ok()?)).flatten()
37 })
38}
39
40pub fn variant_index(v: &Variant, i: usize) -> TokenStream {
43 let index = find_meta_item(v.attrs.iter(), |meta| {
45 if let NestedMeta::Meta(Meta::NameValue(ref nv)) = meta {
46 if nv.path.is_ident("index") {
47 if let Lit::Int(ref v) = nv.lit {
48 let byte = v
49 .base10_parse::<u8>()
50 .expect("Internal error, index attribute must have been checked");
51 return Some(byte)
52 }
53 }
54 }
55
56 None
57 });
58
59 index.map(|i| quote! { #i }).unwrap_or_else(|| {
61 v.discriminant
62 .as_ref()
63 .map(|(_, expr)| quote! { #expr })
64 .unwrap_or_else(|| quote! { #i })
65 })
66}
67
68pub fn get_encoded_as_type(field: &Field) -> Option<TokenStream> {
71 find_meta_item(field.attrs.iter(), |meta| {
72 if let NestedMeta::Meta(Meta::NameValue(ref nv)) = meta {
73 if nv.path.is_ident("encoded_as") {
74 if let Lit::Str(ref s) = nv.lit {
75 return Some(
76 TokenStream::from_str(&s.value())
77 .expect("Internal error, encoded_as attribute must have been checked"),
78 )
79 }
80 }
81 }
82
83 None
84 })
85}
86
87pub fn is_compact(field: &Field) -> bool {
89 find_meta_item(field.attrs.iter(), |meta| {
90 if let NestedMeta::Meta(Meta::Path(ref path)) = meta {
91 if path.is_ident("compact") {
92 return Some(())
93 }
94 }
95
96 None
97 })
98 .is_some()
99}
100
101pub fn should_skip(attrs: &[Attribute]) -> bool {
103 find_meta_item(attrs.iter(), |meta| {
104 if let NestedMeta::Meta(Meta::Path(ref path)) = meta {
105 if path.is_ident("skip") {
106 return Some(path.span())
107 }
108 }
109
110 None
111 })
112 .is_some()
113}
114
115pub fn has_dumb_trait_bound(attrs: &[Attribute]) -> bool {
117 find_meta_item(attrs.iter(), |meta| {
118 if let NestedMeta::Meta(Meta::Path(ref path)) = meta {
119 if path.is_ident("dumb_trait_bound") {
120 return Some(())
121 }
122 }
123
124 None
125 })
126 .is_some()
127}
128
129fn crate_access() -> syn::Result<proc_macro2::Ident> {
131 use proc_macro2::{Ident, Span};
132 use proc_macro_crate::{crate_name, FoundCrate};
133 const DEF_CRATE: &str = "parity-scale-codec";
134 match crate_name(DEF_CRATE) {
135 Ok(FoundCrate::Itself) => {
136 let name = DEF_CRATE.to_string().replace('-', "_");
137 Ok(syn::Ident::new(&name, Span::call_site()))
138 },
139 Ok(FoundCrate::Name(name)) => Ok(Ident::new(&name, Span::call_site())),
140 Err(e) => Err(syn::Error::new(Span::call_site(), e)),
141 }
142}
143
144struct CratePath {
146 _crate_token: Token![crate],
147 _eq_token: Token![=],
148 path: Path,
149}
150
151impl Parse for CratePath {
152 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
153 Ok(CratePath {
154 _crate_token: input.parse()?,
155 _eq_token: input.parse()?,
156 path: input.parse()?,
157 })
158 }
159}
160
161impl From<CratePath> for Path {
162 fn from(CratePath { path, .. }: CratePath) -> Self {
163 path
164 }
165}
166
167fn codec_crate_path_inner(attr: &Attribute) -> Option<Path> {
169 attr.path
171 .is_ident("codec")
172 .then(|| {
173 attr.parse_args::<CratePath>().map(Into::into).ok()
175 })
176 .flatten()
177}
178
179pub fn codec_crate_path(attrs: &[Attribute]) -> syn::Result<Path> {
185 match attrs.iter().find_map(codec_crate_path_inner) {
186 Some(path) => Ok(path),
187 None => crate_access().map(|ident| parse_quote!(::#ident)),
188 }
189}
190
191pub enum CustomTraitBound<N> {
193 SpecifiedBounds {
194 _name: N,
195 _paren_token: token::Paren,
196 bounds: Punctuated<syn::WherePredicate, Token![,]>,
197 },
198 SkipTypeParams {
199 _name: N,
200 _paren_token_1: token::Paren,
201 _skip_type_params: skip_type_params,
202 _paren_token_2: token::Paren,
203 type_names: Punctuated<syn::Ident, Token![,]>,
204 },
205}
206
207impl<N: Parse> Parse for CustomTraitBound<N> {
208 fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
209 let mut content;
210 let _name: N = input.parse()?;
211 let _paren_token = syn::parenthesized!(content in input);
212 if content.peek(skip_type_params) {
213 Ok(Self::SkipTypeParams {
214 _name,
215 _paren_token_1: _paren_token,
216 _skip_type_params: content.parse::<skip_type_params>()?,
217 _paren_token_2: syn::parenthesized!(content in content),
218 type_names: content.parse_terminated(syn::Ident::parse)?,
219 })
220 } else {
221 Ok(Self::SpecifiedBounds {
222 _name,
223 _paren_token,
224 bounds: content.parse_terminated(syn::WherePredicate::parse)?,
225 })
226 }
227 }
228}
229
230syn::custom_keyword!(encode_bound);
231syn::custom_keyword!(decode_bound);
232syn::custom_keyword!(mel_bound);
233syn::custom_keyword!(skip_type_params);
234
235pub fn custom_decode_trait_bound(attrs: &[Attribute]) -> Option<CustomTraitBound<decode_bound>> {
239 find_meta_item(attrs.iter(), Some)
240}
241
242pub fn custom_encode_trait_bound(attrs: &[Attribute]) -> Option<CustomTraitBound<encode_bound>> {
246 find_meta_item(attrs.iter(), Some)
247}
248
249#[cfg(feature = "max-encoded-len")]
253pub fn custom_mel_trait_bound(attrs: &[Attribute]) -> Option<CustomTraitBound<mel_bound>> {
254 find_meta_item(attrs.iter(), Some)
255}
256
257pub fn filter_skip_named(fields: &syn::FieldsNamed) -> impl Iterator<Item = &Field> {
260 fields.named.iter().filter(|f| !should_skip(&f.attrs))
261}
262
263pub fn filter_skip_unnamed(
266 fields: &syn::FieldsUnnamed,
267) -> impl Iterator<Item = (usize, &Field)> {
268 fields.unnamed.iter().enumerate().filter(|(_, f)| !should_skip(&f.attrs))
269}
270
271pub fn check_attributes(input: &DeriveInput) -> syn::Result<()> {
294 for attr in &input.attrs {
295 check_top_attribute(attr)?;
296 }
297
298 match input.data {
299 Data::Struct(ref data) => match &data.fields {
300 | Fields::Named(FieldsNamed { named: fields, .. }) |
301 Fields::Unnamed(FieldsUnnamed { unnamed: fields, .. }) =>
302 for field in fields {
303 for attr in &field.attrs {
304 check_field_attribute(attr)?;
305 }
306 },
307 Fields::Unit => (),
308 },
309 Data::Enum(ref data) =>
310 for variant in data.variants.iter() {
311 for attr in &variant.attrs {
312 check_variant_attribute(attr)?;
313 }
314 for field in &variant.fields {
315 for attr in &field.attrs {
316 check_field_attribute(attr)?;
317 }
318 }
319 },
320 Data::Union(_) => (),
321 }
322 Ok(())
323}
324
325pub fn is_lint_attribute(attr: &Attribute) -> bool {
327 attr.path.is_ident("allow") ||
328 attr.path.is_ident("deny") ||
329 attr.path.is_ident("forbid") ||
330 attr.path.is_ident("warn")
331}
332
333fn check_field_attribute(attr: &Attribute) -> syn::Result<()> {
338 let field_error = "Invalid attribute on field, only `#[codec(skip)]`, `#[codec(compact)]` and \
339 `#[codec(encoded_as = \"$EncodeAs\")]` are accepted.";
340
341 if attr.path.is_ident("codec") {
342 match attr.parse_meta()? {
343 Meta::List(ref meta_list) if meta_list.nested.len() == 1 => {
344 match meta_list.nested.first().expect("Just checked that there is one item; qed") {
345 NestedMeta::Meta(Meta::Path(path))
346 if path.get_ident().map_or(false, |i| i == "skip") =>
347 Ok(()),
348
349 NestedMeta::Meta(Meta::Path(path))
350 if path.get_ident().map_or(false, |i| i == "compact") =>
351 Ok(()),
352
353 NestedMeta::Meta(Meta::NameValue(MetaNameValue {
354 path,
355 lit: Lit::Str(lit_str),
356 ..
357 })) if path.get_ident().map_or(false, |i| i == "encoded_as") =>
358 TokenStream::from_str(&lit_str.value())
359 .map(|_| ())
360 .map_err(|_e| syn::Error::new(lit_str.span(), "Invalid token stream")),
361
362 elt => Err(syn::Error::new(elt.span(), field_error)),
363 }
364 },
365 meta => Err(syn::Error::new(meta.span(), field_error)),
366 }
367 } else {
368 Ok(())
369 }
370}
371
372fn check_variant_attribute(attr: &Attribute) -> syn::Result<()> {
376 let variant_error = "Invalid attribute on variant, only `#[codec(skip)]` and \
377 `#[codec(index = $u8)]` are accepted.";
378
379 if attr.path.is_ident("codec") {
380 match attr.parse_meta()? {
381 Meta::List(ref meta_list) if meta_list.nested.len() == 1 => {
382 match meta_list.nested.first().expect("Just checked that there is one item; qed") {
383 NestedMeta::Meta(Meta::Path(path))
384 if path.get_ident().map_or(false, |i| i == "skip") =>
385 Ok(()),
386
387 NestedMeta::Meta(Meta::NameValue(MetaNameValue {
388 path,
389 lit: Lit::Int(lit_int),
390 ..
391 })) if path.get_ident().map_or(false, |i| i == "index") => lit_int
392 .base10_parse::<u8>()
393 .map(|_| ())
394 .map_err(|_| syn::Error::new(lit_int.span(), "Index must be in 0..255")),
395
396 elt => Err(syn::Error::new(elt.span(), variant_error)),
397 }
398 },
399 meta => Err(syn::Error::new(meta.span(), variant_error)),
400 }
401 } else {
402 Ok(())
403 }
404}
405
406fn check_top_attribute(attr: &Attribute) -> syn::Result<()> {
408 let top_error = "Invalid attribute: only `#[codec(dumb_trait_bound)]`, \
409 `#[codec(crate = path::to::crate)]`, `#[codec(encode_bound(T: Encode))]`, \
410 `#[codec(decode_bound(T: Decode))]`, or `#[codec(mel_bound(T: MaxEncodedLen))]` \
411 are accepted as top attribute";
412 if attr.path.is_ident("codec") &&
413 attr.parse_args::<CustomTraitBound<encode_bound>>().is_err() &&
414 attr.parse_args::<CustomTraitBound<decode_bound>>().is_err() &&
415 attr.parse_args::<CustomTraitBound<mel_bound>>().is_err() &&
416 codec_crate_path_inner(attr).is_none()
417 {
418 match attr.parse_meta()? {
419 Meta::List(ref meta_list) if meta_list.nested.len() == 1 => {
420 match meta_list.nested.first().expect("Just checked that there is one item; qed") {
421 NestedMeta::Meta(Meta::Path(path))
422 if path.get_ident().map_or(false, |i| i == "dumb_trait_bound") =>
423 Ok(()),
424
425 elt => Err(syn::Error::new(elt.span(), top_error)),
426 }
427 },
428 _ => Err(syn::Error::new(attr.span(), top_error)),
429 }
430 } else {
431 Ok(())
432 }
433}
434
435fn check_repr(attrs: &[syn::Attribute], value: &str) -> bool {
436 let mut result = false;
437 for raw_attr in attrs {
438 let path = raw_attr.path.clone().into_token_stream().to_string();
439 if path != "repr" {
440 continue;
441 }
442
443 result = raw_attr.tokens.clone().into_token_stream().to_string() == value;
444 }
445
446 result
447}
448
449pub fn is_transparent(attrs: &[syn::Attribute]) -> bool {
451 check_repr(attrs, "(transparent)")
453}