use syn::{parse::Nothing, spanned::Spanned, ImplItem::{Method, Const}, Type::Path,
ItemFn, ItemImpl, DeriveInput, Ident, Attribute};
use proc_macro::TokenStream;
use proc_macro2::Span;
use quote::quote;
#[proc_macro_attribute]
pub fn override_final(attr: TokenStream, input: TokenStream)-> TokenStream {
syn::parse_macro_input!(attr as Nothing);
if let Ok(impl_block) = syn::parse::<ItemImpl>(input.clone()) {
let self_type = match impl_block.self_ty.as_ref() {
Path(path) => path,
_ => return quick_error(format!("Could not get Path for impl \
(should never see this)")),
}.path.segments[0].ident.to_string();
match impl_block.items.into_iter().fold(None, |acc, item| {
let new_error = match item {
Method(method) => {
let priority_lesser =
std::env::var(format!("__override_final_method_{}_{}", self_type,
&method.sig.ident.to_string()))
.expect("Failed covering final. \
Did you configure your build script to watch this file?");
syn::Error::new(
method.sig.ident.span(),
match priority_lesser.as_str() {
"0" =>
format!("Method requested final. \
Replace #[override_final] with #[default] or higher \
on a (seperate if required) impl block to make top \
level."),
"1" =>
format!("Method requested final. \
Replace #[override_final] with #[override_default] \
or higher on a (seperate if required) impl block to \
make top level."),
priority_lesser =>
format!("Method requested final. \
Replace #[override_final] with \
#[override_default(priority = {})] or higher on a \
(seperate if required) impl block to make top level.",
priority_lesser),
}
)
},
Const(constant) => {
let priority_lesser =
std::env::var(format!("__override_final_implconst_{}_{}",
self_type, &constant.ident.to_string()))
.expect("Failed covering final. \
Did you configure your build script to watch this file?");
syn::Error::new(
constant.ident.span(),
match priority_lesser.as_str() {
"0" =>
format!("Impl constant requested final. \
Replace #[override_final] with #[default] or higher \
on a (seperate if required) impl block to make top \
level."),
"1" =>
format!("Impl constant requested final. \
Replace #[override_final] with #[override_default] \
or higher on a (seperate if required) impl block \
to make top level."),
priority_lesser =>
format!("Impl constant requested final. \
Replace #[override_final] with \
#[override_default(priority = {})] or higher on a \
(seperate if required) impl block to make top level.",
priority_lesser),
}
)
}
item => syn::Error::new(item.span(),
format!("I can't finalize this yet")),
};
match acc {
None => Some(new_error),
Some(mut errors) => {
errors.combine(new_error);
Some(errors)
},
}
}) {
Some(errors) => errors.to_compile_error().into(),
None => input,
}
} else if let Ok(item) = syn::parse::<ItemFn>(input) {
let priority_lesser =
std::env::var(format!("__override_final_func_{}", &item.sig.ident.to_string()))
.expect("Failed covering final. \
Did you configure your build script to watch this file?");
return syn::Error::new(
item.sig.ident.span(),
match priority_lesser.as_str() {
"0" =>
format!("Function requested final. \
Replace #[override_final] with #[default] or higher \
to make top level."),
"1" =>
format!("Function requested final. \
Replace #[override_final] with #[override_default] \
or higher to make top level."),
priority_lesser =>
format!("Function requested final. \
Replace #[override_final] with #[override_default(priority = {})] \
or higher to make top level.",
priority_lesser),
}
).to_compile_error().into();
} else {
quick_error(format!("I can't finalize whatever this is attached to yet"))
}
}
#[proc_macro_attribute]
pub fn default(attr: TokenStream, input: TokenStream) -> TokenStream {
syn::parse_macro_input!(attr as Nothing);
attach(input, 0)
}
#[proc_macro_attribute]
pub fn override_default(attr: TokenStream, input: TokenStream) -> TokenStream {
let priority = {
if let Ok(_) = syn::parse::<Nothing>(attr.clone()) {
1
} else if let Ok(syn::Expr::Assign(assign)) = syn::parse::<syn::Expr>(attr.clone()) {
if let (syn::Expr::Path(left), syn::Expr::Lit(right)) = (*assign.left, *assign.right) {
if left.path.segments[0].ident.to_string() == "priority" {
if let syn::Lit::Int(lit) = right.lit {
if let Ok(i) = lit.base10_parse::<u32>() {
i
} else {
return quick_error(format!("Could not parse literal"));
}
} else {
return quick_error(format!("Expected integer literal"));
}
} else {
return quick_error(format!("Unexpected arguement name"));
}
} else {
return quick_error(format!("Incorrect arguement format / expected positive integer"));
}
} else {
return quick_error(format!("Unexpected arguement {} (expected priority)", attr.to_string()));
}
};
attach(input, priority)
}
fn quick_error(message: String) -> TokenStream {
syn::Error::new(
Span::call_site(),
message
).to_compile_error().into()
}
fn attach(input: TokenStream, priority: u32) -> TokenStream {
if let Ok(item) = syn::parse::<ItemImpl>(input.clone()) {
attach_impl(item, priority)
} else if let Ok(item) = syn::parse::<ItemFn>(input) {
attach_function(item, priority)
} else {
quick_error(format!("I can't parse this yet"))
}
}
fn attach_function(mut input: ItemFn, priority: u32) -> TokenStream {
attr_add(&mut input.attrs,
format!("__override_priority_{}_func_{}", priority, &input.sig.ident));
match std::env::var(format!("__override_acceptflags_func_{}", &input.sig.ident)) {
Err(_) => {
TokenStream::from(quote! {
#input
})
},
Ok(flagstrs) => {
let flags = flagstrs.split(" ").map(|f| {
let v = f.split("_").collect::<Vec<&str>>();
(v[0], v[1])
}).collect::<Vec<(&str, &str)>>();
let old_attrs = input.attrs.clone();
let old_ident = &input.sig.ident;
let old_sig = input.sig.clone();
let mut args = Vec::new();
for input in &input.sig.inputs {
match input {
syn::FnArg::Typed(t) => {
match t.pat.as_ref() {
syn::Pat::Ident(p) => args.push(&p.ident),
_ => return syn::Error::new(
t.span(),
format!("I do not know what this is and \
it can't be overriden"))
.to_compile_error().into(),
}
},
arg => {
return syn::Error::new(
arg.span(),
format!("I can only override typed arguments"))
.to_compile_error().into()
},
}
};
let if_branches = flags.into_iter().map(|(modifiers, flagstr)| {
let flagext = Ident::new(&format!("__override_flagext_{}_{}",
flagstr, old_ident),
Span::call_site());
if modifiers.find("i").is_some() {
quote! {
if CLAP_FLAGS.occurrences_of(#flagstr) == 0 {
#flagext (#(#args),*)
}
}
} else {
quote! {
if CLAP_FLAGS.occurrences_of(#flagstr) > 0 {
#flagext (#(#args),*)
}
}
}
}).collect::<Vec<proc_macro2::TokenStream>>();
input.sig.ident = Ident::new(&format!("__override_flagentry_{}",
old_ident),
Span::call_site());
let sigentry = &input.sig.ident;
attr_inline(&mut input.attrs);
TokenStream::from(quote! {
#(#old_attrs)*
#old_sig {
#(#if_branches else )* {
#sigentry (#(#args),*)
}
}
#input
})
}
}
}
fn attach_impl(mut input: ItemImpl, priority: u32) -> TokenStream {
let self_type = match input.self_ty.as_ref() {
Path(path) => path,
item => return syn::Error::new(
item.span(),
format!("Could not get Path for impl (should never see this)"))
.to_compile_error().into(),
}.path.segments[0].ident.to_string();
let mut additional_items: Vec::<syn::ImplItem> = Vec::new();
for item in &mut input.items {
match item {
Method(method) => {
attr_add(&mut method.attrs, format!("__override_priority_{}_method_{}_{}",
priority,
self_type,
&method.sig.ident));
if let Ok(flagstrs) = std::env::var(format!("__override_acceptflags_method_{}_{}",
self_type, &method.sig.ident)) {
let flags = flagstrs.split(" ").map(|f| {
let v = f.split("_").collect::<Vec<&str>>();
(v[0], v[1])
}).collect::<Vec<(&str, &str)>>();
let old_attrs = method.attrs.clone();
let old_ident = &method.sig.ident;
let old_sig = method.sig.clone();
let mut args = Vec::new();
let mut receiver = false;
for input in &method.sig.inputs {
match input {
syn::FnArg::Typed(t) => {
match t.pat.as_ref() {
syn::Pat::Ident(p) => args.push(&p.ident),
_ => return syn::Error::new(
t.span(),
format!("I do not know what this is and \
it can't be overriden"))
.to_compile_error().into(),
}
},
syn::FnArg::Receiver(_) => {
receiver = true;
},
}
};
let if_branches = flags.into_iter().map(|(modifiers, flagstr)| {
let flagext = Ident::new(&format!("__override_flagext_{}_{}",
flagstr, old_ident),
Span::call_site());
if receiver {
if modifiers.find("i").is_some() {
quote! {
if CLAP_FLAGS.occurrences_of(#flagstr) == 0 {
self.#flagext (#(#args),*)
}
}
} else {
quote! {
if CLAP_FLAGS.occurrences_of(#flagstr) > 0 {
self.#flagext (#(#args),*)
}
}
}
} else {
if modifiers.find("i").is_some() {
quote! {
if CLAP_FLAGS.occurrences_of(#flagstr) == 0 {
Self::#flagext (#(#args),*)
}
}
} else {
quote! {
if CLAP_FLAGS.occurrences_of(#flagstr) > 0 {
Self::#flagext (#(#args),*)
}
}
}
}
}).collect::<Vec<proc_macro2::TokenStream>>();
method.sig.ident = Ident::new(&format!("__override_flagentry_{}",
old_ident),
Span::call_site());
let sigentry = &method.sig.ident;
attr_inline(&mut method.attrs);
let self_tok: proc_macro2::TokenStream = if receiver {
"self.".parse::<TokenStream>()
} else {
"Self::".parse::<TokenStream>()
}.unwrap().into();
let vis_tok: proc_macro2::TokenStream = match method.vis {
syn::Visibility::Public(_) => "pub".parse::<TokenStream>(),
_ => "".parse::<TokenStream>(),
}.unwrap().into();
additional_items.push(syn::parse2::<syn::ImplItem>(quote! {
#(#old_attrs)*
#vis_tok #old_sig {
#(#if_branches else )* {
#self_tok #sigentry (#(#args),*)
}
}
}).unwrap());
}
},
Const(constant) =>
match std::env::var(format!("__override_acceptflags_method_{}", self_type)) {
Err(_) =>
attr_add(&mut constant.attrs,
format!("__override_priority_{}_implconst_{}_{}",
priority,
self_type,
&constant.ident)),
Ok(_) => return syn::Error::new(
item.span(),
format!("Laying flags on const currently envokes undefined behavior"))
.to_compile_error().into(),
},
item => return syn::Error::new(
item.span(),
format!("I can't overload anything other than methods/consts \
in an impl block yet"))
.to_compile_error().into(),
}
}
input.items.append(&mut additional_items);
TokenStream::from(quote! {
#input
})
}
fn attr_add(attrs: &mut Vec<Attribute>, flag: String) {
let override_flag = Ident::new(&flag, Span::call_site());
attrs.push(
syn::parse2::<DeriveInput>(
quote! {
#[cfg(not(#override_flag))]
struct Dummy;
}
).unwrap().attrs.swap_remove(0));
}
fn attr_inline(attrs: &mut Vec<Attribute>) {
attrs.push(
syn::parse2::<DeriveInput>(
quote! {
#[inline(always)]
struct Dummy;
}
).unwrap().attrs.swap_remove(0));
}
#[proc_macro_attribute]
pub fn override_flag(attr: TokenStream, input: TokenStream) -> TokenStream {
let mut flag = None;
let mut priority = 0;
let mut _invert = false;
let attrstr = attr.to_string();
for arg in attrstr.split(",") {
let mut iter = arg.split("=");
let left = iter.next().expect("Malformed arguement").trim();
let right = iter.next().expect("Malformed arguement").trim();
match left {
"flag" => flag = Some(right),
"priority" => priority = right.parse().expect(&format!("Invalid arguement '{}'", right)),
"invert" => _invert = right.parse().expect(&format!("Invalid arguement '{}'", right)),
_ => panic!("Invalid arguement '{}'", right),
}
}
if flag.is_none() {
return quick_error("Missing mandatory arguement 'flag'".to_string());
}
if let Ok(item) = syn::parse::<ItemImpl>(input.clone()) {
flag_impl(item, priority, flag.unwrap().to_string())
} else if let Ok(item) = syn::parse::<ItemFn>(input) {
flag_function(item, priority, flag.unwrap().to_string())
} else {
quick_error(format!("I can't parse this yet"))
}
}
fn flag_function(mut item: ItemFn, priority: u32, flag: String) -> TokenStream {
attr_add(&mut item.attrs,
format!("__override_priority_{}_flag_{}_func_{}",
priority, flag, item.sig.ident));
attr_inline(&mut item.attrs);
item.sig.ident = Ident::new(&format!("__override_flagext_{}_{}",
flag, item.sig.ident),
Span::call_site());
return TokenStream::from(quote! {
#item
});
}
fn flag_impl(mut impl_block: ItemImpl, priority: u32, flag: String) -> TokenStream {
let self_type = match impl_block.self_ty.as_ref() {
Path(path) => path,
_ => return quick_error(format!("Could not get Path for impl (should never see this)")),
}.path.segments[0].ident.to_string();
for item in &mut impl_block.items {
match item {
Method(method) => {
attr_add(&mut method.attrs,
format!("__override_priority_{}_flag_{}_method_{}_{}",
priority, flag, self_type, method.sig.ident));
attr_inline(&mut method.attrs);
method.sig.ident = Ident::new(&format!("__override_flagext_{}_{}",
flag, method.sig.ident),
Span::call_site());
},
Const(_constant) => {
return quick_error(format!("flagging a constant currently envokes undefined \
behavior"));
},
item => return syn::Error::new(
item.span(),
format!("I can't overload anything other than methods/consts in an impl \
block yet"))
.to_compile_error().into(),
}
}
TokenStream::from(quote! {
#impl_block
})
}