extern crate proc_macro;
use darling::FromMeta;
use proc_macro2::{Ident, Span, TokenStream};
use quote::quote;
use syn::spanned::Spanned;
use syn::{parse_macro_input, AttributeArgs, Error, FnArg, ItemFn, Pat, Path, Type};
#[derive(darling::FromMeta)]
struct PluginManifestInput {
#[darling(rename = "for")]
core_name: String,
}
#[proc_macro_attribute]
pub fn declare_plugin(
meta: proc_macro::TokenStream,
func: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
let meta = parse_macro_input!(meta as AttributeArgs);
let func = parse_macro_input!(func as ItemFn);
let input = match PluginManifestInput::from_list(&meta) {
Ok(input) => input,
Err(err) => {
return err.write_errors().into();
}
};
let output = match plugin_impl(input, func) {
Ok(ts) => ts,
Err(err) => err.to_compile_error(),
};
output.into()
}
fn plugin_impl(input: PluginManifestInput, func: ItemFn) -> syn::Result<TokenStream> {
let _core_name = &input.core_name; let func_name = &func.ident;
let maps_args = func
.decl
.inputs
.iter()
.map(|arg| {
Ok(match arg_to_dep(&arg)? {
Some(ident_name) => {
quote!(require_plugin!(deps, #ident_name))
}
None => quote!(&mut map),
})
})
.collect::<syn::Result<Vec<_>>>()?;
let output = quote! {
pub struct PluginManifestImpl;
impl ::plugin_runtime::PluginManifest for PluginManifestImpl {
fn init(deps: &mut ::plugin_runtime::PluginList) -> ::plugin_runtime::FeatureMap {
let mut map = FeatureMap::default();
#func_name(#(#maps_args),*);
map
}
}
#func
};
Ok(output.into())
}
#[allow(unused)]
fn test_path(path: &Path, expects: &[&str]) -> bool {
let mut iter = expects.iter();
for segment in &path.segments {
dbg!(&segment.ident);
if let Some(expect) = iter.next() {
if expect != &segment.ident.to_string() {
return false;
}
} else {
return false;
}
}
return true;
}
fn is_feature_map(_path: &Path) -> bool {
true
}
fn is_option_feature_map(_path: &Path) -> bool {
false
}
fn arg_to_dep(arg: &FnArg) -> syn::Result<Option<Ident>> {
match arg {
FnArg::Captured(arg_captured) => {
let ty = &arg_captured.ty;
let pat = &arg_captured.pat;
let (optional, mutable) = match ty {
Type::Reference(reference) => {
if reference.lifetime.is_some() {
return Err(Error::new(
ty.span(),
"Expected &FeatureMap or &mut FeatureMap, found lifetime",
));
}
let optional = match reference.elem.as_ref() {
Type::Path(path) => {
if is_feature_map(&path.path) {
false
} else if is_option_feature_map(&path.path) {
true
} else {
return Err(Error::new(
ty.span(),
"Expected &[mut] FeatureMap, got unexpected type",
));
}
}
_ => {
return Err(Error::new(
ty.span(),
"Expected &[mut] FeatureMap, got complex type",
));
}
};
let mutable = reference.mutability.is_some();
(optional, mutable)
}
_ => {
return Err(Error::new(
ty.span(),
"Expected &FeatureMap or &mut FeatureMap, got non-reference",
))
}
};
let ident = match pat {
Pat::Ident(ident) => &ident.ident,
_ => {
return Err(Error::new(pat.span(), "Expected a single argument name \"this\" or indicating dependency package name"))
}
};
let ident_name = ident.to_string();
if optional {
return Err(Error::new(
ty.span(),
"Option<FeatureMap> is not implemented yet",
));
}
let arg = if "this" == &ident_name {
if !mutable {
return Err(Error::new(
ty.span(),
"\"this\" argument should be \"&mut FeatureMap\"",
));
}
None
} else {
if mutable {
return Err(Error::new(
ty.span(),
"All arguments except \"this\" should be \"&FeatureMap\"",
));
}
let ident_name = if ident_name.starts_with('_') {
&ident_name[1..]
} else {
&ident_name[..]
};
let ident_name = Ident::new(ident_name, Span::call_site());
Some(ident_name)
};
Ok(arg)
}
_ => Err(Error::new(arg.span(), "unexpected argument type")),
}
}