-
Notifications
You must be signed in to change notification settings - Fork 13.3k
Tracking issue for cfg_match
#115585
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
I think it needs to be considered how this will interact with Also, if this is intended to be |
Modulo (auto) doc(cfg), this implementation is more complex than it needs to be, since it's only supporting items. simpler impl#[macro_export]
macro_rules! cfg_match {
{
cfg($cfg:meta) => { $($cfg_items:item)* }
$(cfg($rest_cfg:meta) => { $($rest_cfg_items:item)* })*
$(_ => { $($fallthrough_items:item)* })?
} => {
#[cfg($cfg)]
$crate::cfg_match! {
_ => {
$($cfg_items)*
// validate the inactive cfgs as well
#[cfg(any($($rest_cfg),*))]
const _: () = ();
}
}
#[cfg(not($cfg))]
$crate::cfg_match! {
$(cfg($rest_cfg) => { $($rest_cfg_items)* })*
$(_ => { $($fallthrough_items)* })?
}
};
{
$(_ => { $($items:item)* })?
} => {
$($($items)*)?
};
} No internal matching arms necessary; just a normal inductive definition over the input syntax. The one tricky difference is that without the extra This can be rather simply adapted to expression position by matching a block and using It's unfortunate though that implemented without compiler support |
Something else to consider: This should be able to detect invalid cfgs like the following (ideally regardless of whether the match_cfg! {
cfg(unix) => {}
cfg(unix) => {} // Ooops, specified same `cfg` twice
_ => {}
} |
I would prefer if it could support both items and expressions. It being part of the stdlib means that implementing it in the compiler would be completely reasonable, and would also make a lint that warns on redundant cfg arms pretty doable. |
For detecting duplicates, it's important to note that this is more difficult than it seems at first. rustdoc has been dealing with this for |
I tested the current (1.74.0-nightly (e6aabe8b3 2023-09-26)) behavior of doc_auto_cfg: items defined in Fixing this is actually fairly simple once you understand why this is: instead of expanding to I want to additionally point out that doc_auto_cfg fundamentally can only see what items are defined on the current cfg and mark them as only available on whatever cfg this specific implementation is gated to. If you want to expose a portable API, either define a single portable public version of the API (and use private cfg gates to implement it) or use So what I personally think the macro definition wants to be:
in item position#[macro_export]
macro_rules! cfg_match {
// base case without wildcard arm
() => {};
// base case with wildcard arm (items)
(_ => { $($wild_item:item)* }) => {
$($wild_item)*
};
// process first arm (items; propagate cfg)
(
cfg($this_cfg:meta) => { $($this_item:item)* }
$(cfg($rest_cfg:meta) => { $($rest_item:item)* })*
$(_ => { $($wild_item:item)* })?
) => {
$(#[cfg($this_cfg)] $this_item)*
$crate::cfg_match! {
$(cfg($rest_cfg) => { $(#[cfg(not($this_cfg))] $rest_item)* })*
$(_ => { $(#[cfg(not($this_cfg))] $wild_item)* })?
}
};
} in expression position#[macro_export]
macro_rules! cfg_match {
// base case without wildcard arm
() => {};
// base case with wildcard arm (expression block)
(_ => $wild:block) => {
$wild
};
// process first arm (expression block)
(
cfg($this_cfg:meta) => $this:block
$(cfg($rest_cfg:meta) => $rest:block)*
$(_ => $wild:block)?
) => {
{
#[cfg($this_cfg)]
$crate::cfg_match! {
_ => $this
}
#[cfg(not($this_cfg))]
$crate::cfg_match! {
$(cfg($rest_cfg) => $rest)*
$(_ => $wild)?
}
}
};
} in statement position#[macro_export]
macro_rules! cfg_match {
// base case without wildcard arm
() => {};
// base case with wildcard arm
(_ => { $($wild:tt)* }) => {
$($wild)*
};
// process first arm
(
cfg($this_cfg:meta) => { $($this:tt)* }
$(cfg($rest_cfg:meta) => { $($rest:tt)* })*
$(_ => { $($wild:tt)* })?
) => {
#[cfg($this_cfg)]
$crate::cfg_match! {
_ => { $($this)* }
}
#[cfg(not($this_cfg))]
$crate::cfg_match! {
$(cfg($rest_cfg) => { $($rest)* })*
$(_ => { $($wild)* })?
}
};
} This technically is the most flexible spelling just passing through tt sequences; it works perfectly for items and statements (modulo preserving doc_auto_cfg) and can even be used for expressions if wrapped inside an outer block (e.g. There's also the further question of how much syntax checking is expected for inactive arms. The current implementation that matches everything as A far too overly clever implementation:#[macro_export]
macro_rules! cfg_match {
// base case (wildcard arm)
() => {};
// base case (no wildcard arm)
(_ => { $($wild:tt)* }) => {
$($wild)*
};
// Note: macro patterns matching $:stmt need to come before those matching
// $:item, as otherwise statements will match as the $:item pattern instead
// even when not valid as items (and obviously thus cause a syntax error).
// apply cfg to wildcard arm (expression position)
(_ => #[cfg($cfg:meta)] { $wild:expr }) => {
{ #[cfg($cfg)] { $wild } }
};
// apply cfg to wildcard arm (statement position)
(_ => #[cfg($cfg:meta)] { $wild_stmt:stmt; $($wild:tt)* }) => {
#[cfg($cfg)]
$crate::cfg_match! {
_ => { $wild_stmt; $($wild)* }
}
// We only parse the first statement in the macro pattern, so we need to
// emit the captured syntax even when the cfg is inactive such that any
// syntax errors still get reported. We do the macro capture this way as
// if we match as statements, minor typos try to parse as items instead.
#[cfg(any())]
const _: () = { wild_stmt; $($wild)* };
};
// apply cfg to wildcard arm (item position)
(_ => #[cfg($cfg:meta)] { $($wild:item)* }) => {
$(#[cfg($cfg)] $wild)*
};
// merge multiple cfg into a single cfg(all()) predicate
(_ => $(#[cfg($cfg:meta)])+ { $($wild:tt)* }) => {
$crate::cfg_match! {
_ => #[cfg(all($($cfg),+))] { $($wild)* }
}
};
// split off first arm (empty)
(
cfg($this_cfg_arm:meta) => $(#[cfg($this_cfg_pre:meta)])* { /* empty */ }
$(cfg($rest_cfg_arm:meta) => $(#[cfg($rest_cfg_pre:meta)])* { $($rest:tt)* })*
$(_ => $(#[cfg($wild_cfg_pre:meta)])* { $($wild:tt)* })?
) => {
$crate::cfg_match! {
$(cfg($rest_cfg_arm) => $(#[cfg($rest_cfg_pre)])* #[cfg(not($this_cfg_arm))] { $($rest)* })*
$(_ => $(#[cfg($wild_cfg_pre)])* #[cfg(not($this_cfg_arm))] { $($wild)* })?
}
};
// split off first arm (expression position)
(
cfg($this_cfg_arm:meta) => $(#[cfg($this_cfg_pre:meta)])* { $this:expr }
$(cfg($rest_cfg_arm:meta) => $(#[cfg($rest_cfg_pre:meta)])* { $rest:expr })*
$(_ => $(#[cfg($wild_cfg_pre:meta)])* { $wild:expr })?
) => {{
$crate::cfg_match! {
_ => $(#[cfg($this_cfg_pre)])* #[cfg($this_cfg_arm)] { $this }
}
$crate::cfg_match! {
$(cfg($rest_cfg_arm) => $(#[cfg($rest_cfg_pre)])* #[cfg(not($this_cfg_arm))] { $rest })*
$(_ => $(#[cfg($wild_cfg_pre)])* #[cfg(not($this_cfg_arm))] { $wild })?
}
}};
// split off first arm (statement position)
(
cfg($this_cfg_arm:meta) => $(#[cfg($this_cfg_pre:meta)])* { $this_stmt:stmt; $($this:tt)* }
$(cfg($rest_cfg_arm:meta) => $(#[cfg($rest_cfg_pre:meta)])* { $($rest:tt)* })*
$(_ => $(#[cfg($wild_cfg_pre:meta)])* { $($wild:tt)* })?
) => {
$crate::cfg_match! {
_ => $(#[cfg($this_cfg_pre)])* #[cfg($this_cfg_arm)] { $this_stmt; $($this)* }
}
// Ensure all arms infer statement position by prefixing a statement. We
// only match the first arm in the macro pattern because otherwise minor
// typos unhelpfully cause all arms to parse as item definitions instead
// and thus reporting an error nowhere near the actual problem, or even
// more amusingly, accidentally providing automatic semicolon insertion.
$crate::cfg_match! {
$(cfg($rest_cfg_arm) => $(#[cfg($rest_cfg_pre)])* #[cfg(not($this_cfg_arm))] { {}; $($rest)* })*
$(_ => $(#[cfg($wild_cfg_pre)])* #[cfg(not($this_cfg_arm))] { {}; $($wild)* })?
}
};
// split off first arm (item position)
(
cfg($this_cfg_arm:meta) => $(#[cfg($this_cfg_pre:meta)])* { $($this:item)* }
$(cfg($rest_cfg_arm:meta) => $(#[cfg($rest_cfg_pre:meta)])* { $($rest:item)* })*
$(_ => $(#[cfg($wild_cfg_pre:meta)])* { $($wild:item)* })?
) => {
$crate::cfg_match! {
_ => $(#[cfg($this_cfg_pre)])* #[cfg($this_cfg_arm)] { $($this)* }
}
// Items just match as item in the macro pattern. The error messages
// would be improved if we could pass through tokens without eagerly
// matching them as items, but we need to parse the items in the macro
// so that we can apply the cfg attributes to every item in the arm.
$crate::cfg_match! {
$(cfg($rest_cfg_arm) => $(#[cfg($rest_cfg_pre)])* #[cfg(not($this_cfg_arm))] { $($rest)* })*
$(_ => $(#[cfg($wild_cfg_pre)])* #[cfg(not($this_cfg_arm))] { $($wild)* })?
}
};
} This does almost always behave as desired by using the arm bodies to try to guess how to expand, but is unreasonably complicated by attempting to do so and as a result causes some very poor errors for reasonable syntax errors. |
Well, since #115416 (comment) I have been working in transposing It is worth noting that the current implementation mimics cfg_match! {
cfg(unix) => { fn foo() -> i32 { 1 } },
_ => { fn foo() -> i32 { 2 } },
}
# Expands to ->
#[cfg(all(unix, not(any())))]
cfg_match! { @__identity fn foo() -> i32 { 1 } }
#[cfg(all(not(any(unix))))]
cfg_match! { @__identity fn foo() -> i32 { 2 } } That said, changing the strategy to apply |
FWIW, What we need to do is to keep expansion backtraces for |
Initiate the inner usage of `cfg_match` (Compiler) cc rust-lang#115585 Dogfood to test the implementation and remove dependencies.
Initiate the inner usage of `cfg_match` (Compiler) cc rust-lang#115585 Dogfood to test the implementation and remove dependencies.
Initiate the inner usage of `cfg_match` (Compiler) cc rust-lang#115585 Dogfood to test the implementation and remove dependencies.
Rollup merge of rust-lang#116312 - c410-f3r:try, r=Mark-Simulacrum Initiate the inner usage of `cfg_match` (Compiler) cc rust-lang#115585 Dogfood to test the implementation and remove dependencies.
Initiate the inner usage of `cfg_match` (Library) cc rust-lang#115585 Continuation of rust-lang#116312
Remove `cfg_match` from the prelude Fixes rust-lang#117057 cc rust-lang#115585
Remove `cfg_match` from the prelude Fixes rust-lang#117057 cc rust-lang#115585
Rollup merge of rust-lang#117162 - c410-f3r:try, r=workingjubilee Remove `cfg_match` from the prelude Fixes rust-lang#117057 cc rust-lang#115585
Initiate the inner usage of `cfg_match` (Library) cc rust-lang#115585 Continuation of rust-lang#116312
Initiate the inner usage of `cfg_match` (Library) cc rust-lang#115585 Continuation of rust-lang#116312
Initiate the inner usage of `cfg_match` (Library) cc rust-lang#115585 Continuation of rust-lang#116312
I just found out about this today, and my first impression is that semantically this isn't really a " match () {
() if unix => { ... }
() if target_pointer_width.contains("32") => { ... }
_ => { ... }
} A more similar construct in my mind are the All that is to say that |
Use `cfg_match!` in core All of these uses of `cfg_if!` do not utilize that `cfg_if!` works with auto-`doc(cfg)`, so no functionality is lost from switching to use `cfg_match!` instead. We *do* lose what rustfmt special support exists for `cfg_if!`, though. Tracking issue: rust-lang#115585
…=dtolnay Make `cfg_match!` a semitransparent macro IIUC this is preferred when (potentially) stabilizing `macro` items, to avoid potentially utilizing def-site hygiene instead of mixed-site. Tracking issue: rust-lang#115585
…=dtolnay Make `cfg_match!` a semitransparent macro IIUC this is preferred when (potentially) stabilizing `macro` items, to avoid potentially utilizing def-site hygiene instead of mixed-site. Tracking issue: rust-lang#115585
Rollup merge of rust-lang#138993 - CAD97:cfg_match_semitransparent, r=dtolnay Make `cfg_match!` a semitransparent macro IIUC this is preferred when (potentially) stabilizing `macro` items, to avoid potentially utilizing def-site hygiene instead of mixed-site. Tracking issue: rust-lang#115585
…=dtolnay Make `cfg_match!` a semitransparent macro IIUC this is preferred when (potentially) stabilizing `macro` items, to avoid potentially utilizing def-site hygiene instead of mixed-site. Tracking issue: rust-lang#115585
This is very adjacent to a core language feature, and procedurally I think extending that feature with a macro in core should involve lang input. @rustbot label I-lang-nominated Personally I am very much in favor of improving the usability of If we do accept this macro and it accepts novel syntax for matching against cfgs (like using |
@tmandry To the best of my understanding, |
nitpick: arguably this is But I'm happy to let libs-api argue about that :) Abstractly I agree that this is a helpful thing that would be nice to have, in at least some form. |
@scottmcm and others in the lang meeting made the point that while we can't necessarily enforce exhaustiveness, we might want to consider the possibility of having an implicit I don't know if we want to do that by default, or if we want that to be a lint of some kind, but either way, it's worth considering. @rust-lang/libs-api |
Based on having discussed this, and based on other recent related discussions such as the ones @tmandry mentions, I'm feeling more strongly than I did in the past that if we want to include |
Note: libs-api's previous decision on the naming is here: #137198 |
@rustbot labels +I-libs-api-nominated Nominating for libs-api to consider feedback from lang discussion. |
Sounds good then.
I'm somewhat persuaded by these. If there were a strong reason not to do this it would be okay not to, though I might also quibble with the name in that case. But it feels like a nice way to tie things together, while guarding against unplanned config combinations with an explicit error message. |
I don't see this mentioned yet: I expect this to work (e.g. #![feature(cfg_match)]
fn main() {
println!(cfg_match! {{
unix => { "foo" }
_ => { "bar" }
}});
} I believe given the current grammar https://doc.rust-lang.org/src/core/macros/mod.rs.html#330, the But I believe that, in expression contexts at least, that expands to a
The quick fix is to accept a block containing a string literal, I guess? I don't think there is a better solution when using |
FYI, Bevy has added its own version of this in bevyengine/bevy#18822, primarily to rein in virality of |
On the libs-api call today, this was discussed at some length. Here's where it landed:
On the name, the more we talked about it, the more we continued to develop cold feet about calling this a kind of We committed to and engaged in a thorough syntactic bikeshed to pick a better name. The two candidates that came out of that as plausible -- with general support and few objections -- were The downside of The upside of In the end, we leaned in favor of the name with more existing familiarity and that could be read as a verb, and went with Not including Currently, the macro supports an expression-position syntax that requires the use of double braces such as More generally, we felt that there were a number of open questions we should address regarding the behavior of this macro in expression position. One, while we were OK with always requiring the arm body to be braced in item position, we were still discussing the merits of allowing unbraced arm bodies in expression position. Two, currently, the macro always returns a Some were fine with simply blocking on that implementation work being done. Another option, though, is that we discussed that we'd be OK with stabilizing this only in item position and in expression position when wrapped in outer braces such as Thanks to everyone in this thread for the many useful bits of analysis and feedback that were helpful in arriving at this point after some back and forth. The plan is to cancel the current proposed FCP and to then restart it (with lang) with the proposal as detailed in this comment once updates have been made to the implementation. |
@rfcbot cancel |
@Amanieu proposal cancelled. |
Provides a native way to easily manage multiple conditional flags without having to rewrite each clause multiple times.
Public API
Steps / History
cfg_match!
macro #115416)Unresolved Questions
What should the final syntax be? A match-like syntax feels more natural in the sense that each macro fragment resembles an arm.Should the macro be supported directly by a language feature?What should the feature name be?cfg_match
conflicts with the already existingcfg_match
crate.How can we support usage in both expression-position and item position?match
statements.References
cfg_if
#59442The text was updated successfully, but these errors were encountered: