Skip to main content

sassi_macros/
lib.rs

1//! # sassi-macros
2//!
3//! Proc macros for sassi: `#[derive(Cacheable)]` and
4//! `#[sassi::trait_impl]`.
5//!
6//! Macros call into `sassi-codegen` for the actual `TokenStream`
7//! emission so the codegen logic stays in a regular library crate
8//! that downstream macro crates (e.g., `djogi-macros`) can also
9//! consume without running into proc-macro-cycle limitations.
10
11#![forbid(unsafe_code)]
12
13mod cacheable;
14mod trait_impl;
15
16use proc_macro::TokenStream;
17use proc_macro_crate::{FoundCrate, crate_name};
18use proc_macro2::{Span, TokenStream as TokenStream2};
19use quote::{format_ident, quote};
20
21/// Derive macro for `sassi::Cacheable`.
22///
23/// Generates:
24/// 1. A companion `{StructName}Fields` struct with one
25///    `sassi::Field<Self, FieldType>` per declared field.
26/// 2. `impl sassi::Cacheable for {StructName}` with:
27///    - `Id` = the type of the field literally named `id`.
28///    - `fields()` trait method wiring every accessor to its real
29///      extractor (so generic `T: Cacheable` callers can construct
30///      wired Fields without knowing the concrete type).
31/// 3. When `#[cacheable(watermark_field = "...")]` is present, an
32///    `impl sassi::DeltaSyncCacheable` whose `Watermark` is the named
33///    field's type and whose `watermark()` clones that field.
34///
35/// Requirements:
36/// - Input must be a struct with named fields.
37/// - One of the fields must be literally named `id`.
38/// - `id`'s type must implement `Hash + Eq + Clone + Ord + Send + Sync + 'static`.
39/// - `watermark_field`, when present, must name a field whose type
40///   implements `sassi::MonotonicWatermark`.
41#[proc_macro_derive(Cacheable, attributes(cacheable))]
42pub fn derive_cacheable(input: TokenStream) -> TokenStream {
43    cacheable::derive_cacheable(input)
44}
45
46/// Attribute macro for registering a trait implementation with
47/// `Sassi::all_impl::<dyn Trait>()`.
48///
49/// Apply it to a concrete trait impl:
50///
51/// ```ignore
52/// #[sassi::trait_impl]
53/// impl Nameable for User {
54///     fn name(&self) -> &str { &self.name }
55/// }
56/// ```
57#[proc_macro_attribute]
58pub fn trait_impl(args: TokenStream, input: TokenStream) -> TokenStream {
59    trait_impl::trait_impl(args, input)
60}
61
62fn sassi_path() -> Result<TokenStream2, syn::Error> {
63    let found = crate_name("sassi").map_err(|e| {
64        syn::Error::new(
65            Span::call_site(),
66            format!("sassi macro expansion could not resolve the `sassi` crate: {e}"),
67        )
68    })?;
69
70    Ok(match found {
71        FoundCrate::Itself => quote!(crate),
72        FoundCrate::Name(name) => {
73            let ident = format_ident!("{}", name);
74            quote!(::#ident)
75        }
76    })
77}