frame_support_procedural/
deprecation.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 proc_macro2::TokenStream;
19use quote::quote;
20use syn::{
21	punctuated::Punctuated, spanned::Spanned, Error, Expr, ExprLit, Lit, Meta, MetaNameValue,
22	Result, Token, Variant,
23};
24
25fn deprecation_msg_formatter(msg: &str) -> String {
26	format!(
27		r#"{msg}
28		help: the following are the possible correct uses
29|
30|     #[deprecated = "reason"]
31|
32|     #[deprecated(/*opt*/ since = "version", /*opt*/ note = "reason")]
33|
34|     #[deprecated]
35|"#
36	)
37}
38
39fn parse_deprecated_meta(crate_: &TokenStream, attr: &syn::Attribute) -> Result<TokenStream> {
40	match &attr.meta {
41		Meta::List(meta_list) => {
42			let parsed = meta_list
43				.parse_args_with(Punctuated::<MetaNameValue, Token![,]>::parse_terminated)
44				.map_err(|e| Error::new(attr.span(), e.to_string()))?;
45			let (note, since) = parsed.iter().try_fold((None, None), |mut acc, item| {
46				let value = match &item.value {
47					Expr::Lit(ExprLit { lit: lit @ Lit::Str(_), .. }) => Ok(lit),
48					_ => Err(Error::new(
49						attr.span(),
50						deprecation_msg_formatter(
51							"Invalid deprecation attribute: expected string literal",
52						),
53					)),
54				}?;
55				if item.path.is_ident("note") {
56					acc.0.replace(value);
57				} else if item.path.is_ident("since") {
58					acc.1.replace(value);
59				}
60				Ok::<(Option<&syn::Lit>, Option<&syn::Lit>), Error>(acc)
61			})?;
62			note.map_or_else(
63		  || Err(Error::new(attr.span(), deprecation_msg_formatter("Invalid deprecation attribute: missing `note`"))),
64				|note| {
65					let since = if let Some(str) = since {
66						quote! { Some(#str) }
67					} else {
68						quote! { None }
69					};
70					let doc = quote! { #crate_::__private::metadata_ir::DeprecationStatusIR::Deprecated { note: #note, since: #since }};
71					Ok(doc)
72				},
73			)
74		},
75		Meta::NameValue(MetaNameValue {
76			value: Expr::Lit(ExprLit { lit: lit @ Lit::Str(_), .. }),
77			..
78		}) => {
79			// #[deprecated = "lit"]
80			let doc = quote! { #crate_::__private::metadata_ir::DeprecationStatusIR::Deprecated { note: #lit, since: None } };
81			Ok(doc)
82		},
83		Meta::Path(_) => {
84			// #[deprecated]
85			Ok(
86				quote! { #crate_::__private::metadata_ir::DeprecationStatusIR::DeprecatedWithoutNote },
87			)
88		},
89		_ => Err(Error::new(
90			attr.span(),
91			deprecation_msg_formatter("Invalid deprecation attribute: expected string literal"),
92		)),
93	}
94}
95
96/// collects deprecation attribute if its present.
97pub fn get_deprecation(path: &TokenStream, attrs: &[syn::Attribute]) -> Result<TokenStream> {
98	parse_deprecation(path, attrs).map(|item| {
99		item.unwrap_or_else(|| {
100			quote! {#path::__private::metadata_ir::DeprecationStatusIR::NotDeprecated}
101		})
102	})
103}
104
105fn parse_deprecation(path: &TokenStream, attrs: &[syn::Attribute]) -> Result<Option<TokenStream>> {
106	attrs
107		.iter()
108		.find(|a| a.path().is_ident("deprecated"))
109		.map(|a| parse_deprecated_meta(path, a))
110		.transpose()
111}
112
113/// collects deprecation attribute if its present for enum-like types
114pub fn get_deprecation_enum<'a>(
115	path: &TokenStream,
116	parent_attrs: &[syn::Attribute],
117	children_attrs: impl Iterator<Item = (u8, &'a [syn::Attribute])>,
118) -> Result<TokenStream> {
119	let parent_deprecation = parse_deprecation(path, parent_attrs)?;
120
121	let children = children_attrs
122		.filter_map(|(key, attributes)| {
123			let key = quote::quote! { #path::__private::codec::Compact(#key as u8) };
124			let deprecation_status = parse_deprecation(path, attributes).transpose();
125			deprecation_status.map(|item| item.map(|item| quote::quote! { (#key, #item) }))
126		})
127		.collect::<Result<Vec<TokenStream>>>()?;
128	match (parent_deprecation, children.as_slice()) {
129		(None, []) =>
130			Ok(quote::quote! { #path::__private::metadata_ir::DeprecationInfoIR::NotDeprecated }),
131		(None, _) => {
132			let children = quote::quote! { #path::__private::scale_info::prelude::collections::BTreeMap::from([#( #children),*]) };
133			Ok(
134				quote::quote! { #path::__private::metadata_ir::DeprecationInfoIR::VariantsDeprecated(#children) },
135			)
136		},
137		(Some(depr), _) => Ok(
138			quote::quote! { #path::__private::metadata_ir::DeprecationInfoIR::ItemDeprecated(#depr) },
139		),
140	}
141}
142
143/// Gets the index for the variant inside `Error` or `Event` declaration.
144/// priority is as follows:
145/// Manual `#[codec(index = N)]`
146/// Explicit discriminant `Variant = N`
147/// Variant's definition index
148pub fn variant_index_for_deprecation(index: u8, item: &Variant) -> u8 {
149	let index: u8 =
150		if let Some((_, Expr::Lit(ExprLit { lit: Lit::Int(num_lit), .. }))) = &item.discriminant {
151			num_lit.base10_parse::<u8>().unwrap_or(index as u8)
152		} else {
153			index as u8
154		};
155
156	item.attrs
157		.iter()
158		.find(|attr| attr.path().is_ident("codec"))
159		.and_then(|attr| {
160			if let Meta::List(meta_list) = &attr.meta {
161				meta_list
162					.parse_args_with(Punctuated::<MetaNameValue, syn::Token![,]>::parse_terminated)
163					.ok()
164			} else {
165				None
166			}
167		})
168		.and_then(|parsed| {
169			parsed.iter().fold(None, |mut acc, item| {
170				if let Expr::Lit(ExprLit { lit: Lit::Int(num_lit), .. }) = &item.value {
171					num_lit.base10_parse::<u8>().iter().for_each(|val| {
172						if item.path.is_ident("index") {
173							acc.replace(*val);
174						}
175					})
176				};
177				acc
178			})
179		})
180		.unwrap_or(index)
181}