referrerpolicy=no-referrer-when-downgrade

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	parenthesized, parse_quote, punctuated::Punctuated, spanned::Spanned, token::And, Attribute,
25	Error, Expr, ExprLit, FnArg, GenericArgument, Ident, ItemImpl, Lit, LitInt, LitStr, Meta,
26	MetaNameValue, Pat, Path, 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 =
78				Box::new(sanitize_pattern((*arg.pat).clone(), &mut generated_pattern_counter));
79		}
80	});
81}
82
83/// Fold the given `Signature` to make it usable on the client side.
84pub fn fold_fn_decl_for_client_side(
85	input: &mut Signature,
86	block_hash: &TokenStream,
87	crate_: &TokenStream,
88) {
89	replace_wild_card_parameter_names(input);
90
91	// Add `&self, at:& Block::Hash` as parameters to each function at the beginning.
92	input.inputs.insert(0, parse_quote!( __runtime_api_at_param__: #block_hash ));
93	input.inputs.insert(0, parse_quote!(&self));
94
95	// Wrap the output in a `Result`
96	input.output = {
97		let ty = return_type_extract_type(&input.output);
98		parse_quote!( -> std::result::Result<#ty, #crate_::ApiError> )
99	};
100}
101
102/// Sanitize the given pattern.
103///
104/// - `_` patterns are changed to a variable based on `counter`.
105/// - `mut something` removes the `mut`.
106pub fn sanitize_pattern(pat: Pat, counter: &mut u32) -> Pat {
107	match pat {
108		Pat::Wild(_) => {
109			let generated_name =
110				Ident::new(&format!("__runtime_api_generated_name_{}__", counter), pat.span());
111			*counter += 1;
112
113			parse_quote!( #generated_name )
114		},
115		Pat::Ident(mut pat) => {
116			pat.mutability = None;
117			pat.into()
118		},
119		_ => pat,
120	}
121}
122
123/// Allow `&self` in parameters of a method.
124pub enum AllowSelfRefInParameters {
125	/// Allows `&self` in parameters, but doesn't return it as part of the parameters.
126	YesButIgnore,
127	No,
128}
129
130/// Extracts the name, the type and `&` or ``(if it is a reference or not)
131/// for each parameter in the given function signature.
132pub fn extract_parameter_names_types_and_borrows(
133	sig: &Signature,
134	allow_self: AllowSelfRefInParameters,
135) -> Result<Vec<(Pat, Type, Option<And>)>> {
136	let mut result = Vec::new();
137	let mut generated_pattern_counter = 0;
138	for input in sig.inputs.iter() {
139		match input {
140			FnArg::Typed(arg) => {
141				let (ty, borrow) = match &*arg.ty {
142					Type::Reference(t) => ((*t.elem).clone(), Some(t.and_token)),
143					t => (t.clone(), None),
144				};
145
146				let name = sanitize_pattern((*arg.pat).clone(), &mut generated_pattern_counter);
147				result.push((name, ty, borrow));
148			},
149			FnArg::Receiver(_) if matches!(allow_self, AllowSelfRefInParameters::No) =>
150				return Err(Error::new(input.span(), "`self` parameter not supported!")),
151			FnArg::Receiver(recv) =>
152				if recv.mutability.is_some() || recv.reference.is_none() {
153					return Err(Error::new(recv.span(), "Only `&self` is supported!"));
154				},
155		}
156	}
157
158	Ok(result)
159}
160
161/// Prefix the given function with the trait name.
162pub fn prefix_function_with_trait<F: ToString>(trait_: &Ident, function: &F) -> String {
163	format!("{}_{}", trait_, function.to_string())
164}
165
166/// Extracts the block type from a trait path.
167///
168/// It is expected that the block type is the first type in the generic arguments.
169pub fn extract_block_type_from_trait_path(trait_: &Path) -> Result<&TypePath> {
170	let span = trait_.span();
171	let generics = trait_
172		.segments
173		.last()
174		.ok_or_else(|| Error::new(span, "Empty path not supported"))?;
175
176	match &generics.arguments {
177		PathArguments::AngleBracketed(ref args) => args
178			.args
179			.first()
180			.and_then(|v| match v {
181				GenericArgument::Type(Type::Path(ref block)) => Some(block),
182				_ => None,
183			})
184			.ok_or_else(|| Error::new(args.span(), "Missing `Block` generic parameter.")),
185		PathArguments::None => {
186			let span = trait_.segments.last().as_ref().unwrap().span();
187			Err(Error::new(span, "Missing `Block` generic parameter."))
188		},
189		PathArguments::Parenthesized(_) =>
190			Err(Error::new(generics.arguments.span(), "Unexpected parentheses in path!")),
191	}
192}
193
194/// Should a qualified trait path be required?
195///
196/// e.g. `path::Trait` is qualified and `Trait` is not.
197pub enum RequireQualifiedTraitPath {
198	Yes,
199	No,
200}
201
202/// Extract the trait that is implemented by the given `ItemImpl`.
203pub fn extract_impl_trait(impl_: &ItemImpl, require: RequireQualifiedTraitPath) -> Result<&Path> {
204	impl_
205		.trait_
206		.as_ref()
207		.map(|v| &v.1)
208		.ok_or_else(|| Error::new(impl_.span(), "Only implementation of traits are supported!"))
209		.and_then(|p| {
210			if p.segments.len() > 1 || matches!(require, RequireQualifiedTraitPath::No) {
211				Ok(p)
212			} else {
213				Err(Error::new(
214					p.span(),
215					"The implemented trait has to be referenced with a path, \
216					e.g. `impl client::Core for Runtime`.",
217				))
218			}
219		})
220}
221
222/// Parse the given attribute as `API_VERSION_ATTRIBUTE`.
223pub fn parse_runtime_api_version(version: &Attribute) -> Result<u32> {
224	let version = version.parse_args::<syn::LitInt>().map_err(|_| {
225		Error::new(
226			version.span(),
227			&format!(
228				"Unexpected `{api_version}` attribute. The supported format is `{api_version}(1)`",
229				api_version = API_VERSION_ATTRIBUTE
230			),
231		)
232	})?;
233
234	version.base10_parse()
235}
236
237/// Each versioned trait is named 'ApiNameVN' where N is the specific version. E.g. ParachainHostV2
238pub fn versioned_trait_name(trait_ident: &Ident, version: u32) -> Ident {
239	format_ident!("{}V{}", trait_ident, version)
240}
241
242/// Extract the documentation from the provided attributes.
243pub fn get_doc_literals(attrs: &[syn::Attribute]) -> Vec<syn::Lit> {
244	use quote::ToTokens;
245	attrs
246		.iter()
247		.filter_map(|attr| {
248			let syn::Meta::NameValue(meta) = &attr.meta else { return None };
249			let Ok(lit) = syn::parse2::<syn::Lit>(meta.value.to_token_stream()) else {
250				unreachable!("non-lit doc attribute values do not exist");
251			};
252			meta.path.get_ident().filter(|ident| *ident == "doc").map(|_| lit)
253		})
254		.collect()
255}
256
257/// Filters all attributes except the cfg ones.
258pub fn filter_cfg_attributes(attrs: &[syn::Attribute]) -> Vec<syn::Attribute> {
259	attrs.iter().filter(|a| a.path().is_ident("cfg")).cloned().collect()
260}
261
262fn deprecation_msg_formatter(msg: &str) -> String {
263	format!(
264		r#"{msg}
265		help: the following are the possible correct uses
266|
267|     #[deprecated = "reason"]
268|
269|     #[deprecated(/*opt*/ since = "version", /*opt*/ note = "reason")]
270|
271|     #[deprecated]
272|"#
273	)
274}
275
276fn parse_deprecated_meta(crate_: &TokenStream, attr: &syn::Attribute) -> Result<TokenStream> {
277	match &attr.meta {
278		Meta::List(meta_list) => {
279			let parsed = meta_list
280				.parse_args_with(Punctuated::<MetaNameValue, Token![,]>::parse_terminated)
281				.map_err(|e| Error::new(attr.span(), e.to_string()))?;
282			let (note, since) = parsed.iter().try_fold((None, None), |mut acc, item| {
283				let value = match &item.value {
284					Expr::Lit(ExprLit { lit: lit @ Lit::Str(_), .. }) => Ok(lit),
285					_ => Err(Error::new(
286						attr.span(),
287						deprecation_msg_formatter(
288							"Invalid deprecation attribute: expected string literal",
289						),
290					)),
291				}?;
292				if item.path.is_ident("note") {
293					acc.0.replace(value);
294				} else if item.path.is_ident("since") {
295					acc.1.replace(value);
296				}
297				Ok::<(Option<&syn::Lit>, Option<&syn::Lit>), Error>(acc)
298			})?;
299			note.map_or_else(
300				|| Err(Error::new(attr.span(), 						deprecation_msg_formatter(
301					"Invalid deprecation attribute: missing `note`"))),
302				|note| {
303					let since = if let Some(str) = since {
304						quote! { Some(#str) }
305					} else {
306						quote! { None }
307					};
308					let doc = quote! { #crate_::metadata_ir::ItemDeprecationInfoIR::Deprecated { note: #note, since: #since }};
309					Ok(doc)
310				},
311			)
312		},
313		Meta::NameValue(MetaNameValue {
314			value: Expr::Lit(ExprLit { lit: lit @ Lit::Str(_), .. }),
315			..
316		}) => {
317			// #[deprecated = "lit"]
318			let doc = quote! { #crate_::metadata_ir::ItemDeprecationInfoIR::Deprecated { note: #lit, since: None } };
319			Ok(doc)
320		},
321		Meta::Path(_) => {
322			// #[deprecated]
323			Ok(quote! { #crate_::metadata_ir::ItemDeprecationInfoIR::DeprecatedWithoutNote })
324		},
325		_ => Err(Error::new(
326			attr.span(),
327			deprecation_msg_formatter("Invalid deprecation attribute: expected string literal"),
328		)),
329	}
330}
331
332/// collects deprecation attribute if its present.
333pub fn get_deprecation(crate_: &TokenStream, attrs: &[syn::Attribute]) -> Result<TokenStream> {
334	attrs
335		.iter()
336		.find(|a| a.path().is_ident("deprecated"))
337		.map(|a| parse_deprecated_meta(&crate_, a))
338		.unwrap_or_else(|| Ok(quote! {#crate_::metadata_ir::ItemDeprecationInfoIR::NotDeprecated}))
339}
340
341/// Represents an API version.
342pub struct ApiVersion {
343	/// Corresponds to `#[api_version(X)]` attribute.
344	pub custom: Option<u32>,
345	/// Corresponds to `#[cfg_attr(feature = "enable-staging-api", api_version(99))]`
346	/// attribute. `String` is the feature name, `u32` the staging api version.
347	pub feature_gated: Option<(String, u32)>,
348}
349
350/// Extracts the value of `API_VERSION_ATTRIBUTE` and handles errors.
351/// Returns:
352/// - Err if the version is malformed
353/// - `ApiVersion` on success. If a version is set or not is determined by the fields of
354///   `ApiVersion`
355pub fn extract_api_version(attrs: &[Attribute], span: Span) -> Result<ApiVersion> {
356	// First fetch all `API_VERSION_ATTRIBUTE` values (should be only one)
357	let api_ver = attrs
358		.iter()
359		.filter(|a| a.path().is_ident(API_VERSION_ATTRIBUTE))
360		.collect::<Vec<_>>();
361
362	if api_ver.len() > 1 {
363		return Err(Error::new(
364			span,
365			format!(
366				"Found multiple #[{}] attributes for an API implementation. \
367				Each runtime API can have only one version.",
368				API_VERSION_ATTRIBUTE
369			),
370		));
371	}
372
373	// Parse the runtime version if there exists one.
374	Ok(ApiVersion {
375		custom: api_ver.first().map(|v| parse_runtime_api_version(v)).transpose()?,
376		feature_gated: extract_cfg_api_version(attrs, span)?,
377	})
378}
379
380/// Parse feature flagged api_version.
381/// E.g. `#[cfg_attr(feature = "enable-staging-api", api_version(99))]`
382fn extract_cfg_api_version(attrs: &[Attribute], span: Span) -> Result<Option<(String, u32)>> {
383	let cfg_attrs = attrs.iter().filter(|a| a.path().is_ident("cfg_attr")).collect::<Vec<_>>();
384
385	let mut cfg_api_version_attr = Vec::new();
386	for cfg_attr in cfg_attrs {
387		let mut feature_name = None;
388		let mut api_version = None;
389		cfg_attr.parse_nested_meta(|m| {
390			if m.path.is_ident("feature") {
391				let a = m.value()?;
392				let b: LitStr = a.parse()?;
393				feature_name = Some(b.value());
394			} else if m.path.is_ident(API_VERSION_ATTRIBUTE) {
395				let content;
396				parenthesized!(content in m.input);
397				let ver: LitInt = content.parse()?;
398				api_version = Some(ver.base10_parse::<u32>()?);
399			}
400			Ok(())
401		})?;
402
403		// If there is a cfg attribute containing api_version - save if for processing
404		if let (Some(feature_name), Some(api_version)) = (feature_name, api_version) {
405			cfg_api_version_attr.push((feature_name, api_version, cfg_attr.span()));
406		}
407	}
408
409	if cfg_api_version_attr.len() > 1 {
410		let mut err = Error::new(span, format!("Found multiple feature gated api versions (cfg attribute with nested `{}` attribute). This is not supported.", API_VERSION_ATTRIBUTE));
411		for (_, _, attr_span) in cfg_api_version_attr {
412			err.combine(Error::new(attr_span, format!("`{}` found here", API_VERSION_ATTRIBUTE)));
413		}
414
415		return Err(err);
416	}
417
418	Ok(cfg_api_version_attr
419		.into_iter()
420		.next()
421		.map(|(feature, name, _)| (feature, name)))
422}
423
424#[cfg(test)]
425mod tests {
426	use assert_matches::assert_matches;
427
428	use super::*;
429
430	#[test]
431	fn check_get_doc_literals() {
432		const FIRST: &'static str = "hello";
433		const SECOND: &'static str = "WORLD";
434
435		let doc: Attribute = parse_quote!(#[doc = #FIRST]);
436		let doc_world: Attribute = parse_quote!(#[doc = #SECOND]);
437
438		let attrs = vec![
439			doc.clone(),
440			parse_quote!(#[derive(Debug)]),
441			parse_quote!(#[test]),
442			parse_quote!(#[allow(non_camel_case_types)]),
443			doc_world.clone(),
444		];
445
446		let docs = get_doc_literals(&attrs);
447		assert_eq!(docs.len(), 2);
448		assert_matches!(&docs[0], syn::Lit::Str(val) if val.value() == FIRST);
449		assert_matches!(&docs[1], syn::Lit::Str(val) if val.value() == SECOND);
450	}
451
452	#[test]
453	fn check_filter_cfg_attributes() {
454		let cfg_std: Attribute = parse_quote!(#[cfg(feature = "std")]);
455		let cfg_benchmarks: Attribute = parse_quote!(#[cfg(feature = "runtime-benchmarks")]);
456
457		let attrs = vec![
458			cfg_std.clone(),
459			parse_quote!(#[derive(Debug)]),
460			parse_quote!(#[test]),
461			cfg_benchmarks.clone(),
462			parse_quote!(#[allow(non_camel_case_types)]),
463		];
464
465		let filtered = filter_cfg_attributes(&attrs);
466		assert_eq!(filtered.len(), 2);
467		assert_eq!(cfg_std, filtered[0]);
468		assert_eq!(cfg_benchmarks, filtered[1]);
469	}
470
471	#[test]
472	fn check_deprecated_attr() {
473		const FIRST: &'static str = "hello";
474		const SECOND: &'static str = "WORLD";
475
476		let simple: Attribute = parse_quote!(#[deprecated]);
477		let simple_path: Attribute = parse_quote!(#[deprecated = #FIRST]);
478		let meta_list: Attribute = parse_quote!(#[deprecated(note = #FIRST)]);
479		let meta_list_with_since: Attribute =
480			parse_quote!(#[deprecated(note = #FIRST, since = #SECOND)]);
481		let extra_fields: Attribute =
482			parse_quote!(#[deprecated(note = #FIRST, since = #SECOND, extra = "Test")]);
483		assert_eq!(
484			get_deprecation(&quote! { crate }, &[simple]).unwrap().to_string(),
485			quote! { crate::metadata_ir::ItemDeprecationInfoIR::DeprecatedWithoutNote }.to_string()
486		);
487		assert_eq!(
488			get_deprecation(&quote! { crate }, &[simple_path]).unwrap().to_string(),
489			quote! { crate::metadata_ir::ItemDeprecationInfoIR::Deprecated { note: #FIRST, since: None } }.to_string()
490		);
491		assert_eq!(
492			get_deprecation(&quote! { crate }, &[meta_list]).unwrap().to_string(),
493			quote! { crate::metadata_ir::ItemDeprecationInfoIR::Deprecated { note: #FIRST, since: None } }.to_string()
494		);
495		assert_eq!(
496			get_deprecation(&quote! { crate }, &[meta_list_with_since]).unwrap().to_string(),
497			quote! { crate::metadata_ir::ItemDeprecationInfoIR::Deprecated { note: #FIRST, since: Some(#SECOND) }}.to_string()
498		);
499		assert_eq!(
500			get_deprecation(&quote! { crate }, &[extra_fields]).unwrap().to_string(),
501			quote! { crate::metadata_ir::ItemDeprecationInfoIR::Deprecated { note: #FIRST, since: Some(#SECOND) }}.to_string()
502		);
503	}
504}