// 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::{Span, TokenStream};
use std::collections::{hash_map::RandomState, HashMap, HashSet};
use syn::{
parse::{Parse, ParseStream},
punctuated::Punctuated,
spanned::Spanned,
AttrStyle, Attribute, Error, Field, FieldsNamed, GenericParam, Ident, ItemStruct, Path, Result,
Token, Type, Visibility,
};
use quote::{quote, ToTokens};
mod kw {
syn::custom_keyword!(wip);
syn::custom_keyword!(no_dispatch);
syn::custom_keyword!(blocking);
}
#[derive(Clone, Debug)]
enum SubSysAttrItem {
/// The subsystem is still a work in progress
/// and should not be communicated with.
Wip(kw::wip),
/// The subsystem is blocking and requires to be
/// spawned on an exclusive thread.
Blocking(kw::blocking),
/// External messages should not be - after being converted -
/// be dispatched to the annotated subsystem.
NoDispatch(kw::no_dispatch),
}
impl Parse for SubSysAttrItem {
fn parse(input: ParseStream) -> Result {
let lookahead = input.lookahead1();
Ok(if lookahead.peek(kw::wip) {
Self::Wip(input.parse::()?)
} else if lookahead.peek(kw::blocking) {
Self::Blocking(input.parse::()?)
} else if lookahead.peek(kw::no_dispatch) {
Self::NoDispatch(input.parse::()?)
} else {
return Err(lookahead.error())
})
}
}
impl ToTokens for SubSysAttrItem {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
let ts = match self {
Self::Wip(wip) => {
quote! { #wip }
},
Self::Blocking(blocking) => {
quote! { #blocking }
},
Self::NoDispatch(no_dispatch) => {
quote! { #no_dispatch }
},
};
tokens.extend(ts.into_iter());
}
}
/// A field of the struct annotated with
/// `#[subsystem(no_dispatch, , A | B | C)]`
#[derive(Clone, Debug)]
pub(crate) struct SubSysField {
/// Name of the field.
pub(crate) name: Ident,
/// Generate generic type name for the `AllSubsystems` type
/// which is also used `#wrapper_message :: #variant` variant
/// part.
pub(crate) generic: Ident,
/// Type to be consumed by the subsystem.
pub(crate) consumes: Path,
/// If `no_dispatch` is present, if the message is incoming via
/// an `extern` `Event`, it will not be dispatched to all subsystems.
pub(crate) no_dispatch: bool,
/// If the subsystem implementation is blocking execution and hence
/// has to be spawned on a separate thread or thread pool.
pub(crate) blocking: bool,
/// The subsystem is a work in progress.
/// Avoids dispatching `Wrapper` type messages, but generates the variants.
/// Does not require the subsystem to be instantiated with the builder pattern.
pub(crate) wip: bool,
}
fn try_type_to_path(ty: Type, span: Span) -> Result {
match ty {
Type::Path(path) => Ok(path.path),
_ => Err(Error::new(span, "Type must be a path expression.")),
}
}
macro_rules! extract_variant {
($unique:expr, $variant:ident ; default = $fallback:expr) => {
extract_variant!($unique, $variant).unwrap_or_else(|| $fallback)
};
($unique:expr, $variant:ident ; err = $err:expr) => {
extract_variant!($unique, $variant).ok_or_else(|| Error::new(Span::call_site(), $err))
};
($unique:expr, $variant:ident) => {
$unique.values().find_map(|item| {
if let SubSysAttrItem::$variant(_) = item {
Some(true)
} else {
None
}
})
};
}
pub(crate) struct SubSystemTags {
#[allow(dead_code)]
pub(crate) attrs: Vec,
#[allow(dead_code)]
pub(crate) no_dispatch: bool,
/// The subsystem is in progress, only generate the `Wrapper` variant, but do not forward messages
/// and also not include the subsystem in the list of subsystems.
pub(crate) wip: bool,
pub(crate) blocking: bool,
pub(crate) consumes: Path,
}
impl Parse for SubSystemTags {
fn parse(input: syn::parse::ParseStream) -> Result {
let attrs = Attribute::parse_outer(input)?;
let input = input;
let content;
let _ = syn::parenthesized!(content in input);
let mut items = Punctuated::new();
while let Ok(tag) = content.call(SubSysAttrItem::parse) {
items.push_value(tag);
items.push_punct(content.call(::parse)?);
}
assert!(items.empty_or_trailing(), "Always followed by the message type to consume. qed");
let consumes = content.parse::()?;
let mut unique = HashMap::<
std::mem::Discriminant,
SubSysAttrItem,
RandomState,
>::default();
for item in items {
if let Some(first) = unique.insert(std::mem::discriminant(&item), item.clone()) {
let mut e = Error::new(
item.span(),
format!("Duplicate definition of subsystem attribute found"),
);
e.combine(Error::new(first.span(), "previously defined here."));
return Err(e)
}
}
let no_dispatch = extract_variant!(unique, NoDispatch; default = false);
let blocking = extract_variant!(unique, Blocking; default = false);
let wip = extract_variant!(unique, Wip; default = false);
Ok(Self { attrs, no_dispatch, blocking, consumes, wip })
}
}
/// Fields that are _not_ subsystems.
#[derive(Debug, Clone)]
pub(crate) struct BaggageField {
pub(crate) field_name: Ident,
pub(crate) field_ty: Path,
pub(crate) generic: bool,
pub(crate) vis: Visibility,
}
#[derive(Clone, Debug)]
pub(crate) struct OverseerInfo {
/// Where the support crate `::polkadot_overseer_gen` lives.
pub(crate) support_crate_name: TokenStream,
/// Fields annotated with `#[subsystem(..)]`.
pub(crate) subsystems: Vec,
/// Fields that do not define a subsystem,
/// but are mere baggage.
pub(crate) baggage: Vec,
/// Name of the wrapping enum for all messages, defaults to `AllMessages`.
pub(crate) message_wrapper: Ident,
/// Name of the overseer struct, used as a prefix for
/// almost all generated types.
pub(crate) overseer_name: Ident,
/// Size of the bounded channel.
pub(crate) message_channel_capacity: usize,
/// Size of the bounded signal channel.
pub(crate) signal_channel_capacity: usize,
/// Signals to be sent, sparse information that is used intermittently.
pub(crate) extern_signal_ty: Path,
/// Incoming event type from the outer world, usually an external framework of some sort.
pub(crate) extern_event_ty: Path,
/// Incoming event type from an external entity, commonly from the network.
pub(crate) extern_network_ty: Option,
/// Type of messages that are sent to an external subsystem.
/// Merely here to be included during generation of `message_wrapper` type.
pub(crate) outgoing_ty: Option,
/// Incoming event type from the outer world, commonly from the network.
pub(crate) extern_error_ty: Path,
}
impl OverseerInfo {
pub(crate) fn support_crate_name(&self) -> &TokenStream {
&self.support_crate_name
}
pub(crate) fn variant_names(&self) -> Vec {
self.subsystems.iter().map(|ssf| ssf.generic.clone()).collect::>()
}
pub(crate) fn variant_names_without_wip(&self) -> Vec {
self.subsystems
.iter()
.filter(|ssf| !ssf.wip)
.map(|ssf| ssf.generic.clone())
.collect::>()
}
pub(crate) fn variant_names_only_wip(&self) -> Vec {
self.subsystems
.iter()
.filter(|ssf| ssf.wip)
.map(|ssf| ssf.generic.clone())
.collect::>()
}
pub(crate) fn subsystems(&self) -> &[SubSysField] {
self.subsystems.as_slice()
}
pub(crate) fn subsystem_names_without_wip(&self) -> Vec {
self.subsystems
.iter()
.filter(|ssf| !ssf.wip)
.map(|ssf| ssf.name.clone())
.collect::>()
}
pub(crate) fn baggage_names(&self) -> Vec {
self.baggage.iter().map(|bag| bag.field_name.clone()).collect::>()
}
pub(crate) fn baggage_types(&self) -> Vec {
self.baggage.iter().map(|bag| bag.field_ty.clone()).collect::>()
}
pub(crate) fn baggage_decl(&self) -> Vec {
self.baggage
.iter()
.map(|bag| {
let BaggageField { vis, field_ty, field_name, .. } = bag;
quote! { #vis #field_name: #field_ty }
})
.collect::>()
}
/// Generic types per subsystem, as defined by the user.
pub(crate) fn builder_generic_types(&self) -> Vec {
self.subsystems
.iter()
.filter(|ssf| !ssf.wip)
.map(|sff| sff.generic.clone())
.collect::>()
}
pub(crate) fn baggage_generic_types(&self) -> Vec {
self.baggage
.iter()
.filter(|bag| bag.generic)
.filter_map(|bag| bag.field_ty.get_ident().cloned())
.collect::>()
}
pub(crate) fn consumes(&self) -> Vec {
self.subsystems.iter().map(|ssf| ssf.consumes.clone()).collect::>()
}
pub(crate) fn channel_names_without_wip(&self, suffix: &'static str) -> Vec {
self.subsystems
.iter()
.filter(|ssf| !ssf.wip)
.map(|ssf| Ident::new(&(ssf.name.to_string() + suffix), ssf.name.span()))
.collect::>()
}
pub(crate) fn consumes_without_wip(&self) -> Vec {
self.subsystems
.iter()
.filter(|ssf| !ssf.wip)
.map(|ssf| ssf.consumes.clone())
.collect::>()
}
}
/// Internals of the overseer.
#[derive(Debug, Clone)]
pub(crate) struct OverseerGuts {
pub(crate) name: Ident,
pub(crate) subsystems: Vec,
pub(crate) baggage: Vec,
}
impl OverseerGuts {
pub(crate) fn parse_fields(
name: Ident,
baggage_generics: HashSet,
fields: FieldsNamed,
) -> Result {
let n = fields.named.len();
let mut subsystems = Vec::with_capacity(n);
let mut baggage = Vec::with_capacity(n);
// The types of `#[subsystem(..)]` annotated fields
// have to be unique, since they are used as generics
// for the builder pattern besides other places.
let mut unique_subsystem_idents = HashSet::::new();
for Field { attrs, vis, ident, ty, .. } in fields.named.into_iter() {
let mut consumes =
attrs.iter().filter(|attr| attr.style == AttrStyle::Outer).filter_map(|attr| {
let span = attr.path.span();
attr.path.get_ident().filter(|ident| *ident == "subsystem").map(move |_ident| {
let attr_tokens = attr.tokens.clone();
(attr_tokens, span)
})
});
let ident =
ident.ok_or_else(|| Error::new(ty.span(), "Missing identifier for member. BUG"))?;
if let Some((attr_tokens, span)) = consumes.next() {
if let Some((_attr_tokens2, span2)) = consumes.next() {
return Err({
let mut err = Error::new(span, "The first subsystem annotation is at");
err.combine(Error::new(span2, "but another here for the same field."));
err
})
}
let mut consumes_paths = Vec::with_capacity(attrs.len());
let attr_tokens = attr_tokens.clone();
let variant: SubSystemTags = syn::parse2(attr_tokens.clone())?;
consumes_paths.push(variant.consumes);
let field_ty = try_type_to_path(ty, span)?;
let generic = field_ty
.get_ident()
.ok_or_else(|| {
Error::new(field_ty.span(), "Must be an identifier, not a path.")
})?
.clone();
if let Some(previous) = unique_subsystem_idents.get(&generic) {
let mut e = Error::new(
generic.span(),
format!("Duplicate subsystem names `{}`", generic),
);
e.combine(Error::new(previous.span(), "previously defined here."));
return Err(e)
}
unique_subsystem_idents.insert(generic.clone());
subsystems.push(SubSysField {
name: ident,
generic,
consumes: consumes_paths[0].clone(),
no_dispatch: variant.no_dispatch,
wip: variant.wip,
blocking: variant.blocking,
});
} else {
let field_ty = try_type_to_path(ty, ident.span())?;
let generic = field_ty
.get_ident()
.map(|ident| baggage_generics.contains(ident))
.unwrap_or_default();
baggage.push(BaggageField { field_name: ident, generic, field_ty, vis });
}
}
Ok(Self { name, subsystems, baggage })
}
}
impl Parse for OverseerGuts {
fn parse(input: ParseStream) -> Result {
let ds: ItemStruct = input.parse()?;
match ds.fields {
syn::Fields::Named(named) => {
let name = ds.ident.clone();
// collect the indepedentent subsystem generics
// which need to be carried along, there are the non-generated ones
let mut orig_generics = ds.generics;
// remove defaults from types
let mut baggage_generic_idents = HashSet::with_capacity(orig_generics.params.len());
orig_generics.params = orig_generics
.params
.into_iter()
.map(|mut generic| {
match generic {
GenericParam::Type(ref mut param) => {
baggage_generic_idents.insert(param.ident.clone());
param.eq_token = None;
param.default = None;
},
_ => {},
}
generic
})
.collect();
Self::parse_fields(name, baggage_generic_idents, named)
},
syn::Fields::Unit => Err(Error::new(
ds.fields.span(),
"Must be a struct with named fields. Not an unit struct.",
)),
syn::Fields::Unnamed(unnamed) => Err(Error::new(
unnamed.span(),
"Must be a struct with named fields. Not an unnamed fields struct.",
)),
}
}
}