use crate::capture_output;
use crate::helper::{OwnedAttrs, Query};
use crate::options::Options;
use proc_macro2::{Ident, Span, TokenStream};
use syn::parse::{Parse, ParseStream};
use syn::{Result, Token};
use template_quote::quote;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum Mode {
Attribute,
Derive,
}
impl Mode {
fn word(self) -> &'static str {
match self {
Mode::Attribute => "attr",
Mode::Derive => "derive",
}
}
}
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,
mode: Mode,
attr: &TokenStream,
item: &TokenStream,
random: u64,
) -> TokenStream {
let krate = opts.krate();
let lit = syn::LitInt::new(&format!("{}u64", random), Span::call_site());
let mode = Ident::new(mode.word(), Span::call_site());
let paths: Vec<&syn::Path> = opts.checks.iter().map(|c| &c.path).collect();
let first = paths.first().expect("a query needs a check");
quote! {
#first ! {
@__announce_attrs
[ #(#paths ! ,)* #krate::__private::finish! ]
{ [ #lit ; #mode ( #attr ) { #item } ] }
}
}
}
struct State {
random: u64,
mode: Mode,
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 mode: Ident = core.parse()?;
let mode = match mode.to_string().as_str() {
"attr" => Mode::Attribute,
"derive" => Mode::Derive,
_ => return Err(syn::Error::new(mode.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()?,
mode,
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| {
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");
match state.mode {
Mode::Attribute => {
crate::scan::head(&state.item)?;
t.step("scan");
Ok(capture_output(
&opts,
&state.attr,
state.item,
state.random,
&state.owned,
))
}
Mode::Derive => {
let item: syn::Item = syn::parse2(state.item)?;
crate::generate::ident_vis(&item)?;
t.step("parse");
crate::finish_derive_now(item, opts, state.random, &state.owned, &mut t)
}
}
}