1use 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
29pub 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
56pub 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
64pub 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
72pub 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
85pub 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 input.inputs.insert(0, parse_quote!( __runtime_api_at_param__: #block_hash ));
95 input.inputs.insert(0, parse_quote!(&self));
96
97 input.output = {
99 let ty = return_type_extract_type(&input.output);
100 parse_quote!( -> std::result::Result<#ty, #crate_::ApiError> )
101 };
102}
103
104pub 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
118pub enum AllowSelfRefInParameters {
120 YesButIgnore,
122 No,
123}
124
125pub 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
157pub fn prefix_function_with_trait<F: ToString>(trait_: &Ident, function: &F) -> String {
159 format!("{}_{}", trait_, function.to_string())
160}
161
162pub 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
190pub enum RequireQualifiedTraitPath {
194 Yes,
195 No,
196}
197
198pub 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
218pub 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
233pub fn versioned_trait_name(trait_ident: &Ident, version: u64) -> Ident {
235 format_ident!("{}V{}", trait_ident, version)
236}
237
238pub 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
253pub 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 let doc = quote! { #crate_::metadata_ir::DeprecationStatusIR::Deprecated { note: #lit, since: None } };
315 Ok(doc)
316 },
317 Meta::Path(_) => {
318 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
328pub 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("e! { crate }, &[simple]).unwrap().to_string(),
398 quote! { crate::metadata_ir::DeprecationStatusIR::DeprecatedWithoutNote }.to_string()
399 );
400 assert_eq!(
401 get_deprecation("e! { 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("e! { 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("e! { 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("e! { crate }, &[extra_fields]).unwrap().to_string(),
414 quote! { crate::metadata_ir::DeprecationStatusIR::Deprecated { note: #FIRST, since: Some(#SECOND) }}.to_string()
415 );
416 }
417}