use proc_macro2::TokenStream;
use quote::{format_ident, ToTokens};
use syn::{parse2, parse_quote, punctuated::Punctuated, Result};
use super::{parse::*, *};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum MakeSubsystem {
ImplSubsystemTrait,
AddContextTraitBounds,
}
pub(crate) fn impl_subsystem_context_trait_bounds(
attr: TokenStream,
orig: TokenStream,
make_subsystem: MakeSubsystem,
) -> Result<proc_macro2::TokenStream> {
let args = parse2::<SubsystemAttrArgs>(attr.clone())?;
let span = args.span();
let SubsystemAttrArgs { error_path, subsystem_ident, trait_prefix_path, .. } = args;
let mut item = parse2::<syn::Item>(orig)?;
let support_crate = support_crate()
.or_else(|_e| {
trait_prefix_path.clone().ok_or_else(|| {
syn::Error::new(attr.span(), "Couldn't find `orchestra` in manifest, but also missing a `prefix=` to help trait bound resolution")
})
})?;
let trait_prefix_path = trait_prefix_path.unwrap_or_else(|| parse_quote! { self });
if trait_prefix_path.segments.trailing_punct() {
return Err(syn::Error::new(trait_prefix_path.span(), "Must not end with `::`"))
}
let subsystem_ctx_trait = format_ident!("{}ContextTrait", subsystem_ident);
let subsystem_sender_trait = format_ident!("{}SenderTrait", subsystem_ident);
let extra_where_predicates: Punctuated<syn::WherePredicate, syn::Token![,]> = parse_quote! {
Context: #trait_prefix_path::#subsystem_ctx_trait,
Context: #support_crate::SubsystemContext,
<Context as #trait_prefix_path::#subsystem_ctx_trait>::Sender: #trait_prefix_path::#subsystem_sender_trait,
<Context as #support_crate::SubsystemContext>::Sender: #trait_prefix_path::#subsystem_sender_trait,
};
let apply_ctx_bound_if_present = move |generics: &mut syn::Generics| -> bool {
if generics
.params
.iter()
.find(|generic| match generic {
syn::GenericParam::Type(ty) if ty.ident == "Context" => true,
_ => false,
})
.is_some()
{
let where_clause = generics.make_where_clause();
where_clause.predicates.extend(extra_where_predicates.clone());
true
} else {
false
}
};
match item {
syn::Item::Impl(ref mut struktured_impl) => {
if make_subsystem == MakeSubsystem::ImplSubsystemTrait {
let error_path = error_path.ok_or_else(|| {
syn::Error::new(
span,
"Must annotate the identical orchestra error type via `error=..`.",
)
})?;
struktured_impl.trait_.replace((
None,
parse_quote! {
#support_crate::Subsystem<Context, #error_path>
},
syn::token::For::default(),
));
}
apply_ctx_bound_if_present(&mut struktured_impl.generics);
for item in struktured_impl.items.iter_mut() {
match item {
syn::ImplItem::Method(method) => {
apply_ctx_bound_if_present(&mut method.sig.generics);
},
_others => {
},
}
}
},
syn::Item::Fn(ref mut struktured_fn) => {
if make_subsystem == MakeSubsystem::ImplSubsystemTrait {
return Err(syn::Error::new(struktured_fn.span(), "Cannot make a free function a subsystem, did you mean to apply `contextbound` instead?"));
}
apply_ctx_bound_if_present(&mut struktured_fn.sig.generics);
},
other =>
return Err(syn::Error::new(
other.span(),
"Macro can only be annotated on functions or struct implementations",
)),
};
Ok(item.to_token_stream())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn is_path() {
let _p: Path = parse_quote! { self };
let _p: Path = parse_quote! { crate };
let _p: Path = parse_quote! { ::foo };
let _p: Path = parse_quote! { bar };
}
}