Unverified Commit ed71f321 authored by Bernhard Schuster's avatar Bernhard Schuster Committed by GitHub
Browse files

feat: add proc macro to reduce overseer mock boilerplate (#2949)

parent 6cbf3cc3
Pipeline #136222 passed with stages
in 38 minutes and 26 seconds
......@@ -6179,6 +6179,7 @@ dependencies = [
"polkadot-node-subsystem",
"polkadot-node-subsystem-util",
"polkadot-primitives",
"polkadot-procmacro-overseer-subsystems-gen",
"sc-client-api",
"sp-api",
"sp-core",
......@@ -6229,6 +6230,17 @@ dependencies = [
"thiserror",
]
[[package]]
name = "polkadot-procmacro-overseer-subsystems-gen"
version = "0.1.0"
dependencies = [
"assert_matches",
"proc-macro2",
"quote",
"syn",
"trybuild",
]
[[package]]
name = "polkadot-procmacro-subsystem-dispatch-gen"
version = "0.1.0"
......
......@@ -12,6 +12,7 @@ futures = "0.3.12"
futures-timer = "3.0.2"
polkadot-node-primitives = { package = "polkadot-node-primitives", path = "../primitives" }
polkadot-node-subsystem-util = { path = "../subsystem-util" }
polkadot-procmacro-overseer-subsystems-gen = { path = "./subsystems-gen" }
polkadot-primitives = { path = "../../primitives" }
polkadot-subsystem = { package = "polkadot-node-subsystem", path = "../subsystem" }
tracing = "0.1.25"
......
This diff is collapsed.
[package]
name = "polkadot-procmacro-overseer-subsystems-gen"
version = "0.1.0"
authors = ["Parity Technologies <admin@parity.io>"]
edition = "2018"
description = "Small proc macro to create mocking level iface type helpers"
[lib]
proc-macro = true
[dependencies]
syn = { version = "1.0.60", features = ["full", "extra-traits"] }
quote = "1.0.9"
proc-macro2 = "1.0.24"
assert_matches = "1.5.0"
[dev-dependencies]
trybuild = "1.0.41"
// 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 std::collections::HashSet;
use proc_macro2::TokenStream;
use quote::quote;
use syn::{Error, GenericParam, Ident, Result, Type, parse2};
#[proc_macro_derive(AllSubsystemsGen)]
pub fn subsystems_gen(item: proc_macro::TokenStream) -> proc_macro::TokenStream {
let item: TokenStream = item.into();
impl_subsystems_gen(item).unwrap_or_else(|err| err.to_compile_error()).into()
}
fn impl_subsystems_gen(item: TokenStream) -> Result<proc_macro2::TokenStream> {
let span = proc_macro2::Span::call_site();
let ds = parse2::<syn::ItemStruct>(item.clone())?;
match ds.fields {
syn::Fields::Named(named) => {
#[derive(Clone)]
struct NameTyTup {
field: Ident,
ty: Type,
}
let mut orig_generics = ds.generics;
// remove default types
orig_generics.params = orig_generics.params.into_iter().map(|mut generic| {
match generic {
GenericParam::Type(ref mut param) => {
param.eq_token = None;
param.default = None;
}
_ => {}
}
generic
}).collect();
// prepare a hashmap of generic type to member that uses it
let generic_types = orig_generics.params.iter().filter_map(|generic| {
if let GenericParam::Type(param) = generic {
Some(param.ident.clone())
} else {
None
}
}).collect::<HashSet<Ident>>();
let strukt_ty = ds.ident;
if generic_types.is_empty() {
return Err(Error::new(strukt_ty.span(), "struct must have at least one generic parameter."))
}
// collect all fields that exist, and all fields that are replaceable
let mut replacable_items = Vec::<NameTyTup>::with_capacity(64);
let mut all_fields = replacable_items.clone();
let mut duplicate_generic_detection = HashSet::<Ident>::with_capacity(64);
for field in named.named {
let field_ident = field.ident.clone().ok_or_else(|| Error::new(span, "Member field must have a name."))?;
let ty = field.ty.clone();
let ntt = NameTyTup { field: field_ident, ty };
replacable_items.push(ntt.clone());
// assure every generic is used exactly once
let ty_ident = match field.ty {
Type::Path(path) => path.path.get_ident().cloned().ok_or_else(|| {
Error::new(proc_macro2::Span::call_site(), "Expected an identifier, but got a path.")
}),
_ => return Err(Error::new(proc_macro2::Span::call_site(), "Must be path."))
}?;
if generic_types.contains(&ty_ident) {
if let Some(previous) = duplicate_generic_detection.replace(ty_ident) {
return Err(Error::new(previous.span(), "Generic type parameters may only be used for exactly one field, but is used more than once."))
}
}
all_fields.push(ntt);
}
let msg = "Generated by #[derive(AllSubsystemsGen)] derive proc-macro.";
let mut additive = TokenStream::new();
// generate an impl of `fn replace_#name`
for NameTyTup { field: replacable_item, ty: replacable_item_ty } in replacable_items {
let keeper = all_fields.iter().filter(|ntt| ntt.field != replacable_item).map(|ntt| ntt.field.clone());
let strukt_ty = strukt_ty.clone();
let fname = Ident::new(&format!("replace_{}", replacable_item), span);
// adjust the generics such that the appropriate member type is replaced
let mut modified_generics = orig_generics.clone();
modified_generics.params = modified_generics.params.into_iter().map(|mut generic| {
match generic {
GenericParam::Type(ref mut param) => {
param.eq_token = None;
param.default = None;
if match &replacable_item_ty {
Type::Path(path) =>
path.path.get_ident().filter(|&ident| ident == &param.ident).is_some(),
_ => false
} {
param.ident = Ident::new("NEW", span);
}
}
_ => {}
}
generic
}).collect();
additive.extend(quote! {
impl #orig_generics #strukt_ty #orig_generics {
#[doc = #msg]
pub fn #fname < NEW > (self, replacement: NEW) -> #strukt_ty #modified_generics {
#strukt_ty :: #modified_generics {
#replacable_item: replacement,
#(
#keeper: self.#keeper,
)*
}
}
}
});
}
Ok(additive)
}
syn::Fields::Unit => Err(Error::new(span, "Must be a struct with named fields. Not an unit struct.")),
syn::Fields::Unnamed(_) => {
Err(Error::new(span, "Must be a struct with named fields. Not an unnamed fields struct."))
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn basic() {
let item = quote! {
pub struct AllSubsystems<A,B,CD> {
pub a: A,
pub beee: B,
pub dj: CD,
}
};
let output = impl_subsystems_gen(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");
}
}
#![allow(dead_code)]
use polkadot_procmacro_overseer_subsystems_gen::AllSubsystemsGen;
#[derive(Clone, AllSubsystemsGen)]
enum AllSubsystems<A,B> {
A(A),
B(B),
}
fn main() {
let all = AllSubsystems::<u8,u16>::A(0u8);
}
error: expected `struct`
--> $DIR/err-01-enum.rs:6:1
|
6 | enum AllSubsystems<A,B> {
| ^^^^
#![allow(dead_code)]
use polkadot_procmacro_overseer_subsystems_gen::AllSubsystemsGen;
#[derive(Clone, AllSubsystemsGen)]
struct AllSubsystems<X> {
a: X,
b: X,
}
fn main() {
let all = AllSubsystems::<u16> {
a: 0_u16,
b: 1_u16,
};
let _all = all.replace_a(77u8);
}
error: Generic type parameters may only be used for exactly one field, but is used more than once.
--> $DIR/err-01-generic-used-twice.rs:7:5
|
7 | a: X,
| ^
error[E0599]: no method named `replace_a` found for struct `AllSubsystems<u16>` in the current scope
--> $DIR/err-01-generic-used-twice.rs:16:17
|
6 | struct AllSubsystems<X> {
| ----------------------- method `replace_a` not found for this
...
16 | let _all = all.replace_a(77u8);
| ^^^^^^^^^ method not found in `AllSubsystems<u16>`
#![allow(dead_code)]
use polkadot_procmacro_overseer_subsystems_gen::AllSubsystemsGen;
#[derive(Clone, AllSubsystemsGen)]
struct AllSubsystems {
a: f32,
b: u16,
}
fn main() {
let all = AllSubsystems {
a: 0_f32,
b: 1_u16,
};
let _all = all.replace_a(77u8);
}
error: Generic type parameters may only be used once have at least one generic parameter.
--> $DIR/err-01-no-generics.rs:7:5
|
7 | a: X,
| ^
error[E0599]: no method named `replace_a` found for struct `AllSubsystems<u16>` in the current scope
--> $DIR/err-01-no-generics.rs:16:17
|
6 | struct AllSubsystems<X> {
| ----------------------- method `replace_a` not found for this
...
16 | let _all = all.replace_a(77u8);
| ^^^^^^^^^ method not found in `AllSubsystems<u16>`
#![allow(dead_code)]
use polkadot_procmacro_overseer_subsystems_gen::AllSubsystemsGen;
#[derive(Clone, AllSubsystemsGen)]
struct AllSubsystems<A, B> {
a: A,
b: B,
}
fn main() {
let all = AllSubsystems::<u8, u16> {
a: 0u8,
b: 1u16,
};
let _all: AllSubsystems<_,_> = all.replace_a::<u32>(777_777u32);
}
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment