Skip to content

Fix cfg_select! parser recovery handling and parser session error count leak - #7171

Merged
ytmimi merged 4 commits into
rust-lang:mainfrom
jieyouxu:jieyouxu/cfg-select-parser-recovery
Oct 15, 2026
Merged

ytmimi merged 4 commits into
rust-lang:mainfrom
jieyouxu:jieyouxu/cfg-select-parser-recovery

Conversation

@jieyouxu

@jieyouxu jieyouxu commented Oct 10, 2026 •

Copy link
Copy Markdown
Member

Overview

This PR fixes two bugs:

  1. Incorrect parser recovery handling in cfg_select! parsing which cause us to ICE, cf. [ICE]: bad span: ): ; cfg_select! invalid arm expr "successfully" formats but produces an ICE report #7087, and
  2. A closely related bug in cfg_select! parsing where we unintentionally leak parser session error count. This can unintentionally inhibit formatting of another eligible cfg_select! invocation.

Fixes #7087.

Bug 1: incorrect parser recovery handling

Reported in #7087. rustc's parser can produce recoveries in the form of an Ok(expr) (and other non-terminals) alongside diagnostics even if the expr is technically invalid. One such case is an function item that forgot the argument list parentheses, i.e. fn foo {}. That mistake pattern is common enough that the parser has recovery for it. Indeed,

Once we've got what we think is a valid block expr we go through our regular formatting flow and when we reach fn foo {} things fail because rustfmt (IMO rightfully) assumes that it will always be able to find an opening ( and closing )

-- @ytmimi's comment

To properly handle this, we check for actual parser errors and bail if there are parser errors (i.e. we re-emit the original macro invocation unchanged). We also reset error counts to avoid tainting other parsing calls.

Bug 2: parser session error count leak

We use the same rustc parser session when trying to parse multiple cfg_select! in the same file. The error count is shared state on said parser session. We don't properly reset the error count when we encounter an recovery in parse_items_from_cfg_select_inner which causes examples like

cfg_select! {
    unix   =>   {   fn   bar() {} }
}

cfg_select! { unix => { fn foo {} } }

to give up (note first instance is valid body, second is invalid body) and not format the first instance.

To fix this, we need to properly reset error counts upon encountering potential recoveries.

Notes on testing strategy

  • For bug 1, we can't use simple {source, target} test pair since it would vacuously pass as there's no ICE hook that would cause the test to fail (see analysis). I have to use a binary test here.
  • For bug 2, there we can for the error count leak, since the target file goes from give up formatting => formatted.

Notes for the reviewer

I suggest the reviewer checkout the test commits specifically and observe outcome locally.

Changelog

Fix `cfg_select!` parser recovery handling and parser session error count leak

  • I did not use an LLM to create a change in this PR.
  • I used an LLM to create a change in this PR, and I have explained below how it was used.
  • The core test and impl for fixing [ICE]: bad span: ): ; cfg_select! invalid arm expr "successfully" formats but produces an ICE report #7087 was authored by me. I used LLM to help review my changes, since the parser recovery paths has some hard to notice edge cases.
  • During said review, the LLM found that my fix was sufficient for the single cfg_select!-with-invalid-item case, but also found that parse_items_from_cfg_select_inner has a slightly different parser session error count leak bug. I didn't notice this during the core fix, but indeed I double-checked this by hand (in the form of the test) and confirm that there is indeed a bug here. (I elaborate on the analysis earlier.)
  • Actual code changes I hand-wrote.
  • All communications and analysis are hand-written. (Sorry, I know this is a bit long, but I rather the analysis exists somewhere.)

@jieyouxu jieyouxu added the llm-assisted An LLM-assisted PR as defined by the LLM policy. Requires ahead-of-time consent by assignee. label Oct 10, 2026
@rustbot rustbot added the S-waiting-on-review Status: awaiting review from the assignee but also interested parties. label Oct 10, 2026
Comment thread src/parse/macros/cfg_select.rs Outdated
@jieyouxu jieyouxu added A-cfg_select `cfg_select!` A-macros Area: macros (procedural macros, macro_rules! macros, etc.) A-parser Area: parser X-stability-guarantee-exempt Feature: impacts stable, but explicitly exempt from format stability guarantees. See README labels Oct 10, 2026
@jieyouxu

jieyouxu commented Oct 10, 2026 •

Copy link
Copy Markdown
Member Author

Note that cfg_select! formatting is still gated on nightly-only, so the fix for Bug 2 doesn't impact stable formatting.

@matthewhughes934 matthewhughes934 left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's my first time looking over this macro parsing code, so had some questions to help build my understanding

View changes since this review

Comment thread tests/rustfmt/main.rs
Comment thread src/parse/macros/cfg_select.rs Outdated

if parser.psess.dcx().has_errors().is_some() {
parser.psess.dcx().reset_err_count();
debug!("cfg entry parsed with recovery in cfg_select! predicate");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could/should we propagate these parser errors? So the user knows we gave up because of invalid code

Conversely: do we ever want to not reset the error count like this? In which case could we just push it up a level

@jieyouxu jieyouxu Oct 13, 2026 •

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could/should we propagate these parser errors? So the user knows we gave up because of invalid code

It depends. The problem is that, just because say a cfg_select! arm body is not "valid syntax" outside of macro context, it doesn't mean the overall macro itself is invalid syntax. Consider precisely

cfg_select! { unix => { fn foo {} } }

fn foo {} is not valid syntax outside of macro contexts, but the overall cfg_select! is valid syntax. (If that makes sense.)

I don't think it makes sense to emit the give up either, not even under show_parse_errors=true, because macro formatting is essentially conditioned on a set of heuristics of "does this look like non-macro context rust code". That typically involves us synthetically invoking parts of rustc's parser. Unless under some hypothetical debug_why_rustfmt_gives_up_on_macros=true, then maybe.

Conversely: do we ever want to not reset the error count like this? In which case could we just push it up a level

Probably. I'll check.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I pushed the error count up a level.

I'll probably look into a less footgun-ish approach in a follow-up #7180.

Comment thread tests/source/cfg-select-valid-then-invalid-item-error-count-leak.rs
@jieyouxu

Copy link
Copy Markdown
Member Author

@rustbot author

@rustbot rustbot added S-waiting-on-author Status: awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-review Status: awaiting review from the assignee but also interested parties. labels Oct 13, 2026
Since I didn't want to use a tempdir/tempfile just for piping a simple
source file.
This ICEs on latest main.

This has to be a binary test not the usual {source, target} test, since
the latter doesn't have an installed ICE hook and the test would just
vacuously pass.
@jieyouxu jieyouxu added X-impacts-nightly-gated Expected formatting impact: only nightly gated formatting and removed X-stability-guarantee-exempt Feature: impacts stable, but explicitly exempt from format stability guarantees. See README labels Oct 15, 2026
…nt leak

Since rustc's parser session state is global. We don't reset it properly
in `parse_items_from_cfg_select_inner`, so an valid `cfg_select!`
followed by an invalid `cfg_select!` can inhibit formatting.
rustc's parser can produce an `Ok(expr)` with recoveries in the form of
diagnostics even if the `expr` is technically invalid. One such case is
an fn item that forgot the arg list parentheses, i.e. `fn foo {}`.

To properly handle this, we check for actual parser errors and abort if
there are parser errors (i.e. we re-emit the original macro invocation
unchanged).

ICE reported in <rust-lang#7087>.

We also reset error counts to avoid tainting other parsing sessions.

Since error count is a parser session global state, this was
accidentally leaking previously. For example, if you have a
`cfg_select!` containing valid item syntax followed by an `cfg_select!`
containing invalid item syntax, the invalid `cfg_select!` instance
unintentionally inhibits the formatting of the earlier valid one.

So fix this by properly resetting error counts.
@jieyouxu
jieyouxu force-pushed the jieyouxu/cfg-select-parser-recovery branch from f2a59a1 to 14c3546 Compare October 15, 2026 06:04
@rustbot

rustbot commented Oct 15, 2026

Copy link
Copy Markdown
Collaborator

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.

Comment on lines +20 to +21
// NB: parser recovery can return `Ok(expr)` (or other non-terminals) and still emit errors. We need
// to properly reset the shared error count.

@jieyouxu jieyouxu Oct 15, 2026 •

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: I believe cfg_if! also has this error count leak bug, cf. #7184, I'll look into that later.

View changes since the review

@jieyouxu

jieyouxu commented Oct 15, 2026 •

Copy link
Copy Markdown
Member Author

Changes since last review:

  • Pushed the error count book keeping up a level. Note that I also need to the panic-handling in the cfg arms parsing.
  • Folded the 2 fix commits into one.

r? matthewhughes934 (since you're already reviewing)

@rustbot review

@rustbot rustbot added S-waiting-on-review Status: awaiting review from the assignee but also interested parties. and removed S-waiting-on-author Status: awaiting some action (such as code changes or more information) from the author. labels Oct 15, 2026
@jieyouxu

jieyouxu commented Oct 15, 2026 •

Copy link
Copy Markdown
Member Author

(I know there's a bit of duplicate code for the panic handling, I'll probably pull out a helper when I look into cfg_if! as well.)

@rustbot rustbot added S-waiting-on-author Status: awaiting some action (such as code changes or more information) from the author. and removed S-waiting-on-review Status: awaiting review from the assignee but also interested parties. labels Oct 15, 2026
@jieyouxu jieyouxu added S-waiting-on-review Status: awaiting review from the assignee but also interested parties. and removed S-waiting-on-author Status: awaiting some action (such as code changes or more information) from the author. labels Oct 15, 2026

@ytmimi ytmimi left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you! I'm also in favor of extracting the panic handling code into a helper, but that can be done in a follow up!

View changes since this review

@ytmimi
ytmimi added this pull request to the merge queue Oct 15, 2026
Merged via the queue into rust-lang:main with commit ea05685 Oct 15, 2026
33 checks passed
@rustbot rustbot added release-notes Needs an associated changelog entry and removed S-waiting-on-review Status: awaiting review from the assignee but also interested parties. labels Oct 15, 2026
@jieyouxu
jieyouxu deleted the jieyouxu/cfg-select-parser-recovery branch October 15, 2026 23:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-cfg_select `cfg_select!` A-macros Area: macros (procedural macros, macro_rules! macros, etc.) A-parser Area: parser llm-assisted An LLM-assisted PR as defined by the LLM policy. Requires ahead-of-time consent by assignee. release-notes Needs an associated changelog entry X-impacts-nightly-gated Expected formatting impact: only nightly gated formatting

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[ICE]: bad span: ): ; cfg_select! invalid arm expr "successfully" formats but produces an ICE report

4 participants