Skip to content

fix(derive-encode): emit friendly compile errors instead of panicking#307

Open
lohitkolluri wants to merge 1 commit into
prometheus:masterfrom
lohitkolluri:fix/269-friendly-compile-errors
Open

fix(derive-encode): emit friendly compile errors instead of panicking#307
lohitkolluri wants to merge 1 commit into
prometheus:masterfrom
lohitkolluri:fix/269-friendly-compile-errors

Conversation

@lohitkolluri

Copy link
Copy Markdown

Summary

Replace panic! calls in the EncodeLabelSet and EncodeLabelValue proc-macro derives with compile_error! so unsupported shapes surface a direct diagnostic that points to the #[derive(...)] line the user wrote, instead of the current error: proc-macro derive panicked message that points into the proc-macro internals.

Full changes

  • All 7 panic! sites in derive-encode/src/lib.rs now emit compile_error! tokens.
  • Unknown #[prometheus(...)] attributes (which previously panicked inside a closure) are validated in a pre-pass so the derive can return a compile_error! token stream before generating the impl.
  • New trybuild compile_fail test in derive-encode/tests/build/friendly-compilation-error-msg.rs covers unnamed struct, unit struct, enum, union, and unknown-attribute paths. The expected .stderr is committed alongside it.

Testing

  • cargo test -p prometheus-client-derive-encode --test lib passes deterministically (with and without TRYBUILD=overwrite).
  • cargo clippy -p prometheus-client-derive-encode --all-targets -- -D warnings clean.
  • cargo fmt --check -p prometheus-client-derive-encode clean.

Notes

For valid inputs the derive behavior is unchanged. The new diagnostics are user-facing; no downstream tooling currently parses these derive errors, so this is not a breaking change.

Issues

Closes #269

Developed with Claude Code assistance.

The proc-macro derives panicked for unsupported shapes (unnamed/unit
struct, enum, union, unknown `#[prometheus(...)] attribute) and the
bad-attribute case was inside a closure, which produced a confusing
`error: proc-macro derive panicked` diagnostic that pointed users at
the proc-macro internals rather than the `#[derive(...)] line they
needed to fix.

Replace all `panic!` calls with `compile_error!` so users see a
direct diagnostic that points to the offending `#[derive(...)] line.
For the unknown-attribute case, validate attributes in a pre-pass so
the derive can return a `compile_error!` token stream before
generating the impl.

Add a trybuild `compile_fail` test that exercises each error path
and pins the expected diagnostics.

Signed-off-by: Lohit Kolluri <lohitkolluri@gmail.com>
@lohitkolluri lohitkolluri force-pushed the fix/269-friendly-compile-errors branch from 756f7a2 to 9fcbe1b Compare July 1, 2026 20:46

@krisztianfekete krisztianfekete left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey, thanks for the PR, left a few comments!

Comment thread derive-encode/src/lib.rs
let msg = format!(
"Provided attribute '{other}', but only 'flatten' is supported"
);
return quote! { compile_error!(#msg); }.into();

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could, instead of hand crafting the compile_error!, you could use syn::Error::new_spanned(&other, msg).to_compile_error(). It generates the same kind of error, but the little ^^^^ arrow points right at the unknown in [prometheus(unknown)] instead of #[derive(...)]

We could do these for the other sites too, syn::Error::new_spanned(&ast, "...") for the enum/union/unnamed-struct cases points at the offending type.

Comment thread derive-encode/src/lib.rs
@@ -27,13 +54,7 @@ pub fn derive_encode_label_set(input: TokenStream) -> TokenStream {
.iter()
.find(|a| a.path().is_ident("prometheus"))
.map(|a| a.parse_args::<syn::Ident>().unwrap().to_string());

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This .unwrap() is only safe because the pre-pass already filtered out unparseable attrs. This is easy to break later, and it means we parse every #[prometheus(...)] twice. Having the closure return a Result and .collect()-ing it would drop the pre-pass, the double parse, and the unwrap all at once.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Compilation error messages could be better

2 participants