feat(sync): add typed presence projection

req: sync/001

req: sync/005
This commit is contained in:
slhx agent
2026-07-13 23:03:46 +02:00
parent 87296632bc
commit 88598f181f
9 changed files with 327 additions and 25 deletions
+12
View File
@@ -0,0 +1,12 @@
[package]
name = "hemx-sync-macros"
version.workspace = true
edition.workspace = true
[lib]
proc-macro = true
[dependencies]
proc-macro2 = "1"
quote = "1"
syn = { version = "2", features = ["full"] }
+65
View File
@@ -0,0 +1,65 @@
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, Error, FnArg, ItemFn, Pat, ReturnType};
#[proc_macro_attribute]
pub fn presence(attributes: TokenStream, item: TokenStream) -> TokenStream {
if !attributes.is_empty() {
return Error::new(
proc_macro2::Span::call_site(),
"#[hemx_sync::presence] does not accept arguments",
)
.to_compile_error()
.into();
}
let mut function = parse_macro_input!(item as ItemFn);
if function.sig.asyncness.is_some() {
return Error::new_spanned(
function.sig.asyncness,
"presence projections must be synchronous",
)
.to_compile_error()
.into();
}
if matches!(function.sig.output, ReturnType::Default) {
return Error::new_spanned(
&function.sig,
"presence projections must return impl IntoEffect",
)
.to_compile_error()
.into();
}
let argument = match function.sig.inputs.first() {
Some(FnArg::Typed(argument)) if function.sig.inputs.len() == 1 => argument,
_ => {
return Error::new_spanned(
&function.sig.inputs,
"presence projections require exactly one typed presence argument",
)
.to_compile_error()
.into();
}
};
let argument_name = match argument.pat.as_ref() {
Pat::Ident(argument) => argument.ident.clone(),
pattern => {
return Error::new_spanned(
pattern,
"presence projection argument must be a simple identifier",
)
.to_compile_error()
.into();
}
};
let body = function.block;
function.sig.output = syn::parse_quote!(-> impl ::hemx_sync::PresenceUpdate);
function.block = Box::new(syn::parse_quote!({
let __hemx_sync_channel =
::hemx_sync::PresenceScope::presence_channel(&#argument_name);
let __hemx_sync_effect = (|| #body)();
::hemx_sync::PresenceProjection::new(__hemx_sync_channel, __hemx_sync_effect)
}));
quote!(#function).into()
}