verde_derive/
lib.rs

1use proc_macro2::TokenStream;
2use quote::{quote, quote_spanned, ToTokens};
3use syn::{parse_macro_input, spanned::Spanned, DeriveInput, ItemFn, ItemStruct};
4
5mod database;
6mod pushable;
7mod query;
8mod tracked;
9
10type Result<T> = std::result::Result<T, Error>;
11struct Error {
12	span: proc_macro2::Span,
13	message: String,
14}
15
16impl Error {
17	fn new(span: proc_macro2::Span, message: impl ToString) -> Self {
18		Self {
19			span,
20			message: message.to_string(),
21		}
22	}
23}
24
25impl ToTokens for Error {
26	fn to_tokens(&self, tokens: &mut TokenStream) {
27		let message = &self.message;
28		tokens.extend(quote_spanned! { self.span => compile_error!(#message); });
29	}
30}
31
32/// Allow a type to be tracked by the database.
33///
34/// This type must also implement `Eq`, and by extension, `PartialEq`.
35///
36/// A single field must be marked with `#[id]`, which will uniquely identify an instance of this type output
37/// by a query. Different query functions may output equal IDs, but they will not interfere with each other. The ID must
38/// implement `Eq`, `Hash`, and `Clone`.
39#[proc_macro_derive(Tracked, attributes(id))]
40pub fn tracked(item: proc_macro::TokenStream) -> proc_macro::TokenStream {
41	let input = parse_macro_input!(item as DeriveInput);
42	match tracked::tracked(input) {
43		Ok(x) => x,
44		Err(e) => quote!(#e),
45	}
46	.into()
47}
48
49/// Allow a type to be interned in the database.
50///
51/// This type must implement `Clone`, `Eq`, and `Hash`.
52#[proc_macro_derive(Interned)]
53pub fn interned(item: proc_macro::TokenStream) -> proc_macro::TokenStream {
54	let input = parse_macro_input!(item as DeriveInput);
55	let ty = input.ident;
56	let (i, t, w) = input.generics.split_for_impl();
57	(quote! {
58		impl #i ::verde::Interned for #ty #t #w {}
59
60		impl #i ::verde::internal::Storable for #ty #t #w {
61			type Storage = ::verde::internal::storage::InternedStorage<Self>;
62
63			fn tracked_storage(store: &Self::Storage) -> Option<&dyn ::verde::internal::storage::ErasedTrackedStorage> {
64				None
65			}
66
67			fn query_storage(store: &Self::Storage) -> Option<&dyn ::verde::internal::storage::ErasedQueryStorage> {
68				None
69			}
70
71			fn pushable_storage(store: &Self::Storage) -> Option<&dyn ::verde::internal::storage::ErasedPushableStorage> {
72				None
73			}
74
75			fn interned_storage(store: &Self::Storage) -> Option<&dyn ::verde::internal::storage::ErasedInternedStorage> {
76				Some(store)
77			}
78		}
79	})
80	.into()
81}
82
83/// Allow a type to be pushed into the database from queries.
84#[proc_macro_derive(Pushable)]
85pub fn pushable(item: proc_macro::TokenStream) -> proc_macro::TokenStream {
86	let input = parse_macro_input!(item as DeriveInput);
87	match pushable::pushable(input) {
88		Ok(x) => x,
89		Err(e) => quote!(#e),
90	}
91	.into()
92}
93
94/// Generate a query.
95///
96/// The first argument of the function must be of type `&verde::Ctx`, and they must return a `Tracked` struct.
97///
98/// Arguments can be marked with `#[ignore]` to not be used to identify an execution of the query.
99#[proc_macro_attribute]
100pub fn query(attr: proc_macro::TokenStream, item: proc_macro::TokenStream) -> proc_macro::TokenStream {
101	let attr = TokenStream::from(attr);
102	if !attr.is_empty() {
103		return quote_spanned! { attr.span() => compile_error!("`query` does not take any arguments"); }.into();
104	}
105
106	let input = parse_macro_input!(item as ItemFn);
107	match query::query(input) {
108		Ok(x) => x,
109		Err(e) => quote!(#e),
110	}
111	.into()
112}
113
114/// Generate a storage struct.
115///
116/// Storage structs must be a tuple struct that contain the `Tracked`, `Pushable`, and `Interned` types, as well the
117/// queries that to be stored into the database.
118#[proc_macro_attribute]
119pub fn storage(attr: proc_macro::TokenStream, item: proc_macro::TokenStream) -> proc_macro::TokenStream {
120	let attr = TokenStream::from(attr);
121	if !attr.is_empty() {
122		return quote_spanned! { attr.span() => compile_error!("`storage` does not take any arguments"); }.into();
123	}
124
125	let input = parse_macro_input!(item as ItemStruct);
126	match database::storage(input) {
127		Ok(x) => x,
128		Err(e) => quote!(#e),
129	}
130	.into()
131}
132
133/// Generate a database.
134///
135/// Databases must be a tuple struct that contain the `Storage` types.
136#[proc_macro_attribute]
137pub fn db(attr: proc_macro::TokenStream, item: proc_macro::TokenStream) -> proc_macro::TokenStream {
138	let attr = TokenStream::from(attr);
139	if !attr.is_empty() {
140		return quote_spanned! { attr.span() => compile_error!("`database` does not take any arguments"); }.into();
141	}
142
143	let input = parse_macro_input!(item as ItemStruct);
144	match database::database(input) {
145		Ok(x) => x,
146		Err(e) => quote!(#e),
147	}
148	.into()
149}