use proc_macro::TokenStream;
use proc_macro2::{Ident, Span, TokenStream as TokenStream2};
use proc_macro_crate::{crate_name, FoundCrate};
use quote::quote;
use syn::parse::{Parse, ParseStream, Result};
mod codec;
mod from_assignment_helpers;
mod index_assignment;
mod single_page;
pub(crate) fn vote_field(n: usize) -> Ident {
quote::format_ident!("votes{}", n)
}
pub(crate) fn syn_err(message: &'static str) -> syn::Error {
syn::Error::new(Span::call_site(), message)
}
#[proc_macro]
pub fn generate_solution_type(item: TokenStream) -> TokenStream {
let solution_def = syn::parse_macro_input!(item as SolutionDef);
let imports = imports().unwrap_or_else(|e| e.to_compile_error());
let def = single_page::generate(solution_def).unwrap_or_else(|e| e.to_compile_error());
quote!(
#imports
#def
)
.into()
}
struct SolutionDef {
vis: syn::Visibility,
ident: syn::Ident,
voter_type: syn::Type,
target_type: syn::Type,
weight_type: syn::Type,
max_voters: syn::Type,
count: usize,
compact_encoding: bool,
}
fn check_attributes(input: ParseStream) -> syn::Result<bool> {
let mut attrs = input.call(syn::Attribute::parse_outer).unwrap_or_default();
if attrs.len() > 1 {
let extra_attr = attrs.pop().expect("attributes vec with len > 1 can be popped");
return Err(syn::Error::new_spanned(
extra_attr,
"compact solution can accept only #[compact]",
));
}
if attrs.is_empty() {
return Ok(false);
}
let attr = attrs.pop().expect("attributes vec with len 1 can be popped.");
if attr.path().is_ident("compact") {
Ok(true)
} else {
Err(syn::Error::new_spanned(attr, "compact solution can accept only #[compact]"))
}
}
impl Parse for SolutionDef {
fn parse(input: ParseStream) -> syn::Result<Self> {
let compact_encoding = check_attributes(input)?;
let vis: syn::Visibility = input.parse()?;
<syn::Token![struct]>::parse(input)?;
let ident: syn::Ident = input.parse()?;
<syn::Token![::]>::parse(input)?;
let generics: syn::AngleBracketedGenericArguments = input.parse()?;
if generics.args.len() != 4 {
return Err(syn_err("Must provide 4 generic args."));
}
let expected_types = ["VoterIndex", "TargetIndex", "Accuracy", "MaxVoters"];
let mut types: Vec<syn::Type> = generics
.args
.iter()
.zip(expected_types.iter())
.map(|(t, expected)| match t {
syn::GenericArgument::Type(ty) => {
Err(syn::Error::new_spanned(
ty,
format!("Expected binding: `{} = ...`", expected),
))
},
syn::GenericArgument::AssocType(syn::AssocType { ident, ty, .. }) => {
if ident == expected {
Ok(ty.clone())
} else {
Err(syn::Error::new_spanned(ident, format!("Expected `{}`", expected)))
}
},
_ => Err(syn_err("Wrong type of generic provided. Must be a `type`.")),
})
.collect::<Result<_>>()?;
let max_voters = types.pop().expect("Vector of length 4 can be popped; qed");
let weight_type = types.pop().expect("Vector of length 3 can be popped; qed");
let target_type = types.pop().expect("Vector of length 2 can be popped; qed");
let voter_type = types.pop().expect("Vector of length 1 can be popped; qed");
let count_expr: syn::ExprParen = input.parse()?;
let count = parse_parenthesized_number::<usize>(count_expr)?;
Ok(Self {
vis,
ident,
voter_type,
target_type,
weight_type,
max_voters,
count,
compact_encoding,
})
}
}
fn parse_parenthesized_number<N: std::str::FromStr>(input_expr: syn::ExprParen) -> syn::Result<N>
where
<N as std::str::FromStr>::Err: std::fmt::Display,
{
let expr = input_expr.expr;
let expr_lit = match *expr {
syn::Expr::Lit(count_lit) => count_lit.lit,
_ => return Err(syn_err("Count must be literal.")),
};
let int_lit = match expr_lit {
syn::Lit::Int(int_lit) => int_lit,
_ => return Err(syn_err("Count must be int literal.")),
};
int_lit.base10_parse::<N>()
}
fn imports() -> Result<TokenStream2> {
match crate_name("pezframe-election-provider-support") {
Ok(FoundCrate::Itself) => Ok(quote! {
use crate as _feps;
use _feps::private as _fepsp;
}),
Ok(FoundCrate::Name(pezframe_election_provider_support)) => {
let ident = syn::Ident::new(&pezframe_election_provider_support, Span::call_site());
Ok(quote!(
use #ident as _feps;
use _feps::private as _fepsp;
))
},
Err(e) => match crate_name("pezkuwi-sdk") {
Ok(FoundCrate::Name(pezkuwi_sdk)) => {
let ident = syn::Ident::new(&pezkuwi_sdk, Span::call_site());
Ok(quote!(
use #ident::pezframe_election_provider_support as _feps;
use _feps::private as _fepsp;
))
},
_ => Err(syn::Error::new(Span::call_site(), e)),
},
}
}
#[cfg(test)]
mod tests {
#[test]
fn ui_fail() {
if std::env::var("RUN_UI_TESTS").is_err() {
return;
}
let cases = trybuild::TestCases::new();
cases.compile_fail("tests/ui/fail/*.rs");
}
}