1use codec::Encode;
19use proc_macro2::{Span, TokenStream};
20use proc_macro_warning::Warning;
21use quote::quote;
22use syn::{
23 parse::{Error, Result},
24 parse_macro_input,
25 spanned::Spanned as _,
26 Expr, ExprLit, FieldValue, ItemConst, Lit,
27};
28
29pub fn decl_runtime_version_impl(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
34 let item = parse_macro_input!(input as ItemConst);
35 decl_runtime_version_impl_inner(item)
36 .unwrap_or_else(|e| e.to_compile_error())
37 .into()
38}
39
40fn decl_runtime_version_impl_inner(item: ItemConst) -> Result<TokenStream> {
41 let (parsed_runtime_version, warnings) = ParseRuntimeVersion::parse_expr(&item.expr)?;
42 let runtime_version = parsed_runtime_version.build(item.expr.span())?;
43 let link_section =
44 generate_emit_link_section_decl(&runtime_version.encode(), "runtime_version");
45
46 Ok(quote! {
47 #item
48 #link_section
49 const _:() = {
50 #(
51 #warnings
52 )*
53 };
54 })
55}
56
57#[derive(Encode)]
65struct RuntimeVersion {
66 spec_name: String,
67 impl_name: String,
68 authoring_version: u32,
69 spec_version: u32,
70 impl_version: u32,
71 apis: u8,
72 transaction_version: u32,
73 system_version: u8,
74}
75
76#[derive(Default, Debug)]
77struct ParseRuntimeVersion {
78 spec_name: Option<String>,
79 impl_name: Option<String>,
80 authoring_version: Option<u32>,
81 spec_version: Option<u32>,
82 impl_version: Option<u32>,
83 transaction_version: Option<u32>,
84 system_version: Option<u8>,
85}
86
87impl ParseRuntimeVersion {
88 fn parse_expr(init_expr: &Expr) -> Result<(ParseRuntimeVersion, Vec<Warning>)> {
89 let init_expr = match init_expr {
90 Expr::Struct(ref e) => e,
91 _ =>
92 return Err(Error::new(init_expr.span(), "expected a struct initializer expression")),
93 };
94
95 let mut parsed = ParseRuntimeVersion::default();
96 let mut warnings = vec![];
97 for field_value in init_expr.fields.iter() {
98 warnings.append(&mut parsed.parse_field_value(field_value)?)
99 }
100 Ok((parsed, warnings))
101 }
102
103 fn parse_field_value(&mut self, field_value: &FieldValue) -> Result<Vec<Warning>> {
104 let field_name = match field_value.member {
105 syn::Member::Named(ref ident) => ident,
106 syn::Member::Unnamed(_) =>
107 return Err(Error::new(field_value.span(), "only named members must be used")),
108 };
109
110 fn parse_once<T>(
111 value: &mut Option<T>,
112 field: &FieldValue,
113 parser: impl FnOnce(&Expr) -> Result<T>,
114 ) -> Result<()> {
115 if value.is_some() {
116 Err(Error::new(field.span(), "field is already initialized before"))
117 } else {
118 *value = Some(parser(&field.expr)?);
119 Ok(())
120 }
121 }
122
123 let mut warnings = vec![];
124 if field_name == "spec_name" {
125 parse_once(&mut self.spec_name, field_value, Self::parse_str_literal)?;
126 } else if field_name == "impl_name" {
127 parse_once(&mut self.impl_name, field_value, Self::parse_str_literal)?;
128 } else if field_name == "authoring_version" {
129 parse_once(&mut self.authoring_version, field_value, Self::parse_num_literal)?;
130 } else if field_name == "spec_version" {
131 parse_once(&mut self.spec_version, field_value, Self::parse_num_literal)?;
132 } else if field_name == "impl_version" {
133 parse_once(&mut self.impl_version, field_value, Self::parse_num_literal)?;
134 } else if field_name == "transaction_version" {
135 parse_once(&mut self.transaction_version, field_value, Self::parse_num_literal)?;
136 } else if field_name == "state_version" {
137 let warning = Warning::new_deprecated("RuntimeVersion")
138 .old("state_version")
139 .new("system_version)")
140 .help_link("https://github.com/paritytech/polkadot-sdk/pull/4257")
141 .span(field_name.span())
142 .build_or_panic();
143 warnings.push(warning);
144 parse_once(&mut self.system_version, field_value, Self::parse_num_literal_u8)?;
145 } else if field_name == "system_version" {
146 parse_once(&mut self.system_version, field_value, Self::parse_num_literal_u8)?;
147 } else if field_name == "apis" {
148 } else {
154 return Err(Error::new(field_name.span(), "unknown field"))
155 }
156
157 Ok(warnings)
158 }
159
160 fn parse_num_literal(expr: &Expr) -> Result<u32> {
161 let lit = match *expr {
162 Expr::Lit(ExprLit { lit: Lit::Int(ref lit), .. }) => lit,
163 _ =>
164 return Err(Error::new(
165 expr.span(),
166 "only numeric literals (e.g. `10`) are supported here",
167 )),
168 };
169 lit.base10_parse::<u32>()
170 }
171
172 fn parse_num_literal_u8(expr: &Expr) -> Result<u8> {
173 let lit = match *expr {
174 Expr::Lit(ExprLit { lit: Lit::Int(ref lit), .. }) => lit,
175 _ =>
176 return Err(Error::new(
177 expr.span(),
178 "only numeric literals (e.g. `10`) are supported here",
179 )),
180 };
181 lit.base10_parse::<u8>()
182 }
183
184 fn parse_str_literal(expr: &Expr) -> Result<String> {
185 match expr {
186 Expr::Macro(syn::ExprMacro { mac, .. }) => {
188 let lit: ExprLit = mac.parse_body().map_err(|e| {
189 Error::new(
190 e.span(),
191 format!(
192 "a single literal argument is expected, but parsing is failed: {}",
193 e
194 ),
195 )
196 })?;
197
198 match &lit.lit {
199 Lit::Str(lit) => Ok(lit.value()),
200 _ => Err(Error::new(lit.span(), "only string literals are supported here")),
201 }
202 },
203 Expr::Call(call) => {
204 if call.args.len() != 1 {
205 return Err(Error::new(
206 expr.span(),
207 "a single literal argument is expected, but parsing is failed",
208 ));
209 }
210 let Expr::Lit(lit) = call.args.first().expect("Length checked above; qed") else {
211 return Err(Error::new(
212 expr.span(),
213 "a single literal argument is expected, but parsing is failed",
214 ));
215 };
216
217 match &lit.lit {
218 Lit::Str(lit) => Ok(lit.value()),
219 _ => Err(Error::new(lit.span(), "only string literals are supported here")),
220 }
221 },
222 _ => Err(Error::new(
223 expr.span(),
224 format!("a function call is expected here, instead of: {expr:?}"),
225 )),
226 }
227 }
228
229 fn build(self, span: Span) -> Result<RuntimeVersion> {
230 macro_rules! required {
231 ($e:expr) => {
232 $e.ok_or_else(|| {
233 Error::new(span, format!("required field '{}' is missing", stringify!($e)))
234 })?
235 };
236 }
237
238 let Self {
239 spec_name,
240 impl_name,
241 authoring_version,
242 spec_version,
243 impl_version,
244 transaction_version,
245 system_version,
246 } = self;
247
248 Ok(RuntimeVersion {
249 spec_name: required!(spec_name),
250 impl_name: required!(impl_name),
251 authoring_version: required!(authoring_version),
252 spec_version: required!(spec_version),
253 impl_version: required!(impl_version),
254 transaction_version: required!(transaction_version),
255 system_version: required!(system_version),
256 apis: 0,
257 })
258 }
259}
260
261fn generate_emit_link_section_decl(contents: &[u8], section_name: &str) -> TokenStream {
262 let len = contents.len();
263 quote! {
264 const _: () = {
265 #[cfg(not(feature = "std"))]
266 #[link_section = #section_name]
267 static SECTION_CONTENTS: [u8; #len] = [#(#contents),*];
268 };
269 }
270}
271
272#[cfg(test)]
273mod tests {
274 use super::*;
275 use std::borrow::Cow;
276
277 #[test]
278 fn version_can_be_deserialized() {
279 let version_bytes = RuntimeVersion {
280 spec_name: "hello".to_string(),
281 impl_name: "world".to_string(),
282 authoring_version: 10,
283 spec_version: 265,
284 impl_version: 1,
285 apis: 0,
286 transaction_version: 2,
287 system_version: 1,
288 }
289 .encode();
290
291 assert_eq!(
292 sp_version::RuntimeVersion::decode_with_version_hint(&mut &version_bytes[..], Some(4))
293 .unwrap(),
294 sp_version::RuntimeVersion {
295 spec_name: "hello".into(),
296 impl_name: "world".into(),
297 authoring_version: 10,
298 spec_version: 265,
299 impl_version: 1,
300 apis: Cow::Owned(vec![]),
301 transaction_version: 2,
302 system_version: 1,
303 },
304 );
305 }
306}