Unused items don't raise a warning when emitted from an imported macro

Given this sample:

I'm (fairly obviously) getting warnings about the TAG static being unused. However, if I move the macro to a separate crate (with #[macro_export]), the warning is gone. Can I get the warning back? #[warn(unused)] doesn't seem to do anything.

This seems somewhat similar to No unused warning on proc macro generated struct which ended without resolution.

Why am I even bothering with this?

I have a bunch of macros in my public API and whenever the user uses one of them, it's basically certain they'll want to use one or more of the others, so I'd like to have a gentle reminder if the user just calls the first one.

What I'm really trying to do is to emit a bunch of #[no_mangle] pub fns for each trait a user-defined type implements, but having an impl Trait for Type + yeah_this_implements_trait!(Type) is the best I could come up with. For example, I learned that putting a #[no_mangle] pub fn in a const FOO: () { } always emits the functions, even if the constant isn't ever explicitly used.

The dead_code lint only works within a crate, not for public items, so you need a different mechanism for this. Maybe you can use a panic or compile_error with a message telling the user they forgot to implement something, reminding them of the existence of yeah_this_implements_trait?

The dead_code lint only works within a crate, not for public items, so you need a different mechanism for this.

The public item from another crate is the macro, which expands to a private static. "Something" carries over from the macro, but the actual item itself is clearly defined in the local crate. Manually expanding the macro gives the expected result (unused warnings).

Maybe you can use a panic or compile_error with a message telling the user they forgot to implement something, reminding them of the existence of yeah_this_implements_trait ?

I wish :slight_smile: I just have no idea what else I have at my disposal. I can't really kill the build in the first macro because the user may (and probably will) invoke one of the other macros later. And I can't really have just one macro, because I have like six traits that can be implemented in addition to the top-level one. Hmm, okay, maybe I could (pass a list of implemented traits and pick them off one by one in the macro rules), but it wouldn't be pretty I think.

(in reality, it's more like yeah_this_implements_traitX, for X=0...n, and any non-empty combination is valid)

1 Like

Sorry, I misread your statement, or rather didn't correctly connect it to the playground illustrating your setup. I didn't know that the dead_code lint treats expanded macro-code differently depending on whether the macro is foreign or not.

No worries :slight_smile: I came up with something slightly closer to a solution though from a different perspective. As the user won't probably forget to implement the trait they want (it wraps the business logic they care about and implementing one or more of these traits is the entire point of using my crate), the real risk is forgetting to invoke the yeah_this_implements_foo macro: nothing really guards against this.

So, my solution (of sorts) is:

  1. Add an undocumented unsafe FooIsExported marker trait
  2. Declare Foo (the trait the user wants) as pub trait Foo: FooIsExported
  3. unsafe impl FooIsExported for $ty {} in the yeah_this_implements_foo macro

(repeat for all the optional traits)

So now any type implementing Foo needs exactly one yeah_this_implements_foo invocation to compile. With a #[diagnostic::on_unimplemented] on FooIsExported this actually provides decent developer experience.

The original problem of warning when none of the optional traits is implemented remains, but is a much smaller footgun now.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.