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