[go: up one dir, main page]

Attribute Macro cfg_eval

Source
#[cfg_eval]
Expand description

#[::core::prelude::v1::cfg_eval] in stable Rust.

  • Note: this macro, by default, only works on struct, enum, and union definitions (i.e., on #[derive] input).

    Enable features = ["items"] to get support for arbitary items.

§Example

use ::macro_rules_attribute::apply;

#[macro_use]
extern crate cfg_eval;

fn main()
{
    let output_without_cfg_eval = {
        #[apply(stringify!)]
        enum Foo {
            Bar,

            #[cfg(FALSE)]
            NonExisting,
        }
    };
    // This is usually not great.
    assert!(output_without_cfg_eval.contains("NonExisting"));

    let output_with_cfg_eval = {
        #[cfg_eval]
        #[apply(stringify!)]
        enum Foo {
            Bar,

            #[cfg(FALSE)]
            NonExisting,
        }
    };
    assert_eq!(output_with_cfg_eval, stringify! {
        enum Foo {
            Bar,
        }
    });
}

§How it works

The way this is achieved is by taking advantage of #[derive(SomeDerive)] having #[cfg_eval] semantics built in.

  • This means that if you have the luxury of hesitating between offering a derive macro, or an attribute macro, and need #[cfg_eval] semantics, you’d be better off using a derive than #[cfg_eval]!

  • but the reality is that certain macros do want to modify their input, which rules out implementing it as a #[derive].

§Illustration

With that knowledge, let’s see a step-by-step description of what the macro is doing.

Consider, for instance, the following snippet:

#[cfg_eval]
#[other attrs...]
#[cfg_attr(all(), for_instance)]
struct Foo {
    x: i32,
    #[cfg(any())]
    y: u8,
}
  • Remember: all() is cfg-speak for true, and any(), for false.

What #[cfg_eval] does, then, on this snippet, is emitting the following:

#[derive(RemoveExterminate)] // šŸ‘ˆ
#[exterminate]               // šŸ‘ˆ
#[other attrs...]
#[cfg_attr(all(), for_instance)]
struct Foo {
    x: i32,
    #[cfg(any())]
    y: u8,
}

With the added macros doing what their names indicate. If this is not clear enough, then feel free to read the following more detailed section:

Click to show

The #[derive(RemoveExterminate)] invocation leads to the following two snippets of code, as per the rules of #[derive()]s (i.e., that the annotated item be emitted independently of what the macro emits):

  • independently of what RemoveExterminate does, we have:

    #[exterminate] // šŸ‘ˆ
    #[other attrs...]
    #[cfg_attr(all(), for_instance)]
    struct Foo {
        x: i32,
        #[cfg(any())]
        y: u8,
    }

    which then, thus, calls #[exterminate].

    What #[exterminate] does, as hinted to the attentive reader by its otherwise totally unconspicuous name, is removing the annotated item:

    /* nothing here! */
  • independently of the previous bullet, #[derive(RemoveExterminate)] gets called, and we get:

    //! Pseudo-code!
    
    RemoveExterminate! {
        // Note: thanks to `#[derive]` Magicā„¢, the following tokens have
        // been `#[cfg_eval]`-cleaned up! // ----+
        #[exterminate]                        // |
        #[other attrs...]                     // |
        #[for_instance] // <---------------------+
        struct Foo {                          // |
            x: i32,                           // |
        /* removed! <----------------------------+
            #[cfg(any())]
            y: u8,
         */
        }
    }

    From here, this RemoveExterminate macro knows it has been invoked on an item doomed to die. It can thus, contrary to usual derives, reƫmit the item it receives, modifying it at leisure.

    What it actually does, then, is reƫmitting it almost as-is, but for that pesky #[exterminate] which is no longer useful to us.

    #[other attrs...]
    #[for_instance]
    struct Foo {
        x: i32,
    }

Which matches the expected #[cfg_eval] semantics on that original input:

#[cfg_eval]
#[other attrs...]
#[cfg_attr(all(), for_instance)]
struct Foo {
    x: i32,
    #[cfg(any())]
    y: u8,
}

§Reëxporting this macro: the crate = ::some::path optional attribute arg

The following section is only meaningful to macro authors wishing to reëxport #[cfg_eval] / reüse it from further downstream code.

Click to show

Given the above implementation, it should be no surprise that #[cfg_eval] needs to refer to sibling helper macros in a path-robust manner.

By default, it uses ::cfg_eval::* paths, expecting there to be an external ::cfg_eval crate with the items of this very crate (like proc-macros are known to do, given lack of $crate for non-function-like proc-macros), as a direct dependency.

This means that if you have your own macro which, internally (or publicly), needs #[cfg_eval], then your downstream dependents, which are likely not to depend on ::cfg_eval directly, will be unable to make it work.

The solution, to this, is an optional crate = ::some::path attribute arg:

#[doc(hidden)] /** Not part of the public API */
pub mod __internal {
    pub use ::cfg_eval;
}

#[macro_export]
macro_rules! example {( $input:item ) => (
    #[$crate::__internal::cfg_eval::cfg_eval(
        // Add this:
        crate = $crate::__internal::cfg_eval // šŸ‘ˆ
    )]
    $input
)}

// so that downstream users can write:
::your_crate::example! {
    struct Foo { /* … */ }
}