extern crate proc_macro;
use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use std::collections::HashMap;
use syn::parse::ParseStream;
use syn::spanned::Spanned;
use syn::Error;
mod kw {
syn::custom_keyword!(to);
syn::custom_keyword!(target);
}
#[derive(Clone)]
enum DelegatedInput {
Input(syn::FnArg),
Argument(syn::Expr),
}
impl syn::parse::Parse for DelegatedInput {
fn parse(input: syn::parse::ParseStream) -> Result<Self, Error> {
let lookahead = input.lookahead1();
if lookahead.peek(syn::token::Bracket) {
let content;
let _bracket_token = syn::bracketed!(content in input);
let expression: syn::Expr = content.parse()?;
Ok(Self::Argument(expression))
} else {
let input: syn::FnArg = input.parse()?;
Ok(Self::Input(input))
}
}
}
struct DelegatedMethod {
method: syn::TraitItemMethod,
attributes: Vec<syn::Attribute>,
visibility: syn::Visibility,
arguments: syn::punctuated::Punctuated<syn::Expr, syn::Token![,]>,
}
fn parse_input_into_argument_expression(
function_name: &syn::Ident,
input: &syn::FnArg,
) -> Option<syn::Expr> {
match input {
syn::FnArg::Typed(typed) => {
match &*typed.pat {
syn::Pat::Ident(ident) if ident.ident == "self" => None,
syn::Pat::Ident(ident) => {
let path_segment = syn::PathSegment {
ident: ident.ident.clone(),
arguments: syn::PathArguments::None,
};
let mut segments = syn::punctuated::Punctuated::new();
segments.push(path_segment);
let path = syn::Path {
leading_colon: None,
segments,
};
let ident_as_expr = syn::Expr::from(syn::ExprPath {
attrs: Vec::new(),
qself: None,
path,
});
Some(ident_as_expr)
}
_ => panic!(
"You have to use simple identifiers for delegated method parameters ({})",
function_name ),
}
}
syn::FnArg::Receiver(_receiver) => None,
}
}
impl syn::parse::Parse for DelegatedMethod {
fn parse(input: ParseStream) -> Result<Self, Error> {
let attributes = input.call(syn::Attribute::parse_outer)?;
let visibility = input.call(syn::Visibility::parse)?;
let constness: Option<syn::Token![const]> = input.parse()?;
let asyncness: Option<syn::Token![async]> = input.parse()?;
let unsafety: Option<syn::Token![unsafe]> = input.parse()?;
let abi: Option<syn::Abi> = input.parse()?;
let fn_token: syn::Token![fn] = input.parse()?;
let ident: syn::Ident = input.parse()?;
let generics: syn::Generics = input.parse()?;
let content;
let paren_token = syn::parenthesized!(content in input);
let delegated_inputs =
content.parse_terminated::<DelegatedInput, syn::Token![,]>(DelegatedInput::parse)?;
let mut inputs: syn::punctuated::Punctuated<syn::FnArg, syn::Token![,]> =
syn::punctuated::Punctuated::new();
let mut arguments: syn::punctuated::Punctuated<syn::Expr, syn::Token![,]> =
syn::punctuated::Punctuated::new();
delegated_inputs
.into_pairs()
.map(|punctuated_pair| match punctuated_pair {
syn::punctuated::Pair::Punctuated(item, comma) => (item, Some(comma)),
syn::punctuated::Pair::End(item) => (item, None),
})
.for_each(|pair| match pair {
(DelegatedInput::Argument(argument), maybe_comma) => {
arguments.push_value(argument);
if let Some(comma) = maybe_comma {
arguments.push_punct(comma)
}
}
(DelegatedInput::Input(input), maybe_comma) => {
inputs.push_value(input.clone());
if let Some(comma) = maybe_comma {
inputs.push_punct(comma);
}
let maybe_argument = parse_input_into_argument_expression(&ident, &input);
if let Some(argument) = maybe_argument {
arguments.push(argument);
if let Some(comma) = maybe_comma {
arguments.push_punct(comma);
}
}
}
});
let output: syn::ReturnType = input.parse()?;
let where_clause: Option<syn::WhereClause> = input.parse()?;
let signature = syn::Signature {
constness,
asyncness,
unsafety,
abi,
fn_token,
ident,
paren_token,
inputs,
output,
variadic: None,
generics: syn::Generics {
where_clause,
..generics
},
};
let lookahead = input.lookahead1();
let semi_token: Option<syn::Token![;]> = if lookahead.peek(syn::Token![;]) {
Some(input.parse()?)
} else {
panic!(
"Do not include implementation of delegated functions ({})",
signature.ident
);
};
let method = syn::TraitItemMethod {
attrs: Vec::new(),
sig: signature,
default: None,
semi_token,
};
Ok(DelegatedMethod {
method,
attributes,
visibility,
arguments,
})
}
}
struct DelegatedSegment {
delegator: syn::Expr,
methods: Vec<DelegatedMethod>,
}
impl syn::parse::Parse for DelegatedSegment {
fn parse(input: ParseStream) -> Result<Self, Error> {
if let Ok(keyword) = input.parse::<kw::target>() {
return Err(Error::new(keyword.span(), "You are using the old `target` expression, which is deprecated. Please replace `target` with `to`."));
} else {
input.parse::<kw::to>()?;
}
syn::Expr::parse_without_eager_brace(input).and_then(|delegator| {
let content;
syn::braced!(content in input);
let mut methods = vec![];
while !content.is_empty() {
methods.push(content.parse::<DelegatedMethod>().unwrap());
}
Ok(DelegatedSegment { delegator, methods })
})
}
}
struct DelegationBlock {
segments: Vec<DelegatedSegment>,
}
impl syn::parse::Parse for DelegationBlock {
fn parse(input: ParseStream) -> Result<Self, Error> {
let mut segments = vec![];
while !input.is_empty() {
segments.push(input.parse()?);
}
Ok(DelegationBlock { segments })
}
}
struct CallMethodAttribute {
name: syn::Ident,
}
impl syn::parse::Parse for CallMethodAttribute {
fn parse(input: ParseStream) -> Result<Self, Error> {
let content;
syn::parenthesized!(content in input);
Ok(CallMethodAttribute {
name: content.parse()?,
})
}
}
struct GenerateAwaitAttribute {
literal: syn::LitBool,
}
impl syn::parse::Parse for GenerateAwaitAttribute {
fn parse(input: ParseStream) -> Result<Self, Error> {
let content;
syn::parenthesized!(content in input);
Ok(GenerateAwaitAttribute {
literal: content.parse()?,
})
}
}
struct ParsedAttributes<'a> {
attributes: Vec<&'a syn::Attribute>,
target_method: Option<syn::Ident>,
generate_into: bool,
generate_await: bool,
}
fn parse_attributes<'a>(
attrs: &'a [syn::Attribute],
method: &syn::TraitItemMethod,
) -> ParsedAttributes<'a> {
let mut target_method: Option<syn::Ident> = None;
let mut generate_into: Option<bool> = None;
let mut generate_await: Option<bool> = None;
let mut map: HashMap<&str, Box<dyn FnMut(TokenStream2)>> = Default::default();
map.insert(
"call",
Box::new(|stream| {
let target = syn::parse2::<CallMethodAttribute>(stream).unwrap();
if target_method.is_some() {
panic!(
"Multiple call attributes specified for {}",
method.sig.ident
)
}
target_method = Some(target.name);
}),
);
map.insert(
"target_method",
Box::new(|_| {
panic!("You are using the old `target_method` attribute, which is deprecated. Please replace `target_method` with `call`.");
}),
);
map.insert(
"into",
Box::new(|_| {
if generate_into.is_some() {
panic!(
"Multiple into attributes specified for {}",
method.sig.ident
)
}
generate_into = Some(true);
}),
);
map.insert(
"await",
Box::new(|stream| {
if generate_await.is_some() {
panic!(
"Multiple await attributes specified for {}",
method.sig.ident
)
}
let generate = syn::parse2::<GenerateAwaitAttribute>(stream).unwrap();
generate_await = Some(generate.literal.value);
}),
);
let attrs: Vec<&syn::Attribute> = attrs
.iter()
.filter(|attr| {
if let syn::AttrStyle::Outer = attr.style {
for (ident, callback) in map.iter_mut() {
if attr.path.is_ident(ident) {
callback(attr.tokens.clone());
return false;
}
}
}
true
})
.collect();
drop(map);
ParsedAttributes {
attributes: attrs,
target_method,
generate_into: generate_into.unwrap_or(false),
generate_await: generate_await.unwrap_or_else(|| method.sig.asyncness.is_some()),
}
}
fn has_inline_attribute(attrs: &[&syn::Attribute]) -> bool {
attrs.iter().any(|attr| {
if let syn::AttrStyle::Outer = attr.style {
attr.path.is_ident("inline")
} else {
false
}
})
}
#[proc_macro]
pub fn delegate(tokens: TokenStream) -> TokenStream {
let block: DelegationBlock = syn::parse_macro_input!(tokens);
let sections = block.segments.iter().map(|delegator| {
let delegator_attribute = &delegator.delegator;
let functions = delegator.methods.iter().map(|method| {
let input = &method.method;
let signature = &input.sig;
let attributes = parse_attributes(&method.attributes, input);
if input.default.is_some() {
panic!(
"Do not include implementation of delegated functions ({})",
signature.ident
);
}
let args: Vec<syn::Expr> = method.arguments.clone().into_iter().collect();
let name = match &attributes.target_method {
Some(n) => n,
None => &input.sig.ident,
};
let inline = if has_inline_attribute(&attributes.attributes) {
quote!()
} else {
quote! { #[inline(always)] }
};
let visibility = &method.visibility;
let body = quote::quote! { #delegator_attribute.#name(#(#args),*) };
let body = if attributes.generate_await {
quote::quote! { #body.await }
} else {
body
};
let span = input.span();
let body = match &signature.output {
syn::ReturnType::Default => quote::quote! { #body; },
syn::ReturnType::Type(_, ret_type) => {
if attributes.generate_into {
quote::quote! { std::convert::Into::<#ret_type>::into(#body) }
} else {
body
}
}
};
let attrs = attributes.attributes;
quote::quote_spanned! {span=>
#(#attrs)*
#inline
#visibility #signature {
#body
}
}
});
quote! { #(#functions)* }
});
let result = quote! {
#(#sections)*
};
result.into()
}