use codec::Encode;
use proc_macro2::{Span, TokenStream};
use proc_macro_warning::Warning;
use quote::quote;
use syn::{
parse::{Error, Result},
parse_macro_input,
spanned::Spanned as _,
Expr, ExprLit, FieldValue, ItemConst, Lit,
};
pub fn decl_runtime_version_impl(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
let item = parse_macro_input!(input as ItemConst);
decl_runtime_version_impl_inner(item)
.unwrap_or_else(|e| e.to_compile_error())
.into()
}
fn decl_runtime_version_impl_inner(item: ItemConst) -> Result<TokenStream> {
let (parsed_runtime_version, warnings) = ParseRuntimeVersion::parse_expr(&item.expr)?;
let runtime_version = parsed_runtime_version.build(item.expr.span())?;
let link_section =
generate_emit_link_section_decl(&runtime_version.encode(), "runtime_version");
Ok(quote! {
#item
#link_section
const _:() = {
#(
#warnings
)*
};
})
}
#[derive(Encode)]
struct RuntimeVersion {
spec_name: String,
impl_name: String,
authoring_version: u32,
spec_version: u32,
impl_version: u32,
apis: u8,
transaction_version: u32,
system_version: u8,
}
#[derive(Default, Debug)]
struct ParseRuntimeVersion {
spec_name: Option<String>,
impl_name: Option<String>,
authoring_version: Option<u32>,
spec_version: Option<u32>,
impl_version: Option<u32>,
transaction_version: Option<u32>,
system_version: Option<u8>,
}
impl ParseRuntimeVersion {
fn parse_expr(init_expr: &Expr) -> Result<(ParseRuntimeVersion, Vec<Warning>)> {
let init_expr = match init_expr {
Expr::Struct(ref e) => e,
_ =>
return Err(Error::new(init_expr.span(), "expected a struct initializer expression")),
};
let mut parsed = ParseRuntimeVersion::default();
let mut warnings = vec![];
for field_value in init_expr.fields.iter() {
warnings.append(&mut parsed.parse_field_value(field_value)?)
}
Ok((parsed, warnings))
}
fn parse_field_value(&mut self, field_value: &FieldValue) -> Result<Vec<Warning>> {
let field_name = match field_value.member {
syn::Member::Named(ref ident) => ident,
syn::Member::Unnamed(_) =>
return Err(Error::new(field_value.span(), "only named members must be used")),
};
fn parse_once<T>(
value: &mut Option<T>,
field: &FieldValue,
parser: impl FnOnce(&Expr) -> Result<T>,
) -> Result<()> {
if value.is_some() {
Err(Error::new(field.span(), "field is already initialized before"))
} else {
*value = Some(parser(&field.expr)?);
Ok(())
}
}
let mut warnings = vec![];
if field_name == "spec_name" {
parse_once(&mut self.spec_name, field_value, Self::parse_str_literal)?;
} else if field_name == "impl_name" {
parse_once(&mut self.impl_name, field_value, Self::parse_str_literal)?;
} else if field_name == "authoring_version" {
parse_once(&mut self.authoring_version, field_value, Self::parse_num_literal)?;
} else if field_name == "spec_version" {
parse_once(&mut self.spec_version, field_value, Self::parse_num_literal)?;
} else if field_name == "impl_version" {
parse_once(&mut self.impl_version, field_value, Self::parse_num_literal)?;
} else if field_name == "transaction_version" {
parse_once(&mut self.transaction_version, field_value, Self::parse_num_literal)?;
} else if field_name == "state_version" {
let warning = Warning::new_deprecated("RuntimeVersion")
.old("state_version")
.new("system_version)")
.help_link("https://github.com/paritytech/polkadot-sdk/pull/4257")
.span(field_name.span())
.build_or_panic();
warnings.push(warning);
parse_once(&mut self.system_version, field_value, Self::parse_num_literal_u8)?;
} else if field_name == "system_version" {
parse_once(&mut self.system_version, field_value, Self::parse_num_literal_u8)?;
} else if field_name == "apis" {
} else {
return Err(Error::new(field_name.span(), "unknown field"))
}
Ok(warnings)
}
fn parse_num_literal(expr: &Expr) -> Result<u32> {
let lit = match *expr {
Expr::Lit(ExprLit { lit: Lit::Int(ref lit), .. }) => lit,
_ =>
return Err(Error::new(
expr.span(),
"only numeric literals (e.g. `10`) are supported here",
)),
};
lit.base10_parse::<u32>()
}
fn parse_num_literal_u8(expr: &Expr) -> Result<u8> {
let lit = match *expr {
Expr::Lit(ExprLit { lit: Lit::Int(ref lit), .. }) => lit,
_ =>
return Err(Error::new(
expr.span(),
"only numeric literals (e.g. `10`) are supported here",
)),
};
lit.base10_parse::<u8>()
}
fn parse_str_literal(expr: &Expr) -> Result<String> {
let mac = match *expr {
Expr::Macro(syn::ExprMacro { ref mac, .. }) => mac,
_ => return Err(Error::new(expr.span(), "a macro expression is expected here")),
};
let lit: ExprLit = mac.parse_body().map_err(|e| {
Error::new(
e.span(),
format!("a single literal argument is expected, but parsing is failed: {}", e),
)
})?;
match lit.lit {
Lit::Str(ref lit) => Ok(lit.value()),
_ => Err(Error::new(lit.span(), "only string literals are supported here")),
}
}
fn build(self, span: Span) -> Result<RuntimeVersion> {
macro_rules! required {
($e:expr) => {
$e.ok_or_else(|| {
Error::new(span, format!("required field '{}' is missing", stringify!($e)))
})?
};
}
let Self {
spec_name,
impl_name,
authoring_version,
spec_version,
impl_version,
transaction_version,
system_version,
} = self;
Ok(RuntimeVersion {
spec_name: required!(spec_name),
impl_name: required!(impl_name),
authoring_version: required!(authoring_version),
spec_version: required!(spec_version),
impl_version: required!(impl_version),
transaction_version: required!(transaction_version),
system_version: required!(system_version),
apis: 0,
})
}
}
fn generate_emit_link_section_decl(contents: &[u8], section_name: &str) -> TokenStream {
let len = contents.len();
quote! {
const _: () = {
#[cfg(not(feature = "std"))]
#[link_section = #section_name]
static SECTION_CONTENTS: [u8; #len] = [#(#contents),*];
};
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::borrow::Cow;
#[test]
fn version_can_be_deserialized() {
let version_bytes = RuntimeVersion {
spec_name: "hello".to_string(),
impl_name: "world".to_string(),
authoring_version: 10,
spec_version: 265,
impl_version: 1,
apis: 0,
transaction_version: 2,
system_version: 1,
}
.encode();
assert_eq!(
sp_version::RuntimeVersion::decode_with_version_hint(&mut &version_bytes[..], Some(4))
.unwrap(),
sp_version::RuntimeVersion {
spec_name: "hello".into(),
impl_name: "world".into(),
authoring_version: 10,
spec_version: 265,
impl_version: 1,
apis: Cow::Owned(vec![]),
transaction_version: 2,
system_version: 1,
},
);
}
}