diff --git a/library/core/src/macros/mod.rs b/library/core/src/macros/mod.rs index 646100fe27b..c27be8d2ffd 100644 --- a/library/core/src/macros/mod.rs +++ b/library/core/src/macros/mod.rs @@ -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` diff --git a/library/core/tests/lib.rs b/library/core/tests/lib.rs index 17011b845cf..773f2b955d8 100644 --- a/library/core/tests/lib.rs +++ b/library/core/tests/lib.rs @@ -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)] @@ -139,6 +140,7 @@ mod hash; mod intrinsics; mod iter; mod lazy; +#[cfg(test)] mod macros; mod manually_drop; mod mem; diff --git a/library/core/tests/macros.rs b/library/core/tests/macros.rs index ff3632e3550..eb886def164 100644 --- a/library/core/tests/macros.rs +++ b/library/core/tests/macros.rs @@ -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,); @@ -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_()); + + #[cfg(not(target_pointer_width = "16"))] + 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 { Some(1) } + } + _ => { fn works1() -> Option { 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 { 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(); + } +} diff --git a/library/std/src/lib.rs b/library/std/src/lib.rs index 5e3249655b8..f1f0f8b1653 100644 --- a/library/std/src/lib.rs +++ b/library/std/src/lib.rs @@ -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;