use proc_macro2::TokenStream;
use quote::{format_ident, quote, ToTokens};
use syn::parse::ParseStream;
use syn::{
parse_quote, parse_quote_spanned, FnArg, Ident, Lifetime, LitStr, Result, ReturnType, Token,
Type,
};
use crate::lifetime::TraitContext;
use crate::utils::*;
pub fn expand(attr: TokenStream, input: TokenStream) -> Result<TokenStream> {
let opts = syn::parse2::<Options>(attr)?;
let input_item = syn::parse2::<syn::Item>(input.clone())?;
let is_remote = opts.remote.is_some();
let output = match input_item {
syn::Item::Trait(t) => expand_trait(opts, t)?,
syn::Item::Fn(f) => expand_fn(opts, f)?,
item => {
return Err(syn::Error::new_spanned(
&item,
"expected a `fn` or `trait` item",
))
},
};
let input = (!is_remote).then_some(input);
Ok(quote!(#input #output))
}
fn expand_trait(opts: Options, mut dyn_trait: syn::ItemTrait) -> Result<TokenStream> {
let target_trait = if let Some(remote) = opts.remote {
remote
} else {
let dyn_trait_name = opts
.rename
.unwrap_or_else(|| format_ident!("Dyn{}", dyn_trait.ident));
let target_trait_name = std::mem::replace(&mut dyn_trait.ident, dyn_trait_name);
parse_quote!(#target_trait_name)
};
let impl_target = {
let target_trait_name = &target_trait.segments.last().unwrap().ident;
format_ident!("{}Implementor", target_trait_name)
};
let (_, ty_generics, where_clause) = dyn_trait.generics.split_for_impl();
let mut trait_impl_items = TokenStream::new();
for item in dyn_trait.items.iter_mut() {
let impl_item = match item {
syn::TraitItem::Const(syn::TraitItemConst {
attrs,
const_token,
ident,
colon_token,
ty,
semi_token,
..
}) => {
let attrs = attrs.outer();
quote!(#(#attrs)* #const_token #ident #colon_token #ty
= #impl_target::#ident #semi_token)
},
syn::TraitItem::Type(syn::TraitItemType {
attrs,
type_token,
ident,
generics,
semi_token,
..
}) => {
let attrs = attrs.outer();
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
quote!(#(#attrs)* #type_token #ident #impl_generics
= #impl_target::#ident #ty_generics #where_clause #semi_token)
},
syn::TraitItem::Fn(syn::TraitItemFn { attrs, sig, .. }) => {
let context = TraitContext {
generics: &dyn_trait.generics,
};
let transformed = transform_fn(Some(&context), sig, false)?;
let attrs_outer = attrs.outer();
let attrs_inner = attrs.inner();
let target_fn = quote_with(|tokens| {
impl_target.to_tokens(tokens);
NewToken![::].to_tokens(tokens);
sig.ident.to_tokens(tokens);
});
let impl_body = quote_transformed_body(transformed, &target_fn, sig);
quote!(#(#attrs_outer)* #sig { #(#attrs_inner)* #impl_body })
},
_ => continue,
};
trait_impl_items.extend(impl_item);
}
let impl_generics = quote_impl_generics(&dyn_trait.generics);
let dyn_trait_name = &dyn_trait.ident;
Ok(quote!(
#[allow(async_fn_in_trait)]
#[allow(clippy::type_complexity)]
#dyn_trait
#[allow(clippy::type_complexity)]
impl<#impl_generics #impl_target: #target_trait #ty_generics>
#dyn_trait_name #ty_generics for #impl_target
#where_clause { #trait_impl_items }
))
}
fn expand_fn(opts: Options, mut dyn_fn: syn::ItemFn) -> Result<TokenStream> {
let syn::ItemFn {
vis,
sig,
attrs,
block: _,
} = &mut dyn_fn;
let target_fn = if let Some(remote) = opts.remote {
remote
} else {
let dyn_fn_name = opts
.rename
.unwrap_or_else(|| format_ident!("dyn_{}", sig.ident));
let target_fn_name = std::mem::replace(&mut sig.ident, dyn_fn_name);
parse_quote!(#target_fn_name)
};
let transformed = transform_fn(None, sig, true)?;
let attrs_outer = attrs.outer();
let attrs_inner = attrs.inner();
let impl_body = quote_transformed_body(transformed, &target_fn, sig);
Ok(quote!(#(#attrs_outer)* #vis #sig { #(#attrs_inner)* #impl_body }))
}
fn quote_transformed_body(
transformed: TransformResult,
target: &dyn ToTokens,
sig: &syn::Signature,
) -> impl ToTokens {
let arg_idents = sig.inputs.pairs().map(|p| {
quote_with(move |tokens| {
match p.value() {
FnArg::Receiver(r) => r.self_token.to_tokens(tokens),
FnArg::Typed(t) => t.pat.to_tokens(tokens),
}
p.punct_or_default().to_tokens(tokens);
})
});
match transformed {
TransformResult::Noop if sig.asyncness.is_some() => {
quote!(#target (#(#arg_idents)*).await)
},
TransformResult::Noop => {
quote!(#target (#(#arg_idents)*))
},
TransformResult::Function | TransformResult::Method => {
let recv = sig.receiver().map(|r| &r.self_token);
quote!(::dynify::__from_fn!([#recv] #target, #(#arg_idents)*))
},
}
}
fn quote_impl_generics(generics: &syn::Generics) -> impl '_ + ToTokens {
quote_with(move |tokens| {
let is_lifetime = |p: &syn::GenericParam| matches!(p, syn::GenericParam::Lifetime(_));
generics
.params
.pairs()
.filter(|p| is_lifetime(p.value()))
.chain(generics.params.pairs().filter(|p| !is_lifetime(p.value())))
.for_each(|p| {
p.value().to_tokens(tokens);
p.punct_or_default().to_tokens(tokens);
});
})
}
#[derive(Clone, Copy)]
enum TransformResult {
Noop,
Function,
Method,
}
fn transform_fn(
context: Option<&TraitContext>,
sig: &mut syn::Signature,
force: bool,
) -> Result<TransformResult> {
let fn_span = sig.ident.span();
if sig.asyncness.is_none() && get_impl_type(&sig.output).is_none() {
if force {
return Err(syn::Error::new(
fn_span,
"input function must return an `impl` type",
));
} else {
return Ok(TransformResult::Noop);
}
}
let sealed_recv = match sig.receiver() {
Some(r) => crate::receiver::infer_receiver(r)
.ok_or_else(|| syn::Error::new(r.self_token.span, "unsupported receiver type"))
.map(Some)?,
None if force => None,
None => return Ok(TransformResult::Noop),
};
let output_lifetime = Lifetime::new("'dynify", fn_span);
crate::lifetime::inject_output_lifetime(context, sig, &output_lifetime)?;
let input_types = quote_with(|tokens| {
sealed_recv
.as_ref()
.map(|r| quote!(::dynify::r#priv::#r,))
.to_tokens(tokens);
sig.inputs
.pairs()
.skip(sealed_recv.is_some() as usize)
.for_each(|p| {
match p.value() {
FnArg::Receiver(r) => r.ty.to_tokens(tokens),
FnArg::Typed(t) => t.ty.to_tokens(tokens),
}
p.punct_or_default().to_tokens(tokens);
});
});
let output_type = match &sig.output {
ReturnType::Default => ReturnType::Type(
NewToken![->],
parse_quote_spanned!(fn_span => ::dynify::r#priv::Fn<
(#input_types),
dyn #output_lifetime + ::core::future::Future<Output = ()>
>),
),
ReturnType::Type(r, ty) if sig.asyncness.is_some() => ReturnType::Type(
*r,
parse_quote_spanned!(fn_span => ::dynify::r#priv::Fn<
(#input_types),
dyn #output_lifetime + ::core::future::Future<Output = #ty>
>),
),
ty @ ReturnType::Type(..) => {
let (r, ty) = get_impl_type(ty).unwrap();
let bounds = ty
.bounds
.pairs()
.filter(|p| !matches!(p.value(), syn::TypeParamBound::Lifetime(_)));
ReturnType::Type(
r,
parse_quote_spanned!(fn_span => ::dynify::r#priv::Fn<
(#input_types),
dyn #output_lifetime + #(#bounds)*
>),
)
},
};
sig.output = output_type;
sig.asyncness = None;
Ok(sealed_recv
.map(|_| TransformResult::Method)
.unwrap_or(TransformResult::Function))
}
fn get_impl_type(ty: &ReturnType) -> Option<(Token![->], &syn::TypeImplTrait)> {
as_variant!(ty, ReturnType::Type(r, t))
.and_then(|(r, ty)| as_variant!(&**ty, Type::ImplTrait).map(|ty| (*r, ty)))
}
struct Options {
rename: Option<Ident>,
remote: Option<syn::Path>,
}
impl syn::parse::Parse for Options {
fn parse(input: ParseStream) -> Result<Options> {
syn::custom_keyword!(remote);
let mut attrs = Options {
rename: None,
remote: None,
};
if input.peek2(Token![=]) {
let remote_token = input.parse::<remote>()?;
let _eq_token = input.parse::<Token![=]>()?;
let remote = input.parse::<LitStr>()?.parse::<syn::Path>()?;
if remote.segments.is_empty() {
return Err(syn::Error::new(remote_token.span, "invalid remote type"));
}
attrs.remote = Some(remote);
} else if !input.is_empty() {
attrs.rename = Some(input.parse()?);
}
input.parse::<syn::parse::Nothing>()?;
Ok(attrs)
}
}
#[cfg(test)]
#[path = "dynify_tests.rs"]
mod tests;