sp_api_proc_macro/
utils.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: Apache-2.0
5
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// 	http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18use crate::common::API_VERSION_ATTRIBUTE;
19use inflector::Inflector;
20use proc_macro2::{Span, TokenStream};
21use proc_macro_crate::{crate_name, FoundCrate};
22use quote::{format_ident, quote};
23use syn::{
24	parse_quote, punctuated::Punctuated, spanned::Spanned, token::And, Attribute, Error, Expr,
25	ExprLit, FnArg, GenericArgument, Ident, ItemImpl, Lit, Meta, MetaNameValue, Pat, Path,
26	PathArguments, Result, ReturnType, Signature, Token, Type, TypePath,
27};
28
29/// Generates the access to the `sc_client` crate.
30pub fn generate_crate_access() -> TokenStream {
31	match crate_name("sp-api") {
32		Ok(FoundCrate::Itself) => quote!(sp_api::__private),
33		Ok(FoundCrate::Name(renamed_name)) => {
34			let renamed_name = Ident::new(&renamed_name, Span::call_site());
35			quote!(#renamed_name::__private)
36		},
37		Err(e) => {
38			if let Ok(FoundCrate::Name(name)) =
39				crate_name(&"polkadot-sdk-frame").or_else(|_| crate_name(&"frame"))
40			{
41				let path = format!("{}::deps::sp_api::__private", name);
42				let path = syn::parse_str::<syn::Path>(&path).expect("is a valid path; qed");
43				quote!( #path )
44			} else if let Ok(FoundCrate::Name(name)) = crate_name(&"polkadot-sdk") {
45				let path = format!("{}::sp_api::__private", name);
46				let path = syn::parse_str::<syn::Path>(&path).expect("is a valid path; qed");
47				quote!( #path )
48			} else {
49				let err = Error::new(Span::call_site(), e).to_compile_error();
50				quote!( #err )
51			}
52		},
53	}
54}
55
56/// Generates the name of the module that contains the trait declaration for the runtime.
57pub fn generate_runtime_mod_name_for_trait(trait_: &Ident) -> Ident {
58	Ident::new(
59		&format!("runtime_decl_for_{}", trait_.to_string().to_snake_case()),
60		Span::call_site(),
61	)
62}
63
64/// Get the type of a `syn::ReturnType`.
65pub fn return_type_extract_type(rt: &ReturnType) -> Type {
66	match rt {
67		ReturnType::Default => parse_quote!(()),
68		ReturnType::Type(_, ref ty) => *ty.clone(),
69	}
70}
71
72/// Replace the `_` (wild card) parameter names in the given signature with unique identifiers.
73pub fn replace_wild_card_parameter_names(input: &mut Signature) {
74	let mut generated_pattern_counter = 0;
75	input.inputs.iter_mut().for_each(|arg| {
76		if let FnArg::Typed(arg) = arg {
77			arg.pat = Box::new(generate_unique_pattern(
78				(*arg.pat).clone(),
79				&mut generated_pattern_counter,
80			));
81		}
82	});
83}
84
85/// Fold the given `Signature` to make it usable on the client side.
86pub fn fold_fn_decl_for_client_side(
87	input: &mut Signature,
88	block_hash: &TokenStream,
89	crate_: &TokenStream,
90) {
91	replace_wild_card_parameter_names(input);
92
93	// Add `&self, at:& Block::Hash` as parameters to each function at the beginning.
94	input.inputs.insert(0, parse_quote!( __runtime_api_at_param__: #block_hash ));
95	input.inputs.insert(0, parse_quote!(&self));
96
97	// Wrap the output in a `Result`
98	input.output = {
99		let ty = return_type_extract_type(&input.output);
100		parse_quote!( -> std::result::Result<#ty, #crate_::ApiError> )
101	};
102}
103
104/// Generate an unique pattern based on the given counter, if the given pattern is a `_`.
105pub fn generate_unique_pattern(pat: Pat, counter: &mut u32) -> Pat {
106	match pat {
107		Pat::Wild(_) => {
108			let generated_name =
109				Ident::new(&format!("__runtime_api_generated_name_{}__", counter), pat.span());
110			*counter += 1;
111
112			parse_quote!( #generated_name )
113		},
114		_ => pat,
115	}
116}
117
118/// Allow `&self` in parameters of a method.
119pub enum AllowSelfRefInParameters {
120	/// Allows `&self` in parameters, but doesn't return it as part of the parameters.
121	YesButIgnore,
122	No,
123}
124
125/// Extracts the name, the type and `&` or ``(if it is a reference or not)
126/// for each parameter in the given function signature.
127pub fn extract_parameter_names_types_and_borrows(
128	sig: &Signature,
129	allow_self: AllowSelfRefInParameters,
130) -> Result<Vec<(Pat, Type, Option<And>)>> {
131	let mut result = Vec::new();
132	let mut generated_pattern_counter = 0;
133	for input in sig.inputs.iter() {
134		match input {
135			FnArg::Typed(arg) => {
136				let (ty, borrow) = match &*arg.ty {
137					Type::Reference(t) => ((*t.elem).clone(), Some(t.and_token)),
138					t => (t.clone(), None),
139				};
140
141				let name =
142					generate_unique_pattern((*arg.pat).clone(), &mut generated_pattern_counter);
143				result.push((name, ty, borrow));
144			},
145			FnArg::Receiver(_) if matches!(allow_self, AllowSelfRefInParameters::No) =>
146				return Err(Error::new(input.span(), "`self` parameter not supported!")),
147			FnArg::Receiver(recv) =>
148				if recv.mutability.is_some() || recv.reference.is_none() {
149					return Err(Error::new(recv.span(), "Only `&self` is supported!"));
150				},
151		}
152	}
153
154	Ok(result)
155}
156
157/// Prefix the given function with the trait name.
158pub fn prefix_function_with_trait<F: ToString>(trait_: &Ident, function: &F) -> String {
159	format!("{}_{}", trait_, function.to_string())
160}
161
162/// Extracts the block type from a trait path.
163///
164/// It is expected that the block type is the first type in the generic arguments.
165pub fn extract_block_type_from_trait_path(trait_: &Path) -> Result<&TypePath> {
166	let span = trait_.span();
167	let generics = trait_
168		.segments
169		.last()
170		.ok_or_else(|| Error::new(span, "Empty path not supported"))?;
171
172	match &generics.arguments {
173		PathArguments::AngleBracketed(ref args) => args
174			.args
175			.first()
176			.and_then(|v| match v {
177				GenericArgument::Type(Type::Path(ref block)) => Some(block),
178				_ => None,
179			})
180			.ok_or_else(|| Error::new(args.span(), "Missing `Block` generic parameter.")),
181		PathArguments::None => {
182			let span = trait_.segments.last().as_ref().unwrap().span();
183			Err(Error::new(span, "Missing `Block` generic parameter."))
184		},
185		PathArguments::Parenthesized(_) =>
186			Err(Error::new(generics.arguments.span(), "Unexpected parentheses in path!")),
187	}
188}
189
190/// Should a qualified trait path be required?
191///
192/// e.g. `path::Trait` is qualified and `Trait` is not.
193pub enum RequireQualifiedTraitPath {
194	Yes,
195	No,
196}
197
198/// Extract the trait that is implemented by the given `ItemImpl`.
199pub fn extract_impl_trait(impl_: &ItemImpl, require: RequireQualifiedTraitPath) -> Result<&Path> {
200	impl_
201		.trait_
202		.as_ref()
203		.map(|v| &v.1)
204		.ok_or_else(|| Error::new(impl_.span(), "Only implementation of traits are supported!"))
205		.and_then(|p| {
206			if p.segments.len() > 1 || matches!(require, RequireQualifiedTraitPath::No) {
207				Ok(p)
208			} else {
209				Err(Error::new(
210					p.span(),
211					"The implemented trait has to be referenced with a path, \
212					e.g. `impl client::Core for Runtime`.",
213				))
214			}
215		})
216}
217
218/// Parse the given attribute as `API_VERSION_ATTRIBUTE`.
219pub fn parse_runtime_api_version(version: &Attribute) -> Result<u64> {
220	let version = version.parse_args::<syn::LitInt>().map_err(|_| {
221		Error::new(
222			version.span(),
223			&format!(
224				"Unexpected `{api_version}` attribute. The supported format is `{api_version}(1)`",
225				api_version = API_VERSION_ATTRIBUTE
226			),
227		)
228	})?;
229
230	version.base10_parse()
231}
232
233/// Each versioned trait is named 'ApiNameVN' where N is the specific version. E.g. ParachainHostV2
234pub fn versioned_trait_name(trait_ident: &Ident, version: u64) -> Ident {
235	format_ident!("{}V{}", trait_ident, version)
236}
237
238/// Extract the documentation from the provided attributes.
239pub fn get_doc_literals(attrs: &[syn::Attribute]) -> Vec<syn::Lit> {
240	use quote::ToTokens;
241	attrs
242		.iter()
243		.filter_map(|attr| {
244			let syn::Meta::NameValue(meta) = &attr.meta else { return None };
245			let Ok(lit) = syn::parse2::<syn::Lit>(meta.value.to_token_stream()) else {
246				unreachable!("non-lit doc attribute values do not exist");
247			};
248			meta.path.get_ident().filter(|ident| *ident == "doc").map(|_| lit)
249		})
250		.collect()
251}
252
253/// Filters all attributes except the cfg ones.
254pub fn filter_cfg_attributes(attrs: &[syn::Attribute]) -> Vec<syn::Attribute> {
255	attrs.iter().filter(|a| a.path().is_ident("cfg")).cloned().collect()
256}
257
258fn deprecation_msg_formatter(msg: &str) -> String {
259	format!(
260		r#"{msg}
261		help: the following are the possible correct uses
262|
263|     #[deprecated = "reason"]
264|
265|     #[deprecated(/*opt*/ since = "version", /*opt*/ note = "reason")]
266|
267|     #[deprecated]
268|"#
269	)
270}
271
272fn parse_deprecated_meta(crate_: &TokenStream, attr: &syn::Attribute) -> Result<TokenStream> {
273	match &attr.meta {
274		Meta::List(meta_list) => {
275			let parsed = meta_list
276				.parse_args_with(Punctuated::<MetaNameValue, Token![,]>::parse_terminated)
277				.map_err(|e| Error::new(attr.span(), e.to_string()))?;
278			let (note, since) = parsed.iter().try_fold((None, None), |mut acc, item| {
279				let value = match &item.value {
280					Expr::Lit(ExprLit { lit: lit @ Lit::Str(_), .. }) => Ok(lit),
281					_ => Err(Error::new(
282						attr.span(),
283						deprecation_msg_formatter(
284							"Invalid deprecation attribute: expected string literal",
285						),
286					)),
287				}?;
288				if item.path.is_ident("note") {
289					acc.0.replace(value);
290				} else if item.path.is_ident("since") {
291					acc.1.replace(value);
292				}
293				Ok::<(Option<&syn::Lit>, Option<&syn::Lit>), Error>(acc)
294			})?;
295			note.map_or_else(
296				|| Err(Error::new(attr.span(), 						deprecation_msg_formatter(
297					"Invalid deprecation attribute: missing `note`"))),
298				|note| {
299					let since = if let Some(str) = since {
300						quote! { Some(#str) }
301					} else {
302						quote! { None }
303					};
304					let doc = quote! { #crate_::metadata_ir::DeprecationStatusIR::Deprecated { note: #note, since: #since }};
305					Ok(doc)
306				},
307			)
308		},
309		Meta::NameValue(MetaNameValue {
310			value: Expr::Lit(ExprLit { lit: lit @ Lit::Str(_), .. }),
311			..
312		}) => {
313			// #[deprecated = "lit"]
314			let doc = quote! { #crate_::metadata_ir::DeprecationStatusIR::Deprecated { note: #lit, since: None } };
315			Ok(doc)
316		},
317		Meta::Path(_) => {
318			// #[deprecated]
319			Ok(quote! { #crate_::metadata_ir::DeprecationStatusIR::DeprecatedWithoutNote })
320		},
321		_ => Err(Error::new(
322			attr.span(),
323			deprecation_msg_formatter("Invalid deprecation attribute: expected string literal"),
324		)),
325	}
326}
327
328/// collects deprecation attribute if its present.
329pub fn get_deprecation(crate_: &TokenStream, attrs: &[syn::Attribute]) -> Result<TokenStream> {
330	attrs
331		.iter()
332		.find(|a| a.path().is_ident("deprecated"))
333		.map(|a| parse_deprecated_meta(&crate_, a))
334		.unwrap_or_else(|| Ok(quote! {#crate_::metadata_ir::DeprecationStatusIR::NotDeprecated}))
335}
336
337#[cfg(test)]
338mod tests {
339	use assert_matches::assert_matches;
340
341	use super::*;
342
343	#[test]
344	fn check_get_doc_literals() {
345		const FIRST: &'static str = "hello";
346		const SECOND: &'static str = "WORLD";
347
348		let doc: Attribute = parse_quote!(#[doc = #FIRST]);
349		let doc_world: Attribute = parse_quote!(#[doc = #SECOND]);
350
351		let attrs = vec![
352			doc.clone(),
353			parse_quote!(#[derive(Debug)]),
354			parse_quote!(#[test]),
355			parse_quote!(#[allow(non_camel_case_types)]),
356			doc_world.clone(),
357		];
358
359		let docs = get_doc_literals(&attrs);
360		assert_eq!(docs.len(), 2);
361		assert_matches!(&docs[0], syn::Lit::Str(val) if val.value() == FIRST);
362		assert_matches!(&docs[1], syn::Lit::Str(val) if val.value() == SECOND);
363	}
364
365	#[test]
366	fn check_filter_cfg_attributes() {
367		let cfg_std: Attribute = parse_quote!(#[cfg(feature = "std")]);
368		let cfg_benchmarks: Attribute = parse_quote!(#[cfg(feature = "runtime-benchmarks")]);
369
370		let attrs = vec![
371			cfg_std.clone(),
372			parse_quote!(#[derive(Debug)]),
373			parse_quote!(#[test]),
374			cfg_benchmarks.clone(),
375			parse_quote!(#[allow(non_camel_case_types)]),
376		];
377
378		let filtered = filter_cfg_attributes(&attrs);
379		assert_eq!(filtered.len(), 2);
380		assert_eq!(cfg_std, filtered[0]);
381		assert_eq!(cfg_benchmarks, filtered[1]);
382	}
383
384	#[test]
385	fn check_deprecated_attr() {
386		const FIRST: &'static str = "hello";
387		const SECOND: &'static str = "WORLD";
388
389		let simple: Attribute = parse_quote!(#[deprecated]);
390		let simple_path: Attribute = parse_quote!(#[deprecated = #FIRST]);
391		let meta_list: Attribute = parse_quote!(#[deprecated(note = #FIRST)]);
392		let meta_list_with_since: Attribute =
393			parse_quote!(#[deprecated(note = #FIRST, since = #SECOND)]);
394		let extra_fields: Attribute =
395			parse_quote!(#[deprecated(note = #FIRST, since = #SECOND, extra = "Test")]);
396		assert_eq!(
397			get_deprecation(&quote! { crate }, &[simple]).unwrap().to_string(),
398			quote! { crate::metadata_ir::DeprecationStatusIR::DeprecatedWithoutNote }.to_string()
399		);
400		assert_eq!(
401			get_deprecation(&quote! { crate }, &[simple_path]).unwrap().to_string(),
402			quote! { crate::metadata_ir::DeprecationStatusIR::Deprecated { note: #FIRST, since: None } }.to_string()
403		);
404		assert_eq!(
405			get_deprecation(&quote! { crate }, &[meta_list]).unwrap().to_string(),
406			quote! { crate::metadata_ir::DeprecationStatusIR::Deprecated { note: #FIRST, since: None } }.to_string()
407		);
408		assert_eq!(
409			get_deprecation(&quote! { crate }, &[meta_list_with_since]).unwrap().to_string(),
410			quote! { crate::metadata_ir::DeprecationStatusIR::Deprecated { note: #FIRST, since: Some(#SECOND) }}.to_string()
411		);
412		assert_eq!(
413			get_deprecation(&quote! { crate }, &[extra_fields]).unwrap().to_string(),
414			quote! { crate::metadata_ir::DeprecationStatusIR::Deprecated { note: #FIRST, since: Some(#SECOND) }}.to_string()
415		);
416	}
417}