io_plugin_macros/
lib.rs

1#![feature(extend_one, let_chains, anonymous_lifetime_in_impl_trait, extract_if)]
2
3use std::collections::HashMap;
4
5use proc_macro::TokenStream;
6use quote::{format_ident, quote, quote_spanned, ToTokens};
7use syn::{spanned::Spanned, ItemEnum, Path, TraitItemFn, parse_macro_input};
8
9use crate::{feature_gates::FeatureGates, util::generate_gate};
10
11mod enums;
12mod feature_gates;
13mod generics;
14mod handle;
15mod plugin_interface;
16mod util;
17
18/// Generate a plugin-interface, based on an enum definition for its' operations
19/// From the plugin's perspective - input types are all fields except the last one, and the output type is the last one
20/// (of course - you can use tuples to output multiple values).
21///
22/// The provided enum's variant must contain only owned data (no &'a) - otherwise, deserialiastaion will cause a compile-time error.
23/// The variants must be [`serde::Serialize`] + [`serde::Deserialize`].
24///
25/// Note that the enum this attribute applies to won't exist.  
26/// Instead, there will be a `message` enum, `response` enum, plugin `trait`, plugin `handle` (a struct) - postfixed with the highlighted words.
27#[proc_macro_attribute]
28pub fn io_plugin(attribute_data: TokenStream, input: TokenStream) -> TokenStream {
29    let gates = syn::parse::<FeatureGates>(attribute_data).ok();
30    let gates = gates.map(|g| g.hashmap()).unwrap_or(HashMap::new());
31    let mut input = parse_macro_input!(input as ItemEnum);
32
33    if let Some(lifetime) = input.generics.lifetimes().last() {
34        return quote_spanned!(lifetime.span()=>compile_error!("lifetimes are not supported in `io_plugin`");).into();
35    }
36
37    input.ident = format_ident!("{}", input.ident.to_string().trim_start_matches("_"));
38
39    let (message, response, response_impl) = enums::split_enum(&mut input);
40
41    for ty in input.generics.type_params_mut() {
42        ty.default = None;
43    }
44
45    #[allow(unused_variables)]
46    let handle = handle::generate_handle(
47        input.clone(),
48        message.clone(),
49        response.clone(),
50        generate_gate(gates.get("handle")),
51    );
52
53    let gate = gates.get("plugin_trait");
54    let (plugin_trait, main_loop_iteration) =
55        plugin_interface::generate_trait(input.clone(), message.clone(), response.clone(), gate);
56    let plugin_trait_gate = generate_gate(gate);
57
58    quote_spanned!(message.span()=>
59    #message
60
61    #response
62    #response_impl
63
64    #plugin_trait_gate
65    #plugin_trait
66
67    #plugin_trait_gate
68    #main_loop_iteration
69
70    #handle
71    )
72    .into()
73}
74
75/// Allows customising the documentation of the handle generated by [`io_plugin`]
76#[proc_macro_attribute]
77pub fn handle_doc(_attr: TokenStream, item: TokenStream) -> TokenStream {
78    item
79}
80
81/// Allows customising the documentation of the plugin trait generated by [`io_plugin`]
82#[proc_macro_attribute]
83pub fn plugin_trait_doc(_attr: TokenStream, item: TokenStream) -> TokenStream {
84    item
85}
86
87/// Attributes which only apply to the plugin message enum
88#[proc_macro_attribute]
89pub fn message_attributes(_attr: TokenStream, item: TokenStream) -> TokenStream {
90    item
91}
92
93/// Attributes which only apply to the plugin response enum
94#[proc_macro_attribute]
95pub fn response_attributes(_attr: TokenStream, item: TokenStream) -> TokenStream {
96    item
97}
98
99/// Provide a default implementation for a plugin method
100#[proc_macro_attribute]
101pub fn trait_method_default(attr: TokenStream, item: TokenStream) -> TokenStream {
102    let mut method = parse_macro_input!(item as TraitItemFn);
103    let signature = &method.sig;
104    let args = signature.inputs.iter().filter_map(|arg| {
105        if let syn::FnArg::Typed(arg) = arg {
106            Some(arg.pat.to_token_stream())
107        } else {
108            None
109        }
110    });
111    let implementation = parse_macro_input!(attr as Path);
112    method.semi_token = None;
113    quote!(#signature {
114        async move {
115            #implementation(self, #(#args),*).await
116        }
117    })
118    .into()
119}