utils.rs 11.6 KB
Newer Older
David's avatar
David committed
1
// Copyright 2018-2020 Parity Technologies
2
3
4
5
6
7
8
9
10
11
12
13
14
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

15
16
//! Various internal utils.
//!
David's avatar
David committed
17
18
//! NOTE: attributes finder must be checked using check_attribute first,
//! otherwise the macro can panic.
19

20
use std::str::FromStr;
21

22
23
24
use proc_macro_crate::{crate_name, FoundCrate};
use proc_macro2::{Span, Ident, TokenStream};
use quote::quote;
Qinxuan Chen's avatar
Qinxuan Chen committed
25
use syn::{
26
27
28
	Attribute, Data, DeriveInput, Error, Field, Fields, FieldsNamed, FieldsUnnamed, Lit, Meta,
	MetaNameValue, NestedMeta, parse::Parse, Path, punctuated::Punctuated,
	spanned::Spanned, token, Variant,
Qinxuan Chen's avatar
Qinxuan Chen committed
29
30
};

31
32
33
34
fn find_meta_item<'a, F, R, I, M>(mut itr: I, mut pred: F) -> Option<R> where
	F: FnMut(M) -> Option<R> + Clone,
	I: Iterator<Item=&'a Attribute>,
	M: Parse,
35
{
36
	itr.find_map(|attr| attr.path.is_ident("codec").then(|| pred(attr.parse_args().ok()?)).flatten())
37
38
}

David's avatar
David committed
39
40
41
42
/// Look for a `#[scale(index = $int)]` attribute on a variant. If no attribute
/// is found, fall back to the discriminant or just the variant index.
pub fn variant_index(v: &Variant, i: usize) -> TokenStream {
	// first look for an attribute
43
44
	let index = find_meta_item(v.attrs.iter(), |meta| {
		if let NestedMeta::Meta(Meta::NameValue(ref nv)) = meta {
Qinxuan Chen's avatar
Qinxuan Chen committed
45
			if nv.path.is_ident("index") {
46
47
48
				if let Lit::Int(ref v) = nv.lit {
					let byte = v.base10_parse::<u8>()
						.expect("Internal error, index attribute must have been checked");
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
					return Some(byte)
				}
			}
		}

		None
	});

	// then fallback to discriminant or just index
	index.map(|i| quote! { #i })
		.unwrap_or_else(|| v.discriminant
			.as_ref()
			.map(|&(_, ref expr)| quote! { #expr })
			.unwrap_or_else(|| quote! { #i })
		)
}

David's avatar
David committed
66
67
68
69
/// Look for a `#[codec(encoded_as = "SomeType")]` outer attribute on the given
/// `Field`.
pub fn get_encoded_as_type(field: &Field) -> Option<TokenStream> {
	find_meta_item(field.attrs.iter(), |meta| {
70
		if let NestedMeta::Meta(Meta::NameValue(ref nv)) = meta {
Qinxuan Chen's avatar
Qinxuan Chen committed
71
			if nv.path.is_ident("encoded_as") {
72
				if let Lit::Str(ref s) = nv.lit {
73
74
					return Some(
						TokenStream::from_str(&s.value())
75
							.expect("Internal error, encoded_as attribute must have been checked")
76
					);
77
78
79
80
81
82
83
84
				}
			}
		}

		None
	})
}

David's avatar
David committed
85
86
87
/// Look for a `#[codec(compact)]` outer attribute on the given `Field`.
pub fn is_compact(field: &Field) -> bool {
	find_meta_item(field.attrs.iter(), |meta| {
Qinxuan Chen's avatar
Qinxuan Chen committed
88
89
		if let NestedMeta::Meta(Meta::Path(ref path)) = meta {
			if path.is_ident("compact") {
90
91
92
93
94
95
96
				return Some(());
			}
		}

		None
	}).is_some()
}
thiolliere's avatar
thiolliere committed
97

David's avatar
David committed
98
99
/// Look for a `#[codec(skip)]` in the given attributes.
pub fn should_skip(attrs: &[Attribute]) -> bool {
thiolliere's avatar
thiolliere committed
100
	find_meta_item(attrs.iter(), |meta| {
Qinxuan Chen's avatar
Qinxuan Chen committed
101
102
103
		if let NestedMeta::Meta(Meta::Path(ref path)) = meta {
			if path.is_ident("skip") {
				return Some(path.span());
thiolliere's avatar
thiolliere committed
104
105
106
107
			}
		}

		None
David's avatar
David committed
108
	}).is_some()
thiolliere's avatar
thiolliere committed
109
}
110

David's avatar
David committed
111
112
/// Look for a `#[codec(dumb_trait_bound)]`in the given attributes.
pub fn has_dumb_trait_bound(attrs: &[Attribute]) -> bool {
113
	find_meta_item(attrs.iter(), |meta| {
Qinxuan Chen's avatar
Qinxuan Chen committed
114
115
		if let NestedMeta::Meta(Meta::Path(ref path)) = meta {
			if path.is_ident("dumb_trait_bound") {
116
117
118
119
120
121
122
123
				return Some(());
			}
		}

		None
	}).is_some()
}

124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
/// Generate the crate access for the crate using 2018 syntax.
fn crate_access() -> syn::Result<Ident> {
	const DEF_CRATE: &str = "parity-scale-codec";
	match crate_name(DEF_CRATE) {
		Ok(FoundCrate::Itself) => {
			let name = DEF_CRATE.to_string().replace("-", "_");
			Ok(syn::Ident::new(&name, Span::call_site()))
		}
		Ok(FoundCrate::Name(name)) => Ok(Ident::new(&name, Span::call_site())),
		Err(e) => Err(Error::new(Span::call_site(), e)),
	}
}

/// Match `#[codec(crate = ...)]` and return the `...`
fn codec_crate_path_lit(attr: &Attribute) -> Option<Lit> {
	// match `#[codec ...]`
	if !attr.path.is_ident("codec") {
		return None;
	};
	// match `#[codec(crate = ...)]` and return the `...`
	match attr.parse_meta() {
		Ok(Meta::NameValue(MetaNameValue { path, lit, .. })) if path.is_ident("crate") => {
			Some(lit)
		}
		_ => None,
	}
}

/// Match `#[codec(crate = "...")]` and return the contents as a `Path`
pub fn codec_crate_path(attrs: &[Attribute]) -> syn::Result<Path> {
	match attrs.iter().find_map(codec_crate_path_lit) {
		Some(Lit::Str(lit_str)) => lit_str.parse::<Path>(),
		Some(lit) => {
			Err(Error::new(
				lit.span(),
				"Expected format: #[codec(crate = \"path::to::codec\")]",
			))
		}
		None => crate_access().map(|ident| ident.into()),
	}
}

166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
/// Trait bounds.
pub type TraitBounds = Punctuated<syn::WherePredicate, token::Comma>;

/// Parse `name(T: Bound, N: Bound)` as a custom trait bound.
struct CustomTraitBound<N> {
	_name: N,
	_paren_token: token::Paren,
	bounds: TraitBounds,
}

impl<N: Parse> Parse for CustomTraitBound<N> {
	fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
		let content;
		Ok(Self {
			_name: input.parse()?,
			_paren_token: syn::parenthesized!(content in input),
			bounds: content.parse_terminated(syn::WherePredicate::parse)?,
		})
	}
}

syn::custom_keyword!(encode_bound);
syn::custom_keyword!(decode_bound);

/// Look for a `#[codec(decode_bound(T: Decode))]`in the given attributes.
///
/// If found, it should be used as trait bounds when deriving the `Decode` trait.
pub fn custom_decode_trait_bound(attrs: &[Attribute]) -> Option<TraitBounds> {
	find_meta_item(attrs.iter(), |meta: CustomTraitBound<decode_bound>| {
		Some(meta.bounds)
	})
}

/// Look for a `#[codec(encode_bound(T: Encode))]`in the given attributes.
///
/// If found, it should be used as trait bounds when deriving the `Encode` trait.
pub fn custom_encode_trait_bound(attrs: &[Attribute]) -> Option<TraitBounds> {
	find_meta_item(attrs.iter(), |meta: CustomTraitBound<encode_bound>| {
		Some(meta.bounds)
	})
}

David's avatar
David committed
208
209
/// Given a set of named fields, return an iterator of `Field` where all fields
/// marked `#[codec(skip)]` are filtered out.
210
211
pub fn filter_skip_named<'a>(fields: &'a syn::FieldsNamed) -> impl Iterator<Item=&Field> + 'a {
	fields.named.iter()
David's avatar
David committed
212
		.filter(|f| !should_skip(&f.attrs))
213
214
}

David's avatar
David committed
215
216
/// Given a set of unnamed fields, return an iterator of `(index, Field)` where all fields
/// marked `#[codec(skip)]` are filtered out.
217
218
219
pub fn filter_skip_unnamed<'a>(fields: &'a syn::FieldsUnnamed) -> impl Iterator<Item=(usize, &Field)> + 'a {
	fields.unnamed.iter()
		.enumerate()
David's avatar
David committed
220
		.filter(|(_, f)| !should_skip(&f.attrs))
221
}
222

David's avatar
David committed
223
224
225
/// Ensure attributes are correctly applied. This *must* be called before using
/// any of the attribute finder methods or the macro may panic if it encounters
/// misapplied attributes.
226
227
228
229
230
231
///
/// The top level can have the following attributes:
///
/// * `#[codec(dumb_trait_bound)]`
/// * `#[codec(crate = "path::to::crate")]
///
David's avatar
David committed
232
/// Fields can have the following attributes:
233
///
David's avatar
David committed
234
235
236
/// * `#[codec(skip)]`
/// * `#[codec(compact)]`
/// * `#[codec(encoded_as = "$EncodeAs")]` with $EncodedAs a valid TokenStream
237
///
David's avatar
David committed
238
/// Variants can have the following attributes:
239
///
David's avatar
David committed
240
241
/// * `#[codec(skip)]`
/// * `#[codec(index = $int)]`
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
pub fn check_attributes(input: &DeriveInput) -> syn::Result<()> {
	for attr in &input.attrs {
		check_top_attribute(attr)?;
	}

	match input.data {
		Data::Struct(ref data) => match &data.fields {
			| Fields::Named(FieldsNamed { named: fields , .. })
			| Fields::Unnamed(FieldsUnnamed { unnamed: fields, .. }) => {
				for field in fields {
					for attr in &field.attrs {
						check_field_attribute(attr)?;
					}
				}
			}
			Fields::Unit => (),
		}
		Data::Enum(ref data) => {
			for variant in data.variants.iter() {
				for attr in &variant.attrs {
					check_variant_attribute(attr)?;
				}
				for field in &variant.fields {
					for attr in &field.attrs {
						check_field_attribute(attr)?;
					}
				}
			}
		},
		Data::Union(_) => (),
	}
	Ok(())
}

276
277
278
279
280
281
282
283
// Check if the attribute is `#[allow(..)]`, `#[deny(..)]`, `#[forbid(..)]` or `#[warn(..)]`.
pub fn is_lint_attribute(attr: &Attribute) -> bool {
	attr.path.is_ident("allow")
		|| attr.path.is_ident("deny")
		|| attr.path.is_ident("forbid")
		|| attr.path.is_ident("warn")
}

David's avatar
David committed
284
// Ensure a field is decorated only with the following attributes:
285
286
287
288
289
290
291
292
293
294
// * `#[codec(skip)]`
// * `#[codec(compact)]`
// * `#[codec(encoded_as = "$EncodeAs")]` with $EncodedAs a valid TokenStream
fn check_field_attribute(attr: &Attribute) -> syn::Result<()> {
	let field_error = "Invalid attribute on field, only `#[codec(skip)]`, `#[codec(compact)]` and \
		`#[codec(encoded_as = \"$EncodeAs\")]` are accepted.";

	if attr.path.is_ident("codec") {
		match attr.parse_meta()? {
			Meta::List(ref meta_list) if meta_list.nested.len() == 1 => {
David's avatar
David committed
295
				match meta_list.nested.first().expect("Just checked that there is one item; qed") {
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
					NestedMeta::Meta(Meta::Path(path))
						if path.get_ident().map_or(false, |i| i == "skip") => Ok(()),

					NestedMeta::Meta(Meta::Path(path))
						if path.get_ident().map_or(false, |i| i == "compact") => Ok(()),

					NestedMeta::Meta(Meta::NameValue(MetaNameValue { path, lit: Lit::Str(lit_str), .. }))
						if path.get_ident().map_or(false, |i| i == "encoded_as")
					=> TokenStream::from_str(&lit_str.value()).map(|_| ())
						.map_err(|_e| syn::Error::new(lit_str.span(), "Invalid token stream")),

					elt @ _ => Err(syn::Error::new(elt.span(), field_error)),
				}
			},
			meta @ _ => Err(syn::Error::new(meta.span(), field_error)),
		}
	} else {
		Ok(())
	}
}

David's avatar
David committed
317
// Ensure a field is decorated only with the following attributes:
318
319
320
321
322
323
324
325
326
// * `#[codec(skip)]`
// * `#[codec(index = $int)]`
fn check_variant_attribute(attr: &Attribute) -> syn::Result<()> {
	let variant_error = "Invalid attribute on variant, only `#[codec(skip)]` and \
		`#[codec(index = $u8)]` are accepted.";

	if attr.path.is_ident("codec") {
		match attr.parse_meta()? {
			Meta::List(ref meta_list) if meta_list.nested.len() == 1 => {
David's avatar
David committed
327
				match meta_list.nested.first().expect("Just checked that there is one item; qed") {
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
					NestedMeta::Meta(Meta::Path(path))
						if path.get_ident().map_or(false, |i| i == "skip") => Ok(()),

					NestedMeta::Meta(Meta::NameValue(MetaNameValue { path, lit: Lit::Int(lit_int), .. }))
						if path.get_ident().map_or(false, |i| i == "index")
					=> lit_int.base10_parse::<u8>().map(|_| ())
						.map_err(|_| syn::Error::new(lit_int.span(), "Index must be in 0..255")),

					elt @ _ => Err(syn::Error::new(elt.span(), variant_error)),
				}
			},
			meta @ _ => Err(syn::Error::new(meta.span(), variant_error)),
		}
	} else {
		Ok(())
	}
}

// Only `#[codec(dumb_trait_bound)]` is accepted as top attribute
fn check_top_attribute(attr: &Attribute) -> syn::Result<()> {
348
349
	let top_error = "Invalid attribute: only `#[codec(dumb_trait_bound)]`, \
		`#[codec(encode_bound(T: Encode))]`, `#[codec(crate = \"path::to::crate\")]`, or \
350
		`#[codec(decode_bound(T: Decode))]` are accepted as top attribute";
351
352
353
354
355
356
357
358
	if attr.path.is_ident("codec")
		&& attr.parse_args::<CustomTraitBound<encode_bound>>().is_err()
		&& attr.parse_args::<CustomTraitBound<decode_bound>>().is_err()
		&& codec_crate_path_lit(attr).is_none()
	{
		match attr.parse_meta()? {
			Meta::List(ref meta_list) if meta_list.nested.len() == 1 => {
				match meta_list.nested.first().expect("Just checked that there is one item; qed") {
359
360
						NestedMeta::Meta(Meta::Path(path))
							if path.get_ident().map_or(false, |i| i == "dumb_trait_bound") => Ok(()),
361

362
363
364
						elt @ _ => Err(syn::Error::new(elt.span(), top_error)),
					}
			}
365
			_ => Err(syn::Error::new(attr.span(), top_error)),
366
367
368
369
370
		}
	} else {
		Ok(())
	}
}