use proc_macro2::{Ident, TokenStream, TokenTree};
use quote::quote;
use syn::ItemTrait;
use crate::util::{compile_err, compile_error_str};
pub(crate) fn parse_names_from_tokens(
tokens: &[TokenTree], trait_def: &ItemTrait,
) -> Result<Vec<Ident>, TokenStream> {
if tokens.is_empty() {
return Err(compile_error_str(
"batch-impl: the directive's argument list cannot be empty",
proc_macro2::Span::call_site(),
));
}
parse_name_tokens(tokens, trait_def, "directive arguments")
}
fn parse_name_tokens(
tokens: &[TokenTree], trait_def: &ItemTrait, what: &str,
) -> Result<Vec<Ident>, TokenStream> {
if tokens.is_empty() {
return Err(compile_err!("batch-impl: {} cannot be empty", what));
}
let mut keep = vec![];
let mut exclude = vec![];
let mut prev_was_comma = true; let mut i = 0;
while i < tokens.len() {
match &tokens[i] {
TokenTree::Ident(id) => {
keep.push(Ident::new(&id.to_string(), id.span()));
prev_was_comma = false;
i += 1;
}
TokenTree::Group(g) if g.delimiter() == delimiter![[]] => {
let inner = g.stream().into_iter().collect::<Vec<_>>();
keep.extend(parse_name_tokens(&inner, trait_def, what)?);
prev_was_comma = false;
i += 1;
}
TokenTree::Punct(p) if p.as_char() == ',' => {
if prev_was_comma {
return Err(compile_err!(
"batch-impl: in {}, a comma is in an illegal position \
(no leading/trailing/consecutive commas)",
what
));
}
prev_was_comma = true;
i += 1;
}
TokenTree::Punct(p) if p.as_char() == '-' => {
let (ids, consumed) =
parse_minus_target(&tokens[i + 1..], trait_def, what)?;
exclude.extend(ids);
i += 1 + consumed;
prev_was_comma = false;
}
_ => {
return Err(compile_err!(
"batch-impl: in {}, expected an identifier, comma, `[...]` \
list, or `-` exclusion, got `{}`",
what,
tokens[i]
));
}
}
}
if prev_was_comma {
return Err(compile_err!(
"batch-impl: in {}, a comma is in an illegal position \
(no leading/trailing/consecutive commas)",
what
));
}
let names = keep
.into_iter()
.filter(|id| !exclude.iter().any(|e| e == id))
.collect::<Vec<_>>();
if names.is_empty() {
return Err(compile_err!("batch-impl: {} cannot be empty", what));
}
Ok(names)
}
fn parse_minus_target(
tokens: &[TokenTree], trait_def: &ItemTrait, what: &str,
) -> Result<(Vec<Ident>, usize), TokenStream> {
match tokens.first() {
Some(TokenTree::Ident(id)) => {
Ok((vec![Ident::new(&id.to_string(), id.span())], 1))
}
Some(TokenTree::Group(g)) if g.delimiter() == delimiter![[]] => {
let inner = g.stream().into_iter().collect::<Vec<_>>();
let ids = parse_name_tokens(&inner, trait_def, what)?;
Ok((ids, 1))
}
_ => Err(compile_err!(
"batch-impl: in {}, after `-` expected an identifier or `[...]` \
list (e.g. `-foo`, `-[a,b]`)",
what
)),
}
}
#[derive(Clone, Copy, PartialEq)]
pub(crate) enum ReceiverFilter {
Ref,
Value,
Static,
}
pub(crate) type AllMarkerSpec =
((bool, bool, bool), Option<bool>, Option<ReceiverFilter>);
pub(crate) fn resolve_all_marker(name: &str) -> Option<AllMarkerSpec> {
match name {
"all" => ((true, true, true), None, None).into(),
"all_methods" => ((true, false, false), None, None).into(),
"all_constants" => ((false, true, false), None, None).into(),
"all_types" => ((false, false, true), None, None).into(),
"all_default" => ((true, true, true), true.into(), None).into(),
"all_default_methods" => ((true, false, false), true.into(), None).into(),
"all_default_constants" => ((false, true, false), true.into(), None).into(),
"all_default_types" => ((false, false, true), true.into(), None).into(),
"all_required" => ((true, true, true), false.into(), None).into(),
"all_required_methods" => ((true, false, false), false.into(), None).into(),
"all_required_constants" => ((false, true, false), false.into(), None).into(),
"all_required_types" => ((false, false, true), false.into(), None).into(),
"all_ref_methods" => {
((true, false, false), None, ReceiverFilter::Ref.into()).into()
}
"all_value_methods" => {
((true, false, false), None, ReceiverFilter::Value.into()).into()
}
"all_static_methods" => {
((true, false, false), None, ReceiverFilter::Static.into()).into()
}
_ => None,
}
}
#[derive(Clone, Copy, PartialEq)]
pub(crate) enum GenericFilter {
Type,
Const,
Lifetime,
}
pub(crate) fn resolve_generic_marker(name: &str) -> Option<GenericFilter> {
match name {
"all_type_params" => GenericFilter::Type.into(),
"all_const_params" => GenericFilter::Const.into(),
"all_lifetimes" => GenericFilter::Lifetime.into(),
_ => None,
}
}
pub(crate) fn get_trait_generic_decl(
trait_def: &ItemTrait, f: GenericFilter,
) -> Option<TokenStream> {
let names = trait_def
.generics
.params
.iter()
.filter_map(|p| match (p, f) {
(syn::GenericParam::Type(tp), GenericFilter::Type) => {
let id = tp.ident.clone();
quote!(#id).into()
}
(syn::GenericParam::Const(cp), GenericFilter::Const) => {
quote!(#cp).into()
}
(syn::GenericParam::Lifetime(ld), GenericFilter::Lifetime) => {
quote!(#ld).into()
}
_ => None,
})
.collect::<Vec<_>>();
if names.is_empty() {
return None;
}
quote::quote!(< #(#names),* >).into()
}
pub(crate) fn get_trait_item_names(
trait_def: &ItemTrait, include_fn: bool, include_const: bool, include_type: bool,
default: Option<bool>, receiver: Option<ReceiverFilter>,
) -> Vec<Ident> {
let mut names = vec![];
for item in &trait_def.items {
let (kind, has_default) = match item {
syn::TraitItem::Fn(f) => (0u8, f.default.is_some()),
syn::TraitItem::Const(c) => (1, c.default.is_some()),
syn::TraitItem::Type(t) => (2, t.default.is_some()),
_ => (3, false),
};
let include = match kind {
0 => include_fn,
1 => include_const,
2 => include_type,
_ => false,
};
let receiver_ok = match (kind, receiver) {
(0, Some(rk)) => {
let rk_syn = match item {
syn::TraitItem::Fn(f) => f.sig.receiver().map(|r| &r.kind),
_ => None,
};
match rk {
ReceiverFilter::Ref => {
matches!(rk_syn, Some(syn::ReceiverKind::Reference(..)))
}
ReceiverFilter::Value => matches!(
rk_syn,
Some(syn::ReceiverKind::Value | syn::ReceiverKind::Typed(..))
),
ReceiverFilter::Static => rk_syn.is_none(),
}
}
_ => true,
};
if include && receiver_ok && default.is_none_or(|d| d == has_default) {
match item {
syn::TraitItem::Fn(f) => names.push(f.sig.ident.clone()),
syn::TraitItem::Const(c) => names.push(c.ident.clone()),
syn::TraitItem::Type(t) => names.push(t.ident.clone()),
_ => {}
}
}
}
names
}
pub(crate) fn get_trait_item<'a>(
trait_def: &'a ItemTrait, name: &Ident,
) -> Result<&'a syn::TraitItem, TokenStream> {
for item in &trait_def.items {
let found = match item {
syn::TraitItem::Fn(f) => f.sig.ident == *name,
syn::TraitItem::Const(c) => c.ident == *name,
syn::TraitItem::Type(t) => t.ident == *name,
_ => false,
};
if found {
return Ok(item);
}
}
Err(compile_err!(
"batch-impl: item `{}` not found in trait `{}`",
trait_def.ident,
name
))
}
pub(crate) fn build_from_item(
item: &syn::TraitItem, body: &TokenStream,
) -> TokenStream {
match item {
syn::TraitItem::Fn(f) => {
let mut f = f.clone();
f.semi_token = None;
f.default = syn::Block {
brace_token: syn::token::Brace::default(),
stmts: vec![syn::Stmt::Expr(syn::Expr::Verbatim(body.clone()), None)],
}
.into();
quote! {#f}
}
syn::TraitItem::Const(c) => {
let mut c = c.clone();
c.default =
(syn::token::Eq::default(), syn::Expr::Verbatim(body.clone())).into();
quote! {#c}
}
syn::TraitItem::Type(t) => {
let mut t = t.clone();
t.default =
(syn::token::Eq::default(), syn::Type::Verbatim(body.clone())).into();
quote! {#t}
}
_ => compile_error_str(
"invalid item form; this error cannot occur",
proc_macro2::Span::call_site(),
),
}
}
pub(crate) fn collect_call_args(sig: &syn::Signature) -> Result<Vec<Ident>, String> {
let mut args = vec![];
for arg in &sig.inputs {
match arg {
syn::FnArg::Receiver(_) => {}
syn::FnArg::Typed(pat_type) => {
if let syn::Pat::Ident(pat_ident) = &*pat_type.pat {
args.push(pat_ident.ident.clone());
} else {
return Err(quote!(#pat_type).to_string());
}
}
}
}
Ok(args)
}