Closed
Description
macro_rules! borked {
() => {
#[derive(Debug)] pub struct Good;
#[cfg_attr(not(test), derive(Debug))] pub struct Bad;
}
}
borked!();
fn main() {
println!("{:?}", Good);
println!("{:?}", Bad);
}
produces
foo.rs:12:22: 12:25 error: the trait `core::fmt::Show` is not implemented for the type `Bad` [E0277]
foo.rs:12 println!("{:?}", Bad);
^~~
I think what happens is the following:
- The first cfg stripping pass runs. At this point the macro body is just a token tree, and there's no way for cfg stripping to work out what
# [ cfg_attr ( not ( test ) ...
means. - Syntax expansion runs. The macro emits two items. One has a
derive
attribute, which matches aDecorator
in the syntax environment, so that extension also runs. There's no extension namedcfg_attr
so that attribute has no effect on expansion. - The post-expansion cfg stripping pass runs. The second attribute turns into
#[derive(Debug)]
but not in time for syntax expansion to notice it.
Previously, cfg_attr
was a syntax extension, which had numerous drawbacks, but handled this case correctly.
The somewhat obvious fix is to re-run cfg_attr
processing on the AST subtree produced by every macro expansion, but I'd like to think about this more. I'd also like to know whether it's something people often run into in practice.
Was mentioned on #19372. cc @sfackler, @SergioBenitez