Skip to main content

sassi_macros/
lib.rs

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