referrerpolicy=no-referrer-when-downgrade

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
39// Should we generate a #crate_::__private::metadata_ir::ItemDeprecationInfoIR
40// or a #crate_::__private::metadata_ir::VariantDeprecationInfoIR? In other words,
41// are we targeting variant deprecation information, or generic item deprecation
42// information.
43#[derive(Copy, Clone, PartialEq)]
44enum DeprecationTarget {
45	Item,
46	Variant,
47}
48
49fn parse_deprecated_meta(
50	crate_: &TokenStream,
51	attr: &syn::Attribute,
52	target: DeprecationTarget,
53) -> Result<TokenStream> {
54	let target = match target {
55		DeprecationTarget::Item =>
56			quote! { #crate_::__private::metadata_ir::ItemDeprecationInfoIR },
57		DeprecationTarget::Variant =>
58			quote! { #crate_::__private::metadata_ir::VariantDeprecationInfoIR },
59	};
60
61	match &attr.meta {
62		Meta::List(meta_list) => {
63			let parsed = meta_list
64				.parse_args_with(Punctuated::<MetaNameValue, Token![,]>::parse_terminated)
65				.map_err(|e| Error::new(attr.span(), e.to_string()))?;
66			let (note, since) = parsed.iter().try_fold((None, None), |mut acc, item| {
67				let value = match &item.value {
68					Expr::Lit(ExprLit { lit: lit @ Lit::Str(_), .. }) => Ok(lit),
69					_ => Err(Error::new(
70						attr.span(),
71						deprecation_msg_formatter(
72							"Invalid deprecation attribute: expected string literal",
73						),
74					)),
75				}?;
76				if item.path.is_ident("note") {
77					acc.0.replace(value);
78				} else if item.path.is_ident("since") {
79					acc.1.replace(value);
80				}
81				Ok::<(Option<&syn::Lit>, Option<&syn::Lit>), Error>(acc)
82			})?;
83			note.map_or_else(
84				|| {
85					Err(Error::new(
86						attr.span(),
87						deprecation_msg_formatter("Invalid deprecation attribute: missing `note`"),
88					))
89				},
90				|note| {
91					let since = if let Some(str) = since {
92						quote! { Some(#str) }
93					} else {
94						quote! { None }
95					};
96					let doc = quote! { #target::Deprecated { note: #note, since: #since }};
97					Ok(doc)
98				},
99			)
100		},
101		Meta::NameValue(MetaNameValue {
102			value: Expr::Lit(ExprLit { lit: lit @ Lit::Str(_), .. }),
103			..
104		}) => {
105			// #[deprecated = "lit"]
106			let doc = quote! { #target::Deprecated { note: #lit, since: None } };
107			Ok(doc)
108		},
109		Meta::Path(_) => {
110			// #[deprecated]
111			Ok(quote! { #target::DeprecatedWithoutNote })
112		},
113		_ => Err(Error::new(
114			attr.span(),
115			deprecation_msg_formatter("Invalid deprecation attribute: expected string literal"),
116		)),
117	}
118}
119
120fn find_deprecation_attr(attrs: &[syn::Attribute]) -> Option<&syn::Attribute> {
121	attrs.iter().find(|a| a.path().is_ident("deprecated"))
122}
123
124fn parse_deprecation(
125	path: &TokenStream,
126	attrs: &[syn::Attribute],
127	target: DeprecationTarget,
128) -> Result<Option<TokenStream>> {
129	find_deprecation_attr(attrs)
130		.map(|a| parse_deprecated_meta(path, a, target))
131		.transpose()
132}
133
134/// collects deprecation attribute if its present.
135pub fn get_deprecation(path: &TokenStream, attrs: &[syn::Attribute]) -> Result<TokenStream> {
136	parse_deprecation(path, attrs, DeprecationTarget::Item).map(|item| {
137		item.unwrap_or_else(|| {
138			quote! {#path::__private::metadata_ir::ItemDeprecationInfoIR::NotDeprecated}
139		})
140	})
141}
142
143/// Call this on enum attributes to return an error if the #[deprecation] attribute is found.
144pub fn prevent_deprecation_attr_on_outer_enum(parent_attrs: &[syn::Attribute]) -> Result<()> {
145	if let Some(attr) = find_deprecation_attr(parent_attrs) {
146		return Err(Error::new(
147			attr.span(),
148			"The `#[deprecated]` attribute should be applied to individual variants, not the enum as a whole.",
149		));
150	}
151	Ok(())
152}
153
154/// collects deprecation attribute if its present for enum-like types
155pub fn get_deprecation_enum<'a>(
156	path: &TokenStream,
157	children_attrs: impl Iterator<Item = (u8, &'a [syn::Attribute])>,
158) -> Result<TokenStream> {
159	let children = children_attrs
160		.filter_map(|(key, attributes)| {
161			let deprecation_status =
162				parse_deprecation(path, attributes, DeprecationTarget::Variant).transpose();
163			deprecation_status.map(|item| item.map(|item| quote::quote! { (#key, #item) }))
164		})
165		.collect::<Result<Vec<TokenStream>>>()?;
166
167	if children.is_empty() {
168		Ok(
169			quote::quote! { #path::__private::metadata_ir::EnumDeprecationInfoIR::nothing_deprecated() },
170		)
171	} else {
172		let children = quote::quote! { #path::__private::scale_info::prelude::collections::BTreeMap::from([#( #children),*]) };
173		Ok(quote::quote! { #path::__private::metadata_ir::EnumDeprecationInfoIR(#children) })
174	}
175}
176
177/// Gets the index for the variant inside `Error` or `Event` declaration.
178/// priority is as follows:
179/// Manual `#[codec(index = N)]`
180/// Explicit discriminant `Variant = N`
181/// Variant's definition index
182pub fn variant_index_for_deprecation(index: u8, item: &Variant) -> u8 {
183	let index: u8 =
184		if let Some((_, Expr::Lit(ExprLit { lit: Lit::Int(num_lit), .. }))) = &item.discriminant {
185			num_lit.base10_parse::<u8>().unwrap_or(index as u8)
186		} else {
187			index as u8
188		};
189
190	item.attrs
191		.iter()
192		.find(|attr| attr.path().is_ident("codec"))
193		.and_then(|attr| {
194			if let Meta::List(meta_list) = &attr.meta {
195				meta_list
196					.parse_args_with(Punctuated::<MetaNameValue, syn::Token![,]>::parse_terminated)
197					.ok()
198			} else {
199				None
200			}
201		})
202		.and_then(|parsed| {
203			parsed.iter().fold(None, |mut acc, item| {
204				if let Expr::Lit(ExprLit { lit: Lit::Int(num_lit), .. }) = &item.value {
205					num_lit.base10_parse::<u8>().iter().for_each(|val| {
206						if item.path.is_ident("index") {
207							acc.replace(*val);
208						}
209					})
210				};
211				acc
212			})
213		})
214		.unwrap_or(index)
215}
216
217/// Filters all of the `allow` and `deprecated` attributes.
218///
219/// `allow` attributes are returned as is while `deprecated` attributes are replaced by
220/// `#[allow(deprecated)]`.
221pub fn extract_or_return_allow_attrs(
222	items: &[syn::Attribute],
223) -> impl Iterator<Item = syn::Attribute> + '_ {
224	items.iter().filter_map(|attr| {
225		attr.path().is_ident("allow").then(|| attr.clone()).or_else(|| {
226			attr.path().is_ident("deprecated").then(|| {
227				syn::parse_quote! {
228					#[allow(deprecated)]
229				}
230			})
231		})
232	})
233}