// 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 . 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, } 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 { 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::>(); 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 { let event_ty = parse2::(attr)?; let mut ie = parse2::(item)?; let message_enum = ie.ident.clone(); let variants = ie.variants.iter_mut().try_fold(Vec::::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 + 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 }; 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"); } }