aspect_weave/
lib.rs

1//! # An Aspect Toolkit for Rust
2//!
3//! Aspect-RS is a project aiming to provide common ground for the main Aspect-Oriented use cases in Rust. By leveraging the trait system, declarative and procedural macros, Aspect-RS provides blocks that let you wrap methods with your custom logic.
4//!
5//! The project has been extracted from the [Metered project](https://github.com/magnet/metered-rs), which uses the technique to build metrics that can work on expressions or methods, whether they're `async` or not. The technique seemed general enough to be in its own crate and see if it is of any interest to other parties.
6//!
7//! Aspect-RS provides "pointcut" traits when entering or exiting an expression (`OnEnter` and `OnResult`), experimental `Update` and `UpdateRef` traits that can use parameter shadowing to intercept and update method parameters, and weaving constructs useful when building procedural macros. Please look at the [Metered project](https://github.com/magnet/metered-rs) to see Aspect-RS in action.
8//!
9//! This crate provides method weaving support through methods re-usable in procedural macros.
10
11#![deny(missing_docs)]
12#![deny(warnings)]
13// The `quote!` macro requires deep recursion.
14#![recursion_limit = "512"]
15
16extern crate proc_macro;
17
18use proc_macro::TokenStream;
19use std::rc::Rc;
20use syn::parse::Parse;
21use syn::Result;
22use synattra::ParseAttributes;
23
24/// A trait to "Weave" an `impl` block, that is update each annotated method with your custom logic
25///
26/// This trait extends Synattra's `ParseAttributes` that parses custom, non-macro attributes that are attached to the `impl` block or methods.
27pub trait Weave: ParseAttributes {
28    /// The parameters of the macro attribute triggering the weaving, i.e the attributes passed by the compiler to your custom procedural macro.
29    type MacroAttributes: Parse;
30
31    /// Parse the main macro attributes.
32    ///
33    /// The default implementation should work out-of-the-box.
34    fn parse_macro_attributes(attrs: TokenStream) -> syn::Result<Self::MacroAttributes> {
35        Ok(syn::parse(attrs)?)
36    }
37
38    /// A callback that lets you alter the blocks of intercepted methods.
39    fn update_fn_block(
40        fn_def: &syn::ImplItemMethod,
41        main_attr: &Self::MacroAttributes,
42        fn_attr: &[Rc<<Self as ParseAttributes>::Type>],
43    ) -> Result<syn::Block>;
44}
45
46use indexmap::IndexMap;
47/// An `impl` block after it's been woven.
48pub struct WovenImplBlock<M, F> {
49    /// The woven `impl` block, in which individual function blocks have been updated and intercepted attributes removed.
50    pub woven_block: syn::ItemImpl,
51    /// The macro attributes
52    pub main_attributes: M,
53    /// The woven functions, along with their intercepted attributes
54    pub woven_fns: IndexMap<syn::Ident, Vec<Rc<F>>>,
55}
56
57/// Weave an `impl` block
58///
59/// This method is meant to be called from a custom procedural macro.
60pub fn weave_impl_block<W: Weave>(
61    attrs: TokenStream,
62    item: TokenStream,
63) -> Result<WovenImplBlock<W::MacroAttributes, <W as ParseAttributes>::Type>> {
64    let main_attributes = W::parse_macro_attributes(attrs)?;
65
66    let mut parsed_input: syn::ItemImpl = syn::parse(item)?;
67    let mut attrs = &mut parsed_input.attrs;
68    let main_extra_attributes: Vec<Rc<<W as ParseAttributes>::Type>> =
69        process_custom_attributes::<W, _, _>(&mut attrs, Rc::new)?;
70
71    let mut woven = indexmap::map::IndexMap::new();
72
73    for item in parsed_input.items.iter_mut() {
74        if let syn::ImplItem::Method(item_fn) = item {
75            let mut attrs = &mut item_fn.attrs;
76
77            let method_attrs = process_custom_attributes::<W, _, _>(&mut attrs, Rc::new)?;
78
79            if method_attrs.is_empty() {
80                continue;
81            }
82
83            let mut fn_attributes: Vec<Rc<<W as ParseAttributes>::Type>> =
84                main_extra_attributes.clone();
85            fn_attributes.extend(method_attrs);
86
87            item_fn.block = W::update_fn_block(item_fn, &main_attributes, &fn_attributes)?;
88
89            woven.insert(item_fn.sig.ident.clone(), fn_attributes);
90        }
91    }
92
93    Ok(WovenImplBlock {
94        woven_block: parsed_input,
95        main_attributes: main_attributes,
96        woven_fns: woven,
97    })
98}
99
100fn process_custom_attributes<W: ParseAttributes, R, F: Fn(W::Type) -> R>(
101    attrs: &mut Vec<syn::Attribute>,
102    f: F,
103) -> Result<Vec<R>> {
104    let (ours, theirs): (Vec<syn::Attribute>, Vec<syn::Attribute>) = attrs
105        .clone()
106        .into_iter()
107        .partition(|attr| attr.path.is_ident(W::fn_attr_name()));
108
109    *attrs = theirs;
110
111    let mut fn_attributes: Vec<R> = Vec::new();
112    for attr in ours.into_iter() {
113        let p = W::parse_attributes(attr.tokens)?;
114        fn_attributes.push(f(p));
115    }
116
117    Ok(fn_attributes)
118}