plugin_runtime_codegen/
lib.rs

1// plugin-system
2// Copyright (C) SOFe
3//
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8//     http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16extern crate proc_macro;
17
18use darling::FromMeta;
19use proc_macro2::{Ident, Span, TokenStream};
20use quote::quote;
21use syn::spanned::Spanned;
22use syn::{parse_macro_input, AttributeArgs, Error, FnArg, ItemFn, Pat, Path, Type};
23
24#[derive(darling::FromMeta)]
25struct PluginManifestInput {
26    #[darling(rename = "for")]
27    core_name: String,
28}
29
30#[proc_macro_attribute]
31pub fn declare_plugin(
32    meta: proc_macro::TokenStream,
33    func: proc_macro::TokenStream,
34) -> proc_macro::TokenStream {
35    let meta = parse_macro_input!(meta as AttributeArgs);
36    let func = parse_macro_input!(func as ItemFn);
37    let input = match PluginManifestInput::from_list(&meta) {
38        Ok(input) => input,
39        Err(err) => {
40            return err.write_errors().into();
41        }
42    };
43
44    let output = match plugin_impl(input, func) {
45        Ok(ts) => ts,
46        Err(err) => err.to_compile_error(),
47    };
48    output.into()
49}
50
51fn plugin_impl(input: PluginManifestInput, func: ItemFn) -> syn::Result<TokenStream> {
52    let _core_name = &input.core_name; // TODO check correctness of core
53    let func_name = &func.ident;
54    let maps_args = func
55        .decl
56        .inputs
57        .iter()
58        .map(|arg| {
59            Ok(match arg_to_dep(&arg)? {
60                Some(ident_name) => {
61                    quote!(require_plugin!(deps, #ident_name))
62                }
63                None => quote!(&mut map),
64            })
65        })
66        .collect::<syn::Result<Vec<_>>>()?;
67
68    let output = quote! {
69        pub struct PluginManifestImpl;
70
71        impl ::plugin_runtime::PluginManifest for PluginManifestImpl {
72            fn init(deps: &mut ::plugin_runtime::PluginList) -> ::plugin_runtime::FeatureMap {
73                let mut map = FeatureMap::default();
74                #func_name(#(#maps_args),*);
75                map
76            }
77        }
78
79        #func
80    };
81    Ok(output.into())
82}
83
84#[allow(unused)]
85fn test_path(path: &Path, expects: &[&str]) -> bool {
86    let mut iter = expects.iter();
87    for segment in &path.segments {
88        dbg!(&segment.ident);
89        if let Some(expect) = iter.next() {
90            if expect != &segment.ident.to_string() {
91                return false;
92            }
93        } else {
94            return false;
95        }
96    }
97    return true;
98}
99
100fn is_feature_map(_path: &Path) -> bool {
101    // test_path(path, &["plugin_runtime", "FeatureMap"])
102    true
103}
104
105fn is_option_feature_map(_path: &Path) -> bool {
106    false
107    //    if !test_path(path, &["std", "option", "Option"]) {
108    //        return false;
109    //    }
110    //    let types = match &path.segments[2].arguments {
111    //        PathArguments::AngleBracketed(types) => types,
112    //        _ => { return false; },
113    //    };
114    //    if types.args.len() != 1 {
115    //        return false;
116    //    }
117    //    let arg = &types.args[0];
118    //    let ty = match &arg {
119    //        GenericArgument::Type(ty) => ty,
120    //        _ => { return false; },
121    //    };
122    //    let path = match ty {
123    //        Type::Path(path) => &path.path,
124    //        _ => { return false; },
125    //    };
126    //    is_feature_map(path)
127}
128
129fn arg_to_dep(arg: &FnArg) -> syn::Result<Option<Ident>> {
130    match arg {
131        FnArg::Captured(arg_captured) => {
132            let ty = &arg_captured.ty;
133            let pat = &arg_captured.pat;
134
135            let (optional, mutable) = match ty {
136                Type::Reference(reference) => {
137                    if reference.lifetime.is_some() {
138                        return Err(Error::new(
139                            ty.span(),
140                            "Expected &FeatureMap or &mut FeatureMap, found lifetime",
141                        ));
142                    }
143                    let optional = match reference.elem.as_ref() {
144                        Type::Path(path) => {
145                            if is_feature_map(&path.path) {
146                                false
147                            } else if is_option_feature_map(&path.path) {
148                                true
149                            } else {
150                                return Err(Error::new(
151                                    ty.span(),
152                                    "Expected &[mut] FeatureMap, got unexpected type",
153                                ));
154                            }
155                        }
156                        _ => {
157                            return Err(Error::new(
158                                ty.span(),
159                                "Expected &[mut] FeatureMap, got complex type",
160                            ));
161                        }
162                    };
163                    let mutable = reference.mutability.is_some();
164                    (optional, mutable)
165                }
166                _ => {
167                    return Err(Error::new(
168                        ty.span(),
169                        "Expected &FeatureMap or &mut FeatureMap, got non-reference",
170                    ))
171                }
172            };
173
174            let ident = match pat {
175                Pat::Ident(ident) => &ident.ident,
176                _ => {
177                    return Err(Error::new(pat.span(), "Expected a single argument name \"this\" or indicating dependency package name"))
178                }
179            };
180            let ident_name = ident.to_string();
181
182            if optional {
183                return Err(Error::new(
184                    ty.span(),
185                    "Option<FeatureMap> is not implemented yet",
186                ));
187            }
188            let arg = if "this" == &ident_name {
189                if !mutable {
190                    return Err(Error::new(
191                        ty.span(),
192                        "\"this\" argument should be \"&mut FeatureMap\"",
193                    ));
194                }
195                None
196            } else {
197                if mutable {
198                    return Err(Error::new(
199                        ty.span(),
200                        "All arguments except \"this\" should be \"&FeatureMap\"",
201                    ));
202                }
203                let ident_name = if ident_name.starts_with('_') {
204                    &ident_name[1..]
205                } else {
206                    &ident_name[..]
207                };
208                let ident_name = Ident::new(ident_name, Span::call_site());
209                Some(ident_name)
210            };
211            Ok(arg)
212        }
213        _ => Err(Error::new(arg.span(), "unexpected argument type")),
214    }
215}