parity_scale_codec_derive/
utils.rs

1// Copyright 2018-2020 Parity Technologies
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Various internal utils.
16//!
17//! NOTE: attributes finder must be checked using check_attribute first,
18//! otherwise the macro can panic.
19
20use 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
40/// Look for a `#[scale(index = $int)]` attribute on a variant. If no attribute
41/// is found, fall back to the discriminant or just the variant index.
42pub fn variant_index(v: &Variant, i: usize) -> TokenStream {
43	// first look for an attribute
44	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	// then fallback to discriminant or just index
60	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
68/// Look for a `#[codec(encoded_as = "SomeType")]` outer attribute on the given
69/// `Field`.
70pub 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
87/// Look for a `#[codec(compact)]` outer attribute on the given `Field`.
88pub 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
101/// Look for a `#[codec(skip)]` in the given attributes.
102pub 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
115/// Look for a `#[codec(dumb_trait_bound)]`in the given attributes.
116pub 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
129/// Generate the crate access for the crate using 2018 syntax.
130fn 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
144/// This struct matches `crate = ...` where the ellipsis is a `Path`.
145struct 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
167/// Match `#[codec(crate = ...)]` and return the `...` if it is a `Path`.
168fn codec_crate_path_inner(attr: &Attribute) -> Option<Path> {
169	// match `#[codec ...]`
170	attr.path
171		.is_ident("codec")
172		.then(|| {
173			// match `#[codec(crate = ...)]` and return the `...`
174			attr.parse_args::<CratePath>().map(Into::into).ok()
175		})
176		.flatten()
177}
178
179/// Match `#[codec(crate = ...)]` and return the ellipsis as a `Path`.
180///
181/// If not found, returns the default crate access pattern.
182///
183/// If multiple items match the pattern, all but the first are ignored.
184pub 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
191/// Parse `name(T: Bound, N: Bound)` or `name(skip_type_params(T, N))` as a custom trait bound.
192pub 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
235/// Look for a `#[codec(decode_bound(T: Decode))]` in the given attributes.
236///
237/// If found, it should be used as trait bounds when deriving the `Decode` trait.
238pub fn custom_decode_trait_bound(attrs: &[Attribute]) -> Option<CustomTraitBound<decode_bound>> {
239	find_meta_item(attrs.iter(), Some)
240}
241
242/// Look for a `#[codec(encode_bound(T: Encode))]` in the given attributes.
243///
244/// If found, it should be used as trait bounds when deriving the `Encode` trait.
245pub fn custom_encode_trait_bound(attrs: &[Attribute]) -> Option<CustomTraitBound<encode_bound>> {
246	find_meta_item(attrs.iter(), Some)
247}
248
249/// Look for a `#[codec(mel_bound(T: MaxEncodedLen))]` in the given attributes.
250///
251/// If found, it should be used as the trait bounds when deriving the `MaxEncodedLen` trait.
252#[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
257/// Given a set of named fields, return an iterator of `Field` where all fields
258/// marked `#[codec(skip)]` are filtered out.
259pub fn filter_skip_named(fields: &syn::FieldsNamed) -> impl Iterator<Item = &Field> {
260	fields.named.iter().filter(|f| !should_skip(&f.attrs))
261}
262
263/// Given a set of unnamed fields, return an iterator of `(index, Field)` where all fields
264/// marked `#[codec(skip)]` are filtered out.
265pub 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
271/// Ensure attributes are correctly applied. This *must* be called before using
272/// any of the attribute finder methods or the macro may panic if it encounters
273/// misapplied attributes.
274///
275/// The top level can have the following attributes:
276///
277/// * `#[codec(dumb_trait_bound)]`
278/// * `#[codec(encode_bound(T: Encode))]`
279/// * `#[codec(decode_bound(T: Decode))]`
280/// * `#[codec(mel_bound(T: MaxEncodedLen))]`
281/// * `#[codec(crate = path::to::crate)]
282///
283/// Fields can have the following attributes:
284///
285/// * `#[codec(skip)]`
286/// * `#[codec(compact)]`
287/// * `#[codec(encoded_as = "$EncodeAs")]` with $EncodedAs a valid TokenStream
288///
289/// Variants can have the following attributes:
290///
291/// * `#[codec(skip)]`
292/// * `#[codec(index = $int)]`
293pub 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
325// Check if the attribute is `#[allow(..)]`, `#[deny(..)]`, `#[forbid(..)]` or `#[warn(..)]`.
326pub 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
333// Ensure a field is decorated only with the following attributes:
334// * `#[codec(skip)]`
335// * `#[codec(compact)]`
336// * `#[codec(encoded_as = "$EncodeAs")]` with $EncodedAs a valid TokenStream
337fn 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
372// Ensure a field is decorated only with the following attributes:
373// * `#[codec(skip)]`
374// * `#[codec(index = $int)]`
375fn 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
406// Only `#[codec(dumb_trait_bound)]` is accepted as top attribute
407fn 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
449/// Checks whether the given attributes contain a `#[repr(transparent)]`.
450pub fn is_transparent(attrs: &[syn::Attribute]) -> bool {
451	// TODO: When migrating to syn 2 the `"(transparent)"` needs to be changed into `"transparent"`.
452	check_repr(attrs, "(transparent)")
453}