Skip to main content

hax_rust_engine_macros/
lib.rs

1//! Helper crate providing procedural macros for the Rust engine of hax.
2//!
3//! Currently it provides the following.
4//!  - Macros for deriving groups of traits.
5//!    Most of the type from the AST have the same bounds, so that helps deduplicating a lot.
6//!    Also, the fact those derive groups are named is helpful: for instance for code generation
7//!    a simple `use derive_group_for_ast_base as derive_group_for_ast` can change what is to be
8//!    derived without any attribute manipulation.
9
10use proc_macro::TokenStream;
11use proc_macro2::{Ident, Span};
12use quote::quote;
13use syn::{Token, parse_macro_input, visit_mut::VisitMut};
14use utils::*;
15
16mod partial_application;
17mod replace;
18mod struct_fields;
19
20/// Adds a new field with a fresh name to an existing `struct` type definition.
21/// The new field contains error handling and span information to be used with a
22/// visitor. This macro will also derive implementations of
23/// `hax_rust_engine::ast::visitors::wrappers::VisitorWithErrors` and
24/// `hax_rust_engine::ast::HasSpan` for the struct.
25#[proc_macro_attribute]
26pub fn setup_error_handling_struct(_attr: TokenStream, item: TokenStream) -> TokenStream {
27    struct_fields::setup_error_handling_struct(_attr, item)
28}
29
30/// Adds a new field with a fresh name to an existing `struct` type definition.
31/// The new field contains span information to be used with a
32/// printer. This macro will also derive implementations of
33/// `hax_rust_engine::printer::pretty_ast::HasContextualSpan` for the struct.
34#[proc_macro_attribute]
35pub fn setup_printer_struct(_attr: TokenStream, item: TokenStream) -> TokenStream {
36    struct_fields::setup_printer_struct(_attr, item)
37}
38
39mod utils {
40    use super::*;
41
42    /// Get the name of this macro crate (`hax_rust_engine_macros`)
43    pub(crate) fn crate_name() -> Ident {
44        let krate = module_path!().split("::").next().unwrap();
45        Ident::new(krate, Span::call_site())
46    }
47
48    /// Prepends a `proc_macro2::TokenStream` to a `TokenStream`
49    pub(crate) fn prepend(item: TokenStream, prefix: proc_macro2::TokenStream) -> TokenStream {
50        let item: proc_macro2::TokenStream = item.into();
51        quote! {
52            #prefix
53            #item
54        }
55        .into()
56    }
57
58    /// Add a derive attribute to `item`
59    pub(crate) fn add_derive(item: TokenStream, payload: proc_macro2::TokenStream) -> TokenStream {
60        prepend(item, quote! {#[derive(#payload)]})
61    }
62
63    /// Find the name of the crate `hax-rust-engine`. This can be either the
64    /// keyword `crate` or the ident `hax_rust_engine`, depending on the context
65    /// in which the macros using this function are called.
66    pub(crate) fn rust_engine_krate_name() -> proc_macro2::TokenStream {
67        use proc_macro_crate::{FoundCrate, crate_name};
68        match crate_name("hax-rust-engine").unwrap() {
69            FoundCrate::Itself => quote!(crate),
70            FoundCrate::Name(name) => {
71                let ident = Ident::new(&name, Span::call_site());
72                quote!( #ident )
73            }
74        }
75    }
76}
77
78/// Derive the common derives for the hax engine AST.
79/// This is a equivalent to `derive_group_for_ast_serialization` and `derive_group_for_ast_base`.
80#[proc_macro_attribute]
81pub fn derive_group_for_ast(_attr: TokenStream, item: TokenStream) -> TokenStream {
82    let krate = crate_name();
83    prepend(
84        item,
85        quote! {
86            #[#krate::derive_group_for_ast_base]
87            #[#krate::derive_group_for_ast_serialization]
88        },
89    )
90}
91
92/// Derive the necessary (de)serialization related traits for nodes in the AST.
93#[proc_macro_attribute]
94pub fn derive_group_for_ast_serialization(_attr: TokenStream, item: TokenStream) -> TokenStream {
95    add_derive(
96        item,
97        quote! {::serde::Deserialize, ::serde::Serialize, ::schemars::JsonSchema},
98    )
99}
100
101/// Derive the basic necessary traits for nodes in the AST.
102#[proc_macro_attribute]
103pub fn derive_group_for_ast_base(_attr: TokenStream, item: TokenStream) -> TokenStream {
104    add_derive(
105        item,
106        quote! {Debug, Clone, Hash, Eq, PartialEq, PartialOrd, Ord, derive_generic_visitor::Drive, derive_generic_visitor::DriveMut},
107    )
108}
109
110#[proc_macro_attribute]
111/// Replaces all occurrences of an identifier within the attached item.
112///
113/// For example, `#[replace(Name => A, B, C)]` will replace `Name` by `A, B, C`
114/// in the item the proc-macro is applied on.
115///
116/// The special case `#[replace(Name => include(VisitableAstNodes))]` will
117/// expand to a list of visitable AST nodes. This is useful in practice, as this
118/// list is often repeated.
119pub fn replace(attr: TokenStream, item: TokenStream) -> TokenStream {
120    replace::replace(attr, item)
121}
122
123/// An attribute procedural macro that creates a new `macro_rules!` definition
124/// by partially applying an existing macro or function with a given token stream.
125///
126/// Usage:
127/// ```rust,ignore
128/// #[partial_apply(original_macro!, my_expression,)]
129/// macro_rules! new_proxy_macro {
130///     // This content is ignored and replaced by the proc macro.
131/// }
132/// ```
133#[proc_macro_attribute]
134pub fn partial_apply(attr: TokenStream, item: TokenStream) -> TokenStream {
135    partial_application::partial_apply(attr, item)
136}
137
138/// Prepend the body any associated function with the given attribute payload.
139/// ```rust,ignore
140/// #[prepend_associated_functions_with(println!("self is {self}");)]
141/// impl Foo {
142///   fn f(self) {}
143/// }
144/// ```
145///
146/// Expands to:
147/// ```rust,ignore
148/// impl Foo {
149///   fn f(self) {
150///     println!("self is {self}");
151///   }
152/// }
153/// ```
154#[proc_macro_attribute]
155pub fn prepend_associated_functions_with(attr: TokenStream, item: TokenStream) -> TokenStream {
156    struct Visitor {
157        prefix: syn::Expr,
158    }
159    impl VisitMut for Visitor {
160        fn visit_item_impl_mut(&mut self, impl_block: &mut syn::ItemImpl) {
161            for item in &mut impl_block.items {
162                let syn::ImplItem::Fn(impl_item_fn) = item else {
163                    continue;
164                };
165                impl_item_fn.block.stmts.insert(
166                    0,
167                    syn::Stmt::Expr(self.prefix.clone(), Some(Token![;](Span::mixed_site()))),
168                );
169            }
170        }
171    }
172    let mut item: syn::Item = parse_macro_input!(item);
173    let prefix = parse_macro_input!(attr);
174    Visitor { prefix }.visit_item_mut(&mut item);
175    quote! {#item}.into()
176}