Skip to content

Process cfg_attr during expansion #22250

Closed
@kmcallister

Description

@kmcallister
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 a Decorator in the syntax environment, so that extension also runs. There's no extension named cfg_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

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-macrosArea: All kinds of macros (custom derive, macro_rules!, proc macros, ..)A-syntaxextArea: Syntax extensions

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions