use crate::helper::{OwnedAttrs, Query};
use crate::options::{Check, Options};
use proc_macro2::{Ident, Span, TokenStream};
use syn::parse::{Parse, ParseStream};
use syn::{Result, Token};
use template_quote::quote;
const TAG: &str = "attr";
const STATE_ERROR: &str = "announce: the state of the attribute query was changed by a check. \
A check must forward `{ STATE }` unchanged and may only append `attrs(name, ..)`; see the \
`announce::helper::attrs` docs";
pub(crate) fn query(
opts: &Options,
attr: &TokenStream,
item: &TokenStream,
random: u64,
ask: &[Check],
) -> TokenStream {
let krate = opts.krate();
let lit = syn::LitInt::new(&format!("{}u64", random), Span::call_site());
let tag = Ident::new(TAG, Span::call_site());
let paths: Vec<&syn::Path> = ask.iter().map(|c| &c.path).collect();
let first = paths.first().expect("a query needs a check");
let format = crate::helper::format_prefix();
quote! {
#first ! {
#format
@__announce_attrs
[ #(#paths ! ,)* #krate::__private::finish! ]
{ [ #lit ; #tag ( #attr ) { #item } ] }
}
}
}
struct State {
random: u64,
attr: TokenStream,
item: TokenStream,
owned: OwnedAttrs,
}
impl Parse for State {
fn parse(input: ParseStream) -> Result<Self> {
let core;
syn::bracketed!(core in input);
let lit: syn::LitInt = core.parse()?;
core.parse::<Token![;]>()?;
let tag: Ident = core.parse()?;
if tag != TAG {
return Err(syn::Error::new(tag.span(), STATE_ERROR));
}
let attr;
syn::parenthesized!(attr in core);
let attr: TokenStream = attr.parse()?;
let item;
syn::braced!(item in core);
let item: TokenStream = item.parse()?;
if !core.is_empty() {
return Err(core.error(STATE_ERROR));
}
let mut owned = OwnedAttrs::new();
while !input.is_empty() {
let key: Ident = input
.parse()
.map_err(|e| syn::Error::new(e.span(), STATE_ERROR))?;
if key != "attrs" || !input.peek(syn::token::Paren) {
return Err(syn::Error::new(key.span(), STATE_ERROR));
}
let names;
syn::parenthesized!(names in input);
let names = OwnedAttrs::parse_names(names.parse()?)?;
owned.extend(names.names().iter().cloned());
}
Ok(State {
random: lit.base10_parse()?,
attr,
item,
owned,
})
}
}
pub fn finish(input: TokenStream) -> TokenStream {
try_finish(input).unwrap_or_else(|e| e.to_compile_error())
}
pub fn try_finish(input: TokenStream) -> Result<TokenStream> {
let query: Query = syn::parse2(input).map_err(|e| {
if crate::helper::is_format_error(&e) {
return e;
}
syn::Error::new(
e.span(),
format!(
"announce: `finish!` is the end of the attribute query that `#[announce]` \
sends through its checks, and cannot be called by hand ({})",
e
),
)
})?;
match query.list.0.as_slice() {
[crate::helper::Cont::Bang(_)] => {}
[] => {
return Err(syn::Error::new(
Span::call_site(),
"announce: the attribute query reached `finish!` with an empty list; the list \
starts with the macro that is called, so a check must forward its list \
without its own first element, and keep the rest (`[ finish! ]` at the end)",
))
}
_ => {
return Err(syn::Error::new(
Span::call_site(),
"announce: the attribute query reached `finish!` with elements left in its list \
(it must be exactly `[ finish! ]`); a check must forward the list it received \
without its own first element",
))
}
}
let mut t = crate::timing::Timer::new("finish");
let state: State = syn::parse2(query.state)?;
state.owned.check_names()?;
let opts: Options = syn::parse2(state.attr.clone())?;
t.step("state");
let head = crate::scan::head(&state.item)?;
t.step("scan");
Ok(crate::announce_now(
&opts,
&state.attr,
state.item,
state.random,
&state.owned,
&head,
))
}