Allow passing expr metavariable as cfg predicate rust-lang/rust#146961

Merged

28 comments and reviews loaded in 1.44s

Jules-Bertholet Avatar
Jules-Bertholet on 2025-09-24 05:21:01 UTC · edited
Jules-Bertholet Avatar
Jules-Bertholet on 2025-09-24 05:21:01 UTC · edited
View on GitHub

View all comments

This PR allows expanding expr metavariables inside the configuration predicates of cfg and cfg_attr invocations.
For example, the following code will now compile:

macro_rules! mac {
    ($e:expr) => {
        #[cfg_attr($e, inline)]
        #[cfg($e)]
        fn func() {}

        #[cfg(not($e))]
        fn func() {
            panic!()
        }
    }
}


mac!(any(unix, feature = "foo"));

There is currently no macro_rules fragment specifier that can represent all valid cfg predicates. meta comes closest, but excludes true and false. By fixing that, this change makes it easier to write declarative macros that parse cfg or cfg_attr invocations, for example #146281.

@rustbot label T-lang needs-fcp A-attributes A-cfg A-macros

👍4
rustbot Avatar
rustbot on 2025-09-24 05:21:04 UTC
rustbot Avatar
rustbot on 2025-09-24 05:21:04 UTC
View on GitHub

Some changes occurred in compiler/rustc_attr_parsing

cc @jdonszelmann

rustbot Avatar
rustbot on 2025-09-24 05:21:06 UTC
rustbot Avatar
rustbot on 2025-09-24 05:21:06 UTC
View on GitHub

r? @fee1-dead

rustbot has assigned @fee1-dead.
They will have a look at your PR within the next two weeks and either review your PR or reassign to another reviewer.

Use r? to explicitly pick a reviewer

rust-log-analyzer Avatar
rust-log-analyzer on 2025-09-24 06:38:01 UTC · hidden as outdated
rust-log-analyzer Avatar
rust-log-analyzer on 2025-09-24 06:38:01 UTC · hidden as outdated
View on GitHub

The job x86_64-gnu-llvm-20 failed! Check out the build log: (web) (plain enhanced) (plain)

Click to see the possible cause of the failure (guessed by this bot)

---- [ui] tests/ui/macros/cfg_expr.rs stdout ----
Saved the actual stderr to `/checkout/obj/build/x86_64-unknown-linux-gnu/test/ui/macros/cfg_expr/cfg_expr.stderr`
normalized stderr:
warning: function `quux` is never used
##[warning]  --> $DIR/cfg_expr.rs:18:35
   |
LL | foo!(target_pointer_width = "64", quux);
   |                                   ^^^^
   |
   = note: `#[warn(dead_code)]` (part of `#[warn(unused)]`) on by default
   = note: this warning originates in the macro `foo` (in Nightly builds, run with -Z macro-backtrace for more info)

---
To only update this specific test, also pass `--test-args macros/cfg_expr.rs`

error: 1 errors occurred comparing output.
status: exit status: 0
command: env -u RUSTC_LOG_COLOR RUSTC_ICE="0" RUST_BACKTRACE="short" "/checkout/obj/build/x86_64-unknown-linux-gnu/stage2/bin/rustc" "/checkout/tests/ui/macros/cfg_expr.rs" "-Zthreads=1" "-Zsimulate-remapped-rust-src-base=/rustc/FAKE_PREFIX" "-Ztranslate-remapped-path-to-local-path=no" "-Z" "ignore-directory-in-diagnostics-source-blocks=/cargo" "-Z" "ignore-directory-in-diagnostics-source-blocks=/checkout/vendor" "--sysroot" "/checkout/obj/build/x86_64-unknown-linux-gnu/stage2" "--target=i686-unknown-linux-gnu" "--check-cfg" "cfg(test,FALSE)" "--error-format" "json" "--json" "future-incompat" "-Ccodegen-units=1" "-Zui-testing" "-Zdeduplicate-diagnostics=no" "-Zwrite-long-types-to-disk=no" "-Cstrip=debuginfo" "--emit" "metadata" "-C" "prefer-dynamic" "--out-dir" "/checkout/obj/build/x86_64-unknown-linux-gnu/test/ui/macros/cfg_expr" "-A" "internal_features" "-A" "unused_parens" "-A" "unused_braces" "-Crpath" "-Cdebuginfo=0" "-Lnative=/checkout/obj/build/i686-unknown-linux-gnu/native/rust-test-helpers" "-Clinker=x86_64-linux-gnu-gcc"
stdout: none
--- stderr -------------------------------
warning: function `quux` is never used
##[warning]  --> /checkout/tests/ui/macros/cfg_expr.rs:18:35
   |
LL | foo!(target_pointer_width = "64", quux);
   |                                   ^^^^
   |
   = note: `#[warn(dead_code)]` (part of `#[warn(unused)]`) on by default
   = note: this warning originates in the macro `foo` (in Nightly builds, run with -Z macro-backtrace for more info)

RalfJung Avatar
RalfJung commented on 2025-09-24 06:52:05 UTC
tests/ui/attributes/nonterminal-expansion.rs · outdated
15 14 }
16 15
17 16 pass_nonterminal!(n!());
17 //~^ ERROR expected one of `(`, `::`, or `=`, found `!`
RalfJung Avatar RalfJung on 2025-09-24 06:52:05 UTC
View on GitHub

This error is surprisingly different from what you get when "inlining" the macro?

error: expected unsuffixed literal, found `!`
 --> src/main.rs:6:15
  |
6 | #[repr(align(n!()))]
  |               ^

https://play.rust-lang.org/?version=stable&mode=debug&edition=2024&gist=979f349ee4be2e88b300d4f97f5e1db9

petrochenkov Avatar petrochenkov on 2025-09-26 17:37:53 UTC
View on GitHub

$expr effectively has parentheses around it, so the "inlined" version would look more like #[repr(align((n!())))].

petrochenkov Avatar
petrochenkov on 2025-09-26 18:13:51 UTC · edited
petrochenkov Avatar
petrochenkov on 2025-09-26 18:13:51 UTC · edited
View on GitHub

Just as an update, after #124141 we effectively no longer have nonterminal tokens in the grammar (*).
At parsing time a pasted expr metavariable is not some NtExpr token, it's just a token stream in invisible parentheses.
If that token stream from expr parses as a cfg predicate then it can be used as a cfg predicate, if it parses as a type or pattern, then it can be used as a pattern as well.
And on the other hand, if a token stream from some other matcher like ty or pat parses as a cfg predicate, then it can be used as a cfg predicate.
In general, the matcher kind is only relevant during macro LHS matching, not during RHS parsing (**).

I hoped that some time after #124141 either @nnethercote or me relax these rules in many cases, when there are no backward compatibility concerns at least, and pass the change through lang team, but it didn't happen yet.
(In particular, tokens from any matchers should be parse-able in expr, ty and pat positions, there should be no compatibility issues there.)

A number of current hacks like accepting impl $ty for Type, or what this PR suggests, are just special cases of that strategy.

@Jules-Bertholet You could very well start the same process, just from a different point and accept any token streams that look like cfg predicates as cfg predicates, from any matchers, not just expr.

(*) Or we technically have, but only for compatibility and to avoid extending the language without the lang team process.
(**) Again, unless there are backward compatibility issues.

petrochenkov Avatar
petrochenkov on 2025-09-26 18:21:08 UTC
petrochenkov Avatar
petrochenkov on 2025-09-26 18:21:08 UTC
View on GitHub

As for the current implementation, the new expr-accepting logic is used in parse_meta_item and the new attr parsing infra, so it seems more than just cfg predicates specifically, but I'm not sure what else exactly is affected.

joshtriplett Avatar
joshtriplett on 2025-10-08 15:39:09 UTC
joshtriplett Avatar
joshtriplett on 2025-10-08 15:39:09 UTC
View on GitHub

This seems reasonable to me. It allows all :expr fragments here, and not all of those will subsequently parse, but that seems okay.

@rfcbot merge

rust-rfcbot Avatar
rust-rfcbot on 2025-10-08 15:39:11 UTC · edited
rust-rfcbot Avatar
rust-rfcbot on 2025-10-08 15:39:11 UTC · edited
View on GitHub

Team member @joshtriplett has proposed to merge this. The next step is review by the rest of the tagged team members:

No concerns currently listed.

Once a majority of reviewers approve (and at most 2 approvals are outstanding), this will enter its final comment period. If you spot a major issue that hasn't been raised at any point in this process, please speak up!

cc @rust-lang/lang-advisors: FCP proposed for lang, please feel free to register concerns.
See this document for info about what commands tagged team members can give me.

traviscross Avatar
traviscross on 2025-10-08 15:40:58 UTC
traviscross Avatar
traviscross on 2025-10-08 15:40:58 UTC
View on GitHub

@rfcbot reviewed

tmandry Avatar
tmandry on 2025-10-08 15:43:49 UTC
tmandry Avatar
tmandry on 2025-10-08 15:43:49 UTC
View on GitHub

@rfcbot reviewed

rust-rfcbot Avatar
rust-rfcbot on 2025-10-08 15:43:56 UTC
rust-rfcbot Avatar
rust-rfcbot on 2025-10-08 15:43:56 UTC
View on GitHub

🔔 This is now entering its final comment period, as per the review above. 🔔

joshtriplett Avatar
joshtriplett on 2025-10-08 15:44:11 UTC
joshtriplett Avatar
joshtriplett on 2025-10-08 15:44:11 UTC
View on GitHub

As for the current implementation, the new expr-accepting logic is used in parse_meta_item and the new attr parsing infra, so it seems more than just cfg predicates specifically, but I'm not sure what else exactly is affected.

Happy to see a more general version of this, if we can.

nikomatsakis Avatar
nikomatsakis on 2025-10-08 15:44:26 UTC
nikomatsakis Avatar
nikomatsakis on 2025-10-08 15:44:26 UTC
View on GitHub

@rfcbot reviewed

I am in favor of making the "special tokens" and invisible delimiters less visible to the user.

rust-rfcbot Avatar
rust-rfcbot on 2025-10-18 15:47:05 UTC
rust-rfcbot Avatar
rust-rfcbot on 2025-10-18 15:47:05 UTC
View on GitHub

The final comment period, with a disposition to merge, as per the review above, is now complete.

As the automated representative of the governance process, I would like to thank the author for their work and everyone else who contributed.

This will be merged soon.

fee1-dead Avatar
fee1-dead approved on 2025-11-14 19:51:13 UTC
fee1-dead left a comment · edited
View on GitHub

Procedural note, this has been marked as S-waiting-on-t-lang but technically should be S-waiting-on-review.

I reviewed the code and think this would be good to merge, unless @petrochenkov has any opinions.

View changes since this review

petrochenkov Avatar
petrochenkov on 2025-11-14 20:26:15 UTC
petrochenkov Avatar
petrochenkov on 2025-11-14 20:26:15 UTC
View on GitHub

As for the current implementation, the new expr-accepting logic is used in parse_meta_item and the new attr parsing infra, so it seems more than just cfg predicates specifically, but I'm not sure what else exactly is affected.

During the first review it was not clear to me what this PR actually implements ^^^.
But I'll look again.

❤️1
petrochenkov Avatar
petrochenkov on 2025-11-17 11:22:37 UTC
petrochenkov Avatar
petrochenkov on 2025-11-17 11:22:37 UTC
View on GitHub

As for the current implementation, the new expr-accepting logic is used in parse_meta_item and the new attr parsing infra, so it seems more than just cfg predicates specifically, but I'm not sure what else exactly is affected.

This concern still stands.
This PR doesn't just support expr in cfg predicates like the PR description says, it supports it in wide range of contexts using parse_meta_item from the regular parsing infra or parse_attr_item from the Dutch parsing infra.
From the lang team comments above it's not clear whether it's what was approved or not.

The PR either need to be changed to implement what it describes (support for expr in cfg predicates), or document what it actually implements instead and ensure the lang team is ok with it.
@rustbot author

rustbot Avatar
rustbot on 2025-11-17 11:22:41 UTC
rustbot Avatar
rustbot on 2025-11-17 11:22:41 UTC
View on GitHub

Reminder, once the PR becomes ready for a review, use @rustbot ready.

bors Avatar
bors on 2025-12-10 02:16:06 UTC · hidden as RESOLVED
bors Avatar
bors on 2025-12-10 02:16:06 UTC · hidden as RESOLVED
View on GitHub

☔ The latest upstream changes (presumably #149818) made this pull request unmergeable. Please resolve the merge conflicts.

rustbot Avatar
rustbot on 2026-03-15 02:27:06 UTC
rustbot Avatar
rustbot on 2026-03-15 02:27:06 UTC
View on GitHub

The parser was modified, potentially altering the grammar of (stable) Rust
which would be a breaking change.

cc @fmease

rustbot Avatar
rustbot on 2026-03-15 02:27:08 UTC · hidden as outdated
rustbot Avatar
rustbot on 2026-03-15 02:27:08 UTC · hidden as outdated
View on GitHub

This PR was rebased onto a different main commit. Here's a range-diff highlighting what actually changed.

Rebasing is a normal part of keeping PRs up to date, so no action is needed—this note is just to help reviewers.

rustbot Avatar
rustbot on 2026-03-15 17:44:28 UTC
rustbot Avatar
rustbot on 2026-03-15 17:44:28 UTC
View on GitHub

This PR was rebased onto a different main commit. Here's a range-diff highlighting what actually changed.

Rebasing is a normal part of keeping PRs up to date, so no action is needed—this note is just to help reviewers.

Jules-Bertholet Avatar
Jules-Bertholet on 2026-03-15 17:46:20 UTC
Jules-Bertholet Avatar
Jules-Bertholet on 2026-03-15 17:46:20 UTC
View on GitHub

@rustbot ready

This should now be confined to affecting cfg predicates only.

petrochenkov Avatar
petrochenkov on 2026-03-16 12:18:10 UTC
petrochenkov Avatar
petrochenkov on 2026-03-16 12:18:10 UTC
View on GitHub

r? @JonathanBrouwer
Could you double check this? The new attribute parsing infra is impenetrable.
The expr fragments are now supposed to parsed in cfg predicates, and only in cfg predicates.

(Why does it all converge to parse_attr_item? cfg predicates are not attr items at all...)

JonathanBrouwer Avatar
JonathanBrouwer approved on 2026-03-20 11:43:40 UTC
compiler/rustc_attr_parsing/src/attributes/cfg.rs · outdated
363 363 let meta = MetaItemOrLitParser::parse_single(
364 364 parser,
365 365 ShouldEmit::ErrorsAndLints { recovery: Recovery::Allowed },
366 true,
JonathanBrouwer Avatar JonathanBrouwer on 2026-03-20 11:26:21 UTC
View on GitHub

nit: Could you make an explicit enum AllowExprMetavar (or sth) so it is more apparent what this true/false argument is referring to?

👍1
compiler/rustc_attr_parsing/src/parser.rs · outdated
448 458 }
449 459
450 460 fn parse_attr_item(&mut self) -> PResult<'sess, MetaItemParser> {
JonathanBrouwer Avatar JonathanBrouwer on 2026-03-20 11:32:48 UTC
View on GitHub

Yeah this function should actually be called parse_meta_item, I'm not sure why it is called this way, was probably a mistake I made while porting this code.
cfg predicates are meta items, so that would make sense.
Could you rename this?

tests/ui/macros/cfg_attr-expr.rs · resolved
1 macro_rules! foo {
2 ($e:expr) => {
3 #[cfg_attr(true, $e)]
JonathanBrouwer Avatar JonathanBrouwer on 2026-03-20 11:43:15 UTC
View on GitHub

Could you add a similar testcase with #[inline($e)] and passing foo!(always)?

Jules-Bertholet Avatar
Jules-Bertholet on 2026-03-20 22:50:00 UTC
Jules-Bertholet Avatar
Jules-Bertholet on 2026-03-20 22:50:00 UTC
View on GitHub

@rustbot ready

JonathanBrouwer Avatar
JonathanBrouwer approved on 2026-03-20 23:07:46 UTC
rust-bors Avatar
rust-bors on 2026-03-20 23:07:49 UTC
rust-bors Avatar
rust-bors on 2026-03-20 23:07:49 UTC
View on GitHub

📌 Commit ab36d50 has been approved by JonathanBrouwer

It is now in the queue for this repository.