Skip to main content

plugctx_derive/
lib.rs

1//! `plugctx-derive` — 可选过程宏,减少 `Plugin` 样板(FR27)。
2//!
3//! 核心 crate `plugctx` **不**依赖本 crate;插件作者按需引入(crates.io / 工作区:`plugctx-derive` `0.1.3` 与 `plugctx` 锁步)。
4
5use proc_macro::TokenStream;
6use quote::quote;
7use syn::parse::Parse;
8use syn::punctuated::Punctuated;
9use syn::{
10    parse_macro_input, Attribute, Data, DeriveInput, Error as SynError, Path, Result as SynResult,
11    Token,
12};
13
14/// 从结构体上的 `#[plugin(depends(A, B))]` 生成 [`plugctx::Plugin`] 实现。
15///
16/// - `dependencies()`:返回声明类型的 `TypeId` 列表(无 `depends` 则为空)。
17/// - `build()`:委托到用户固有方法 `fn on_build(&self, ctx: &mut Context) -> Result<(), Error>`。
18///
19/// # 示例
20///
21/// ```ignore
22/// use plugctx_derive::Plugin;
23///
24/// #[derive(Plugin)]
25/// #[plugin(depends(Logger))]
26/// struct MyPlugin;
27///
28/// impl MyPlugin {
29///     fn on_build(&self, ctx: &mut plugctx::Context) -> Result<(), plugctx::Error> {
30///         let _ = ctx.get::<Logger>();
31///         Ok(())
32///     }
33/// }
34/// ```
35#[proc_macro_derive(Plugin, attributes(plugin))]
36pub fn derive_plugin(input: TokenStream) -> TokenStream {
37    let input = parse_macro_input!(input as DeriveInput);
38    match expand_plugin(input) {
39        Ok(tokens) => tokens.into(),
40        Err(err) => err.to_compile_error().into(),
41    }
42}
43
44fn expand_plugin(input: DeriveInput) -> SynResult<proc_macro2::TokenStream> {
45    match &input.data {
46        Data::Struct(_) => {}
47        Data::Enum(_) | Data::Union(_) => {
48            return Err(SynError::new_spanned(
49                &input.ident,
50                "#[derive(Plugin)] 仅支持结构体(struct)",
51            ));
52        }
53    }
54
55    let name = &input.ident;
56    let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
57    let depends = parse_depends_attrs(&input.attrs)?;
58
59    let depend_type_ids = depends.iter().map(|path| {
60        quote! {
61            ::std::any::TypeId::of::<#path>()
62        }
63    });
64
65    Ok(quote! {
66        impl #impl_generics ::plugctx::Plugin for #name #ty_generics #where_clause {
67            fn dependencies(&self) -> ::std::vec::Vec<::std::any::TypeId> {
68                ::std::vec![
69                    #(#depend_type_ids),*
70                ]
71            }
72
73            fn build(
74                &self,
75                ctx: &mut ::plugctx::Context,
76            ) -> ::std::result::Result<(), ::plugctx::Error> {
77                Self::on_build(self, ctx)
78            }
79        }
80    })
81}
82
83struct DependsList {
84    types: Punctuated<Path, Token![,]>,
85}
86
87impl Parse for DependsList {
88    fn parse(input: syn::parse::ParseStream) -> SynResult<Self> {
89        let content;
90        syn::parenthesized!(content in input);
91        let types = Punctuated::parse_terminated(&content)?;
92        Ok(Self { types })
93    }
94}
95
96fn parse_depends_attrs(attrs: &[Attribute]) -> SynResult<Vec<Path>> {
97    let mut deps = Vec::new();
98    for attr in attrs {
99        if !attr.path().is_ident("plugin") {
100            continue;
101        }
102        attr.parse_nested_meta(|meta| {
103            if meta.path.is_ident("depends") {
104                let list: DependsList = meta.input.parse()?;
105                deps.extend(list.types);
106                Ok(())
107            } else {
108                Err(meta.error("不支持的 #[plugin(...)] 键;仅支持 depends(...)"))
109            }
110        })?;
111    }
112    Ok(deps)
113}