mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-12 20:31:13 +00:00
test: add unit test to catch missing distribution to subsystems faster (#2495)
* 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>
This commit is contained in:
committed by
GitHub
parent
69734bb8ed
commit
31327eb0c7
@@ -0,0 +1,18 @@
|
||||
[package]
|
||||
name = "polkadot-procmacro-subsystem-dispatch-gen"
|
||||
version = "0.1.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
edition = "2018"
|
||||
description = "Small proc macro to create the distribution code for network events"
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
syn = { version = "1.0.60", features = ["full"] }
|
||||
quote = "1.0.9"
|
||||
proc-macro2 = "1.0.24"
|
||||
assert_matches = "1.5.0"
|
||||
|
||||
[dev-dependencies]
|
||||
trybuild = "1.0.41"
|
||||
@@ -0,0 +1,208 @@
|
||||
// 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");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
#![allow(dead_code)]
|
||||
|
||||
use polkadot_procmacro_subsystem_dispatch_gen::subsystem_dispatch_gen;
|
||||
|
||||
/// The event type in question.
|
||||
#[derive(Clone, Copy)]
|
||||
enum Event {
|
||||
Smth,
|
||||
Else,
|
||||
}
|
||||
|
||||
impl Event {
|
||||
fn focus(&self) -> std::result::Result<Inner, ()> {
|
||||
unimplemented!("foo")
|
||||
}
|
||||
}
|
||||
|
||||
/// This should have a `From<Event>` impl but does not.
|
||||
#[derive(Clone)]
|
||||
enum Inner {
|
||||
Foo,
|
||||
Bar(Event),
|
||||
}
|
||||
|
||||
#[subsystem_dispatch_gen(Event)]
|
||||
#[derive(Clone)]
|
||||
enum AllMessages {
|
||||
/// Foo
|
||||
Vvvvvv(Inner),
|
||||
|
||||
/// Missing a `#[skip]` annotation
|
||||
Uuuuu,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let _x = AllMessages::dispatch_iter(Event::Else);
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
error: Must be annotated with #[skip].
|
||||
--> $DIR/err-01-missing-skip.rs:32:5
|
||||
|
|
||||
32 | Uuuuu,
|
||||
| ^^^^^
|
||||
|
||||
error[E0599]: no variant or associated item named `dispatch_iter` found for enum `AllMessages` in the current scope
|
||||
--> $DIR/err-01-missing-skip.rs:36:27
|
||||
|
|
||||
27 | enum AllMessages {
|
||||
| ---------------- variant or associated item `dispatch_iter` not found here
|
||||
...
|
||||
36 | let _x = AllMessages::dispatch_iter(Event::Else);
|
||||
| ^^^^^^^^^^^^^ variant or associated item not found in `AllMessages`
|
||||
@@ -0,0 +1,41 @@
|
||||
#![allow(dead_code)]
|
||||
|
||||
use polkadot_procmacro_subsystem_dispatch_gen::subsystem_dispatch_gen;
|
||||
|
||||
/// The event type in question.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
enum Event {
|
||||
Smth,
|
||||
Else,
|
||||
}
|
||||
|
||||
impl Event {
|
||||
fn focus(&self) -> std::result::Result<Intermediate, ()> {
|
||||
Ok(Intermediate(self.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct Intermediate(Event);
|
||||
|
||||
|
||||
/// This should have a `From<Event>` impl but does not.
|
||||
#[derive(Debug, Clone)]
|
||||
enum Inner {
|
||||
Foo,
|
||||
Bar(Intermediate),
|
||||
}
|
||||
|
||||
#[subsystem_dispatch_gen(Event)]
|
||||
#[derive(Clone)]
|
||||
enum AllMessages {
|
||||
/// Foo
|
||||
Vvvvvv(Inner),
|
||||
|
||||
#[skip]
|
||||
Uuuuu,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let _x = AllMessages::dispatch_iter(Event::Else);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
error[E0308]: mismatched types
|
||||
--> $DIR/err-02-missing-from.rs:29:1
|
||||
|
|
||||
29 | #[subsystem_dispatch_gen(Event)]
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
| |
|
||||
| expected enum `Inner`, found struct `Intermediate`
|
||||
| help: try using a variant of the expected enum: `Inner::Bar(#[subsystem_dispatch_gen(Event)])`
|
||||
|
|
||||
= note: this error originates in an attribute macro (in Nightly builds, run with -Z macro-backtrace for more info)
|
||||
@@ -0,0 +1,48 @@
|
||||
#![allow(dead_code)]
|
||||
|
||||
use polkadot_procmacro_subsystem_dispatch_gen::subsystem_dispatch_gen;
|
||||
|
||||
/// The event type in question.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
enum Event {
|
||||
Smth,
|
||||
Else,
|
||||
}
|
||||
|
||||
impl Event {
|
||||
fn focus(&self) -> std::result::Result<Intermediate, ()> {
|
||||
Ok(Intermediate(self.clone()))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct Intermediate(Event);
|
||||
|
||||
|
||||
/// This should have a `From<Event>` impl but does not.
|
||||
#[derive(Clone, Debug)]
|
||||
enum Inner {
|
||||
Foo,
|
||||
Bar(Intermediate),
|
||||
}
|
||||
|
||||
impl From<Intermediate> for Inner {
|
||||
fn from(src: Intermediate) -> Self {
|
||||
Inner::Bar(src)
|
||||
}
|
||||
}
|
||||
|
||||
#[subsystem_dispatch_gen(Event)]
|
||||
#[derive(Clone)]
|
||||
enum AllMessages {
|
||||
/// Foo
|
||||
Vvvvvv(Inner),
|
||||
|
||||
#[skip]
|
||||
Uuuuu,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let _x = AllMessages::dispatch_iter(Event::Else);
|
||||
}
|
||||
Reference in New Issue
Block a user