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))),
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}