From 87a19fd923817212ed0249afa13598eacfe5597f Mon Sep 17 00:00:00 2001 From: slhx agent Date: Mon, 25 May 2026 21:26:48 +0200 Subject: [PATCH] fix(derive): require generated handler symbols Make #[slhx::handler] fail when slhx.syms is missing instead of silently skipping generated handle checks. Add compile-fail coverage with an actionable build.rs hint. req: build/003 req: test/003 --- slhx-derive/src/lib.rs | 81 ++++++++++++++++++------------- slhx-derive/tests/compile_fail.rs | 51 +++++++++++++++++++ 2 files changed, 99 insertions(+), 33 deletions(-) diff --git a/slhx-derive/src/lib.rs b/slhx-derive/src/lib.rs index 73226e3..9c8959f 100644 --- a/slhx-derive/src/lib.rs +++ b/slhx-derive/src/lib.rs @@ -8,39 +8,54 @@ pub fn handler(_attr: TokenStream, item: TokenStream) -> TokenStream { let function = parse_macro_input!(item as ItemFn); let name = function.sig.ident.to_string(); - if let Some(syms_path) = syms_path() { - if syms_path.exists() { - if !syms_contains_handle(&syms_path, &name) { - let message = format!( - "unknown slhx handle `{name}`; add `data-slhx-handle=\"{name}\"` to a template or rename this handler" - ); - return quote!( - #function - compile_error!(#message); - ) - .into(); - } - if !has_form_param(&function) && !has_non_unit_return(&function) { - let message = format!( - "slhx handler `{name}` must accept a form/context parameter or return a value implementing IntoEffect" - ); - return quote!( - #function - compile_error!(#message); - ) - .into(); - } - if handle_requires_form(&syms_path, &name) && !has_form_param(&function) { - let message = format!( - "slhx handler `{name}` handles a generated form and must accept slhx::Form<_>" - ); - return quote!( - #function - compile_error!(#message); - ) - .into(); - } - } + let Some(syms_path) = syms_path() else { + let message = "#[slhx::handler] requires OUT_DIR; run inside a Cargo crate with slhx_build::app() in build.rs"; + return quote!( + #function + compile_error!(#message); + ) + .into(); + }; + if !syms_path.exists() { + let message = format!( + "#[slhx::handler] could not find {}; add slhx_build::app().run()? to build.rs or check template generation", + syms_path.display() + ); + return quote!( + #function + compile_error!(#message); + ) + .into(); + } + if !syms_contains_handle(&syms_path, &name) { + let message = format!( + "unknown slhx handle `{name}`; add `data-slhx-handle=\"{name}\"` to a template or rename this handler" + ); + return quote!( + #function + compile_error!(#message); + ) + .into(); + } + if !has_form_param(&function) && !has_non_unit_return(&function) { + let message = format!( + "slhx handler `{name}` must accept a form/context parameter or return a value implementing IntoEffect" + ); + return quote!( + #function + compile_error!(#message); + ) + .into(); + } + if handle_requires_form(&syms_path, &name) && !has_form_param(&function) { + let message = format!( + "slhx handler `{name}` handles a generated form and must accept slhx::Form<_>" + ); + return quote!( + #function + compile_error!(#message); + ) + .into(); } quote!(#function).into() diff --git a/slhx-derive/tests/compile_fail.rs b/slhx-derive/tests/compile_fail.rs index 8cad1b7..d37dd85 100644 --- a/slhx-derive/tests/compile_fail.rs +++ b/slhx-derive/tests/compile_fail.rs @@ -60,6 +60,57 @@ fn known() {} ); } +#[test] +fn handler_macro_reports_missing_syms() { + // req: build/003 req: test/003 + let fixture = Fixture::new("slhx-derive-handler-syms-fail"); + fixture.write( + "Cargo.toml", + &format!( + r#"[package] +name = "slhx-derive-handler-syms-fail" +version = "0.0.0" +edition = "2021" + +[lib] +path = "src/lib.rs" + +[dependencies] +slhx = {{ path = {:?} }} +"#, + repo_path("slhx") + ), + ); + fixture.write( + "build.rs", + r#"fn main() { + println!("cargo:rerun-if-changed=build.rs"); +} +"#, + ); + fixture.write( + "src/lib.rs", + r#"#[slhx::handler] +fn create() -> impl slhx::IntoEffect { + slhx::EffectBatch::default() +} +"#, + ); + + let output = check_fixture(&fixture); + + assert!(!output.status.success(), "fixture unexpectedly compiled"); + let stderr = String::from_utf8_lossy(&output.stderr); + assert!( + stderr.contains("#[slhx::handler] could not find"), + "missing handler syms diagnostic in stderr:\n{stderr}" + ); + assert!( + stderr.contains("add slhx_build::app().run()? to build.rs"), + "missing build.rs hint in stderr:\n{stderr}" + ); +} + #[test] fn form_handle_requires_form_parameter() { // req: form/004 req: form/006 req: test/003