// 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 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 { let span = proc_macro2::Span::call_site(); let ds = parse2::(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::>(); 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::::with_capacity(64); let mut all_fields = replacable_items.clone(); let mut duplicate_generic_detection = HashSet::::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 == ¶m.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 { 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"); } }