Skip to content

Commit 8f6f01c

Browse files
committed
Add the cfg_match! macro
1 parent 3050938 commit 8f6f01c

File tree

4 files changed

+249
-0
lines changed

4 files changed

+249
-0
lines changed

library/core/src/macros/mod.rs

+89
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,95 @@ pub macro debug_assert_matches($($arg:tt)*) {
321321
}
322322
}
323323

324+
/// A macro for defining `#[cfg]` match-like statements.
325+
///
326+
/// It is similar to the `if/elif` C preprocessor macro by allowing definition of a cascade of
327+
/// `#[cfg]` cases, emitting the implementation which matches first.
328+
///
329+
/// This allows you to conveniently provide a long list `#[cfg]`'d blocks of code
330+
/// without having to rewrite each clause multiple times.
331+
///
332+
/// Trailing `_` wildcard match arms are **optional** and they indicate a fallback branch when
333+
/// all previous declarations do not evaluate to true.
334+
///
335+
/// # Example
336+
///
337+
/// ```
338+
/// #![feature(cfg_match)]
339+
///
340+
/// cfg_match! {
341+
/// cfg(unix) => {
342+
/// fn foo() { /* unix specific functionality */ }
343+
/// }
344+
/// cfg(target_pointer_width = "32") => {
345+
/// fn foo() { /* non-unix, 32-bit functionality */ }
346+
/// }
347+
/// _ => {
348+
/// fn foo() { /* fallback implementation */ }
349+
/// }
350+
/// }
351+
/// ```
352+
#[macro_export]
353+
#[unstable(feature = "cfg_match", issue = "115585")]
354+
#[rustc_diagnostic_item = "cfg_match"]
355+
macro_rules! cfg_match {
356+
// with a final wildcard
357+
(
358+
$(cfg($initial_meta:meta) => { $($initial_tokens:item)* })+
359+
_ => { $($extra_tokens:item)* }
360+
) => {
361+
cfg_match! {
362+
@__items ();
363+
$((($initial_meta) ($($initial_tokens)*)),)+
364+
(() ($($extra_tokens)*)),
365+
}
366+
};
367+
368+
// without a final wildcard
369+
(
370+
$(cfg($extra_meta:meta) => { $($extra_tokens:item)* })*
371+
) => {
372+
cfg_match! {
373+
@__items ();
374+
$((($extra_meta) ($($extra_tokens)*)),)*
375+
}
376+
};
377+
378+
// Internal and recursive macro to emit all the items
379+
//
380+
// Collects all the previous cfgs in a list at the beginning, so they can be
381+
// negated. After the semicolon is all the remaining items.
382+
(@__items ($($_:meta,)*);) => {};
383+
(
384+
@__items ($($no:meta,)*);
385+
(($($yes:meta)?) ($($tokens:item)*)),
386+
$($rest:tt,)*
387+
) => {
388+
// Emit all items within one block, applying an appropriate #[cfg]. The
389+
// #[cfg] will require all `$yes` matchers specified and must also negate
390+
// all previous matchers.
391+
#[cfg(all(
392+
$($yes,)?
393+
not(any($($no),*))
394+
))]
395+
cfg_match! { @__identity $($tokens)* }
396+
397+
// Recurse to emit all other items in `$rest`, and when we do so add all
398+
// our `$yes` matchers to the list of `$no` matchers as future emissions
399+
// will have to negate everything we just matched as well.
400+
cfg_match! {
401+
@__items ($($no,)* $($yes,)?);
402+
$($rest,)*
403+
}
404+
};
405+
406+
// Internal macro to make __apply work out right for different match types,
407+
// because of how macros match/expand stuff.
408+
(@__identity $($tokens:item)*) => {
409+
$($tokens)*
410+
};
411+
}
412+
324413
/// Returns whether the given expression matches any of the given patterns.
325414
///
326415
/// Like in a `match` expression, the pattern can be optionally followed by `if`

library/core/tests/lib.rs

+2
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@
9696
#![feature(const_option_ext)]
9797
#![feature(const_result)]
9898
#![cfg_attr(target_has_atomic = "128", feature(integer_atomics))]
99+
#![cfg_attr(test, feature(cfg_match))]
99100
#![feature(int_roundings)]
100101
#![feature(slice_group_by)]
101102
#![feature(split_array)]
@@ -139,6 +140,7 @@ mod hash;
139140
mod intrinsics;
140141
mod iter;
141142
mod lazy;
143+
#[cfg(test)]
142144
mod macros;
143145
mod manually_drop;
144146
mod mem;

library/core/tests/macros.rs

+155
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,25 @@
1+
trait Trait {
2+
fn blah(&self);
3+
}
4+
5+
#[allow(dead_code)]
6+
struct Struct;
7+
8+
impl Trait for Struct {
9+
cfg_match! {
10+
cfg(feature = "blah") => {
11+
fn blah(&self) {
12+
unimplemented!();
13+
}
14+
}
15+
_ => {
16+
fn blah(&self) {
17+
unimplemented!();
18+
}
19+
}
20+
}
21+
}
22+
123
#[test]
224
fn assert_eq_trailing_comma() {
325
assert_eq!(1, 1,);
@@ -18,3 +40,136 @@ fn assert_ne_trailing_comma() {
1840
fn matches_leading_pipe() {
1941
matches!(1, | 1 | 2 | 3);
2042
}
43+
44+
#[test]
45+
fn cfg_match_basic() {
46+
cfg_match! {
47+
cfg(target_pointer_width = "64") => { fn f0_() -> bool { true }}
48+
}
49+
50+
cfg_match! {
51+
cfg(unix) => { fn f1_() -> bool { true }}
52+
cfg(any(target_os = "macos", target_os = "linux")) => { fn f1_() -> bool { false }}
53+
}
54+
55+
cfg_match! {
56+
cfg(target_pointer_width = "32") => { fn f2_() -> bool { false }}
57+
cfg(target_pointer_width = "64") => { fn f2_() -> bool { true }}
58+
}
59+
60+
cfg_match! {
61+
cfg(target_pointer_width = "16") => { fn f3_() -> i32 { 1 }}
62+
_ => { fn f3_() -> i32 { 2 }}
63+
}
64+
65+
#[cfg(target_pointer_width = "64")]
66+
assert!(f0_());
67+
68+
#[cfg(unix)]
69+
assert!(f1_());
70+
71+
#[cfg(target_pointer_width = "32")]
72+
assert!(!f2_());
73+
#[cfg(target_pointer_width = "64")]
74+
assert!(f2_());
75+
76+
#[cfg(not(target_pointer_width = "16"))]
77+
assert_eq!(f3_(), 2);
78+
}
79+
80+
#[test]
81+
fn cfg_match_debug_assertions() {
82+
cfg_match! {
83+
cfg(debug_assertions) => {
84+
assert!(cfg!(debug_assertions));
85+
assert_eq!(4, 2+2);
86+
}
87+
_ => {
88+
assert!(cfg!(not(debug_assertions)));
89+
assert_eq!(10, 5+5);
90+
}
91+
}
92+
}
93+
94+
#[cfg(target_pointer_width = "64")]
95+
#[test]
96+
fn cfg_match_no_duplication_on_64() {
97+
cfg_match! {
98+
cfg(windows) => {
99+
fn foo() {}
100+
}
101+
cfg(unix) => {
102+
fn foo() {}
103+
}
104+
cfg(target_pointer_width = "64") => {
105+
fn foo() {}
106+
}
107+
}
108+
foo();
109+
}
110+
111+
#[test]
112+
fn cfg_match_options() {
113+
cfg_match! {
114+
cfg(test) => {
115+
use core::option::Option as Option2;
116+
fn works1() -> Option2<u32> { Some(1) }
117+
}
118+
_ => { fn works1() -> Option<u32> { None } }
119+
}
120+
121+
cfg_match! {
122+
cfg(feature = "foo") => { fn works2() -> bool { false } }
123+
cfg(test) => { fn works2() -> bool { true } }
124+
_ => { fn works2() -> bool { false } }
125+
}
126+
127+
cfg_match! {
128+
cfg(feature = "foo") => { fn works3() -> bool { false } }
129+
_ => { fn works3() -> bool { true } }
130+
}
131+
132+
cfg_match! {
133+
cfg(test) => {
134+
use core::option::Option as Option3;
135+
fn works4() -> Option3<u32> { Some(1) }
136+
}
137+
}
138+
139+
cfg_match! {
140+
cfg(feature = "foo") => { fn works5() -> bool { false } }
141+
cfg(test) => { fn works5() -> bool { true } }
142+
}
143+
144+
assert!(works1().is_some());
145+
assert!(works2());
146+
assert!(works3());
147+
assert!(works4().is_some());
148+
assert!(works5());
149+
}
150+
151+
#[test]
152+
fn cfg_match_two_functions() {
153+
cfg_match! {
154+
cfg(target_pointer_width = "64") => {
155+
fn foo1() {}
156+
fn bar1() {}
157+
}
158+
_ => {
159+
fn foo2() {}
160+
fn bar2() {}
161+
}
162+
}
163+
164+
#[cfg(target_pointer_width = "64")]
165+
{
166+
foo1();
167+
bar1();
168+
}
169+
#[cfg(not(target_pointer_width = "64"))]
170+
{
171+
foo2();
172+
bar2();
173+
}
174+
}
175+

library/std/src/lib.rs

+3
Original file line numberDiff line numberDiff line change
@@ -665,6 +665,9 @@ pub use core::{
665665
)]
666666
pub use core::concat_bytes;
667667

668+
#[unstable(feature = "cfg_match", issue = "115585")]
669+
pub use core::cfg_match;
670+
668671
#[stable(feature = "core_primitive", since = "1.43.0")]
669672
pub use core::primitive;
670673

0 commit comments

Comments
 (0)