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 let mac = match *expr {
186 Expr::Macro(syn::ExprMacro { ref mac, .. }) => mac,
187 _ => return Err(Error::new(expr.span(), "a macro expression is expected here")),
188 };
189
190 let lit: ExprLit = mac.parse_body().map_err(|e| {
191 Error::new(
192 e.span(),
193 format!("a single literal argument is expected, but parsing is failed: {}", e),
194 )
195 })?;
196
197 match lit.lit {
198 Lit::Str(ref lit) => Ok(lit.value()),
199 _ => Err(Error::new(lit.span(), "only string literals are supported here")),
200 }
201 }
202
203 fn build(self, span: Span) -> Result<RuntimeVersion> {
204 macro_rules! required {
205 ($e:expr) => {
206 $e.ok_or_else(|| {
207 Error::new(span, format!("required field '{}' is missing", stringify!($e)))
208 })?
209 };
210 }
211
212 let Self {
213 spec_name,
214 impl_name,
215 authoring_version,
216 spec_version,
217 impl_version,
218 transaction_version,
219 system_version,
220 } = self;
221
222 Ok(RuntimeVersion {
223 spec_name: required!(spec_name),
224 impl_name: required!(impl_name),
225 authoring_version: required!(authoring_version),
226 spec_version: required!(spec_version),
227 impl_version: required!(impl_version),
228 transaction_version: required!(transaction_version),
229 system_version: required!(system_version),
230 apis: 0,
231 })
232 }
233}
234
235fn generate_emit_link_section_decl(contents: &[u8], section_name: &str) -> TokenStream {
236 let len = contents.len();
237 quote! {
238 const _: () = {
239 #[cfg(not(feature = "std"))]
240 #[link_section = #section_name]
241 static SECTION_CONTENTS: [u8; #len] = [#(#contents),*];
242 };
243 }
244}
245
246#[cfg(test)]
247mod tests {
248 use super::*;
249 use std::borrow::Cow;
250
251 #[test]
252 fn version_can_be_deserialized() {
253 let version_bytes = RuntimeVersion {
254 spec_name: "hello".to_string(),
255 impl_name: "world".to_string(),
256 authoring_version: 10,
257 spec_version: 265,
258 impl_version: 1,
259 apis: 0,
260 transaction_version: 2,
261 system_version: 1,
262 }
263 .encode();
264
265 assert_eq!(
266 sp_version::RuntimeVersion::decode_with_version_hint(&mut &version_bytes[..], Some(4))
267 .unwrap(),
268 sp_version::RuntimeVersion {
269 spec_name: "hello".into(),
270 impl_name: "world".into(),
271 authoring_version: 10,
272 spec_version: 265,
273 impl_version: 1,
274 apis: Cow::Owned(vec![]),
275 transaction_version: 2,
276 system_version: 1,
277 },
278 );
279 }
280}