Skip to content

Commit

Permalink
Add the cfg_match macro
Browse files Browse the repository at this point in the history
  • Loading branch information
c410-f3r committed Sep 23, 2023
1 parent 3050938 commit 9cea470
Show file tree
Hide file tree
Showing 4 changed files with 248 additions and 0 deletions.
89 changes: 89 additions & 0 deletions library/core/src/macros/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,95 @@ pub macro debug_assert_matches($($arg:tt)*) {
}
}

/// A macro for defining `#[cfg]` match-like statements.
///
/// It is similar to the `if/elif` C preprocessor macro by allowing definition of a cascade of
/// `#[cfg]` cases, emitting the implementation which matches first.
///
/// This allows you to conveniently provide a long list `#[cfg]`'d blocks of code
/// without having to rewrite each clause multiple times.
///
/// Trailing `_` wildcard match arms are **optional** and they indicate a fallback branch when
/// all previous declarations do not evaluate to true.
///
/// # Example
///
/// ```
/// #![feature(cfg_match)]
///
/// cfg_match! {
/// cfg(unix) => {
/// fn foo() { /* unix specific functionality */ }
/// }
/// cfg(target_pointer_width = "32") => {
/// fn foo() { /* non-unix, 32-bit functionality */ }
/// }
/// _ => {
/// fn foo() { /* fallback implementation */ }
/// }
/// }
/// ```
#[macro_export]
#[unstable(feature = "cfg_match", issue = "115585")]
#[rustc_diagnostic_item = "cfg_match"]
macro_rules! cfg_match {
// with a final wildcard
(
$(cfg($initial_meta:meta) => { $($initial_tokens:item)* })+
_ => { $($extra_tokens:item)* }
) => {
cfg_match! {
@__items ();
$((($initial_meta) ($($initial_tokens)*)),)+
(() ($($extra_tokens)*)),
}
};

// without a final wildcard
(
$(cfg($extra_meta:meta) => { $($extra_tokens:item)* })*
) => {
cfg_match! {
@__items ();
$((($extra_meta) ($($extra_tokens)*)),)*
}
};

// Internal and recursive macro to emit all the items
//
// Collects all the previous cfgs in a list at the beginning, so they can be
// negated. After the semicolon is all the remaining items.
(@__items ($($_:meta,)*);) => {};
(
@__items ($($no:meta,)*);
(($($yes:meta)?) ($($tokens:item)*)),
$($rest:tt,)*
) => {
// Emit all items within one block, applying an appropriate #[cfg]. The
// #[cfg] will require all `$yes` matchers specified and must also negate
// all previous matchers.
#[cfg(all(
$($yes,)?
not(any($($no),*))
))]
cfg_match! { @__identity $($tokens)* }

// Recurse to emit all other items in `$rest`, and when we do so add all
// our `$yes` matchers to the list of `$no` matchers as future emissions
// will have to negate everything we just matched as well.
cfg_match! {
@__items ($($no,)* $($yes,)?);
$($rest,)*
}
};

// Internal macro to make __apply work out right for different match types,
// because of how macros match/expand stuff.
(@__identity $($tokens:item)*) => {
$($tokens)*
};
}

/// Returns whether the given expression matches any of the given patterns.
///
/// Like in a `match` expression, the pattern can be optionally followed by `if`
Expand Down
2 changes: 2 additions & 0 deletions library/core/tests/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@
#![feature(const_option_ext)]
#![feature(const_result)]
#![cfg_attr(target_has_atomic = "128", feature(integer_atomics))]
#![cfg_attr(test, feature(cfg_match))]
#![feature(int_roundings)]
#![feature(slice_group_by)]
#![feature(split_array)]
Expand Down Expand Up @@ -139,6 +140,7 @@ mod hash;
mod intrinsics;
mod iter;
mod lazy;
#[cfg(test)]
mod macros;
mod manually_drop;
mod mem;
Expand Down
154 changes: 154 additions & 0 deletions library/core/tests/macros.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,25 @@
trait Trait {
fn blah(&self);
}

#[allow(dead_code)]
struct Struct;

impl Trait for Struct {
cfg_match! {
cfg(feature = "blah") => {
fn blah(&self) {
unimplemented!();
}
}
_ => {
fn blah(&self) {
unimplemented!();
}
}
}
}

#[test]
fn assert_eq_trailing_comma() {
assert_eq!(1, 1,);
Expand All @@ -18,3 +40,135 @@ fn assert_ne_trailing_comma() {
fn matches_leading_pipe() {
matches!(1, | 1 | 2 | 3);
}

#[test]
fn cfg_match_basic() {
cfg_match! {
cfg(target_pointer_width = "64") => { fn f0_() -> bool { true }}
}

cfg_match! {
cfg(unix) => { fn f1_() -> bool { true }}
cfg(any(target_os = "macos", target_os = "linux")) => { fn f1_() -> bool { false }}
}

cfg_match! {
cfg(target_pointer_width = "32") => { fn f2_() -> bool { false }}
cfg(target_pointer_width = "64") => { fn f2_() -> bool { true }}
}

cfg_match! {
cfg(target_pointer_width = "16") => { fn f3_() -> i32 { 1 }}
_ => { fn f3_() -> i32 { 2 }}
}

#[cfg(target_pointer_width = "64")]
assert!(f0_());

#[cfg(unix)]
assert!(f1_());

#[cfg(target_pointer_width = "32")]
assert!(!f2_());
#[cfg(target_pointer_width = "64")]
assert!(f2_());

assert_eq!(f3_(), 2);
}

#[test]
fn cfg_match_debug_assertions() {
cfg_match! {
cfg(debug_assertions) => {
assert!(cfg!(debug_assertions));
assert_eq!(4, 2+2);
}
_ => {
assert!(cfg!(not(debug_assertions)));
assert_eq!(10, 5+5);
}
}
}

#[cfg(target_pointer_width = "64")]
#[test]
fn cfg_match_no_duplication_on_64() {
cfg_match! {
cfg(windows) => {
fn foo() {}
}
cfg(unix) => {
fn foo() {}
}
cfg(target_pointer_width = "64") => {
fn foo() {}
}
}
foo();
}

#[test]
fn cfg_match_options() {
cfg_match! {
cfg(test) => {
use core::option::Option as Option2;
fn works1() -> Option2<u32> { Some(1) }
}
_ => { fn works1() -> Option<u32> { None } }
}

cfg_match! {
cfg(feature = "foo") => { fn works2() -> bool { false } }
cfg(test) => { fn works2() -> bool { true } }
_ => { fn works2() -> bool { false } }
}

cfg_match! {
cfg(feature = "foo") => { fn works3() -> bool { false } }
_ => { fn works3() -> bool { true } }
}

cfg_match! {
cfg(test) => {
use core::option::Option as Option3;
fn works4() -> Option3<u32> { Some(1) }
}
}

cfg_match! {
cfg(feature = "foo") => { fn works5() -> bool { false } }
cfg(test) => { fn works5() -> bool { true } }
}

assert!(works1().is_some());
assert!(works2());
assert!(works3());
assert!(works4().is_some());
assert!(works5());
}

#[test]
fn cfg_match_two_functions() {
cfg_match! {
cfg(target_pointer_width = "64") => {
fn foo1() {}
fn bar1() {}
}
_ => {
fn foo2() {}
fn bar2() {}
}
}

#[cfg(target_pointer_width = "64")]
{
foo1();
bar1();
}
#[cfg(not(target_pointer_width = "64"))]
{
foo2();
bar2();
}
}

3 changes: 3 additions & 0 deletions library/std/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -665,6 +665,9 @@ pub use core::{
)]
pub use core::concat_bytes;

#[unstable(feature = "cfg_match", issue = "115585")]
pub use core::cfg_match;

#[stable(feature = "core_primitive", since = "1.43.0")]
pub use core::primitive;

Expand Down

0 comments on commit 9cea470

Please sign in to comment.