mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-06 09:08:18 +00:00
31327eb0c7
* test: add unit test to catch missing distribution to subsystems faster * add a simple count * introduce proc macro to generate dispatch type * refactor * refactor * chore: add license * fixup unit test * fixup merge * better errors * better fmt * fix error spans * better docs * better error messages * ui test foo * Update node/subsystem/dispatch-gen/src/lib.rs Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com> * Update node/network/bridge/src/lib.rs Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com> * Update node/subsystem/Cargo.toml Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com> * Update node/subsystem/dispatch-gen/src/lib.rs Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com> * Update node/subsystem/dispatch-gen/src/lib.rs Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com> * Update node/network/bridge/src/lib.rs Co-authored-by: Andronik Ordian <write@reusable.software> * fix compilation * use find_map * drop the silly 2, use _inner instead * Update node/network/bridge/src/lib.rs Co-authored-by: Andronik Ordian <write@reusable.software> * Update node/subsystem/dispatch-gen/src/lib.rs Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com> * nail deps down * more into() * flatten * missing use statement * fix messages order Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com> Co-authored-by: Andronik Ordian <write@reusable.software>
209 lines
5.6 KiB
Rust
209 lines
5.6 KiB
Rust
// Copyright 2021 Parity Technologies (UK) Ltd.
|
|
// This file is part of Polkadot.
|
|
|
|
// Polkadot is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
|
|
// Polkadot is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with Polkadot. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
use proc_macro2::TokenStream;
|
|
use quote::{quote, ToTokens};
|
|
use std::fmt;
|
|
use syn::{parse2, Error, Fields, FieldsNamed, FieldsUnnamed, Ident, ItemEnum, Path, Result, Type, Variant};
|
|
|
|
#[proc_macro_attribute]
|
|
pub fn subsystem_dispatch_gen(attr: proc_macro::TokenStream, item: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
|
let attr: TokenStream = attr.into();
|
|
let item: TokenStream = item.into();
|
|
let mut backup = item.clone();
|
|
impl_subsystem_dispatch_gen(attr.into(), item).unwrap_or_else(|err| {
|
|
backup.extend(err.to_compile_error());
|
|
backup
|
|
}).into()
|
|
}
|
|
|
|
/// An enum variant without base type.
|
|
#[derive(Clone)]
|
|
struct EnumVariantDispatchWithTy {
|
|
// enum ty name
|
|
ty: Ident,
|
|
// variant
|
|
variant: EnumVariantDispatch,
|
|
}
|
|
|
|
impl fmt::Debug for EnumVariantDispatchWithTy {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
write!(f, "{}::{:?}", self.ty, self.variant)
|
|
}
|
|
}
|
|
|
|
impl ToTokens for EnumVariantDispatchWithTy {
|
|
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
|
|
if let Some(inner) = &self.variant.inner {
|
|
let enum_name = &self.ty;
|
|
let variant_name = &self.variant.name;
|
|
|
|
let quoted = quote! {
|
|
#enum_name::#variant_name(#inner::from(event))
|
|
};
|
|
quoted.to_tokens(tokens);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// An enum variant without the base type, contains the relevant inner type.
|
|
#[derive(Clone)]
|
|
struct EnumVariantDispatch {
|
|
/// variant name
|
|
name: Ident,
|
|
/// The inner type for which a `From::from` impl is anticipated from the input type.
|
|
/// No code will be generated for this enum variant if `inner` is `None`.
|
|
inner: Option<Type>,
|
|
}
|
|
|
|
impl fmt::Debug for EnumVariantDispatch {
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
write!(f, "{}(..)", self.name)
|
|
}
|
|
}
|
|
|
|
fn prepare_enum_variant(variant: &mut Variant) -> Result<EnumVariantDispatch> {
|
|
let skip = variant.attrs.iter().find(|attr| attr.path.is_ident("skip")).is_some();
|
|
variant.attrs = variant.attrs.iter().filter(|attr| !attr.path.is_ident("skip")).cloned().collect::<Vec<_>>();
|
|
|
|
let variant = variant.clone();
|
|
let span = variant.ident.span();
|
|
let inner = match variant.fields.clone() {
|
|
// look for one called inner
|
|
Fields::Named(FieldsNamed { brace_token: _, named }) if !skip => named
|
|
.iter()
|
|
.find_map(
|
|
|field| {
|
|
if let Some(ident) = &field.ident {
|
|
if ident == "inner" {
|
|
return Some(Some(field.ty.clone()))
|
|
}
|
|
}
|
|
None
|
|
},
|
|
)
|
|
.ok_or_else(|| {
|
|
Error::new(span, "To dispatch with struct enum variant, one element must named `inner`")
|
|
})?,
|
|
|
|
// technically, if it has no inner types we cound not require the #[skip] annotation, but better make it consistent
|
|
Fields::Unnamed(FieldsUnnamed { paren_token: _, unnamed }) if !skip => unnamed
|
|
.first()
|
|
.map(|field| Some(field.ty.clone()))
|
|
.ok_or_else(|| Error::new(span, "Must be annotated with skip, even if no inner types exist."))?,
|
|
_ if skip => None,
|
|
Fields::Unit => {
|
|
return Err(Error::new(
|
|
span,
|
|
"Must be annotated with #[skip].",
|
|
))
|
|
}
|
|
Fields::Unnamed(_) => {
|
|
return Err(Error::new(
|
|
span,
|
|
"Must be annotated with #[skip] or have in `inner` element which impls `From<_>`.",
|
|
))
|
|
}
|
|
Fields::Named(_) => {
|
|
return Err(Error::new(
|
|
span,
|
|
"Must be annotated with #[skip] or the first wrapped type must impl `From<_>`.",
|
|
))
|
|
}
|
|
};
|
|
|
|
Ok(EnumVariantDispatch { name: variant.ident, inner })
|
|
}
|
|
|
|
fn impl_subsystem_dispatch_gen(attr: TokenStream, item: TokenStream) -> Result<proc_macro2::TokenStream> {
|
|
let event_ty = parse2::<Path>(attr)?;
|
|
|
|
let mut ie = parse2::<ItemEnum>(item)?;
|
|
|
|
let message_enum = ie.ident.clone();
|
|
let variants = ie.variants.iter_mut().try_fold(Vec::<EnumVariantDispatchWithTy>::new(), |mut acc, variant| {
|
|
let variant = prepare_enum_variant(variant)?;
|
|
if variant.inner.is_some() {
|
|
acc.push(EnumVariantDispatchWithTy { ty: message_enum.clone(), variant })
|
|
}
|
|
Ok::<_, syn::Error>(acc)
|
|
})?;
|
|
|
|
let mut orig = ie.to_token_stream();
|
|
|
|
let msg = "Generated by #[subsystem_dispatch_gen] proc-macro.";
|
|
|
|
orig.extend(quote! {
|
|
impl #message_enum {
|
|
#[doc = #msg]
|
|
pub fn dispatch_iter(event: #event_ty) -> impl Iterator<Item=Self> + Send {
|
|
let mut iter = None.into_iter();
|
|
|
|
#(
|
|
let mut iter = iter.chain(std::iter::once(event.focus().ok().map(|event| {
|
|
#variants
|
|
})));
|
|
)*
|
|
iter.filter_map(|x| x)
|
|
}
|
|
}
|
|
});
|
|
Ok(orig)
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn basic() {
|
|
let attr = quote! {
|
|
NetEvent<foo::Bar>
|
|
};
|
|
|
|
let item = quote! {
|
|
/// Documentation.
|
|
#[derive(Clone)]
|
|
enum AllMessages {
|
|
|
|
Sub1(Inner1),
|
|
|
|
#[skip]
|
|
/// D3
|
|
Sub3,
|
|
|
|
/// D4
|
|
#[skip]
|
|
Sub4(Inner2),
|
|
|
|
/// D2
|
|
Sub2(Inner2),
|
|
}
|
|
};
|
|
|
|
let output = impl_subsystem_dispatch_gen(attr, item).expect("Simple example always works. qed");
|
|
println!("//generated:");
|
|
println!("{}", output);
|
|
}
|
|
|
|
#[test]
|
|
fn ui() {
|
|
let t = trybuild::TestCases::new();
|
|
t.compile_fail("tests/ui/err-*.rs");
|
|
t.pass("tests/ui/ok-*.rs");
|
|
}
|
|
}
|