Skip to content

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

Open
jieyouxu wants to merge 5 commits into
rust-lang:mainfrom
jieyouxu:jieyouxu/cfg-select-parser-recovery
Open

jieyouxu wants to merge 5 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, causing us to incorrectly give up formatting on a program consisting of cfg_select! with valid body followed by cfg_select! with invalid body.

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

This PR is structured to 5 commits:

  • Commit 1 pulls out some basic test harness improvements to allow running rustfmt_with_extra with stdin, so that I can run rustfmt without needing to use tempdir/tempfiles unnecessarily.
  • Commits {2, 3} are for bug 1. Commit 2 is the "pre"-fix test that fails on latest main (i.e. ICEs). Commit 3 is the fix where the test now passes.
  • Commits {4, 5} are for bug 2. Likewise, Commit 4 is the "pre"-fix test that demonstrates the unexpected giving up of formatting. Commit 5 fixes the error count leak, which causes us to now properly format the cfg_select! instance with valid body.

I suggest the reviewer checkout commits 2 and 4 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.)

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.
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).

We also reset error counts to avoid tainting other parsing sessions
(just in case).

ICE reported in <rust-lang#7087>.
…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.
Since error count is a parser session global state, this was
accidentally leaking previously.

This causes weird behavior: 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 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 on lines +177 to +181
if parser.psess.dcx().has_errors().is_some() {
parser.psess.dcx().reset_err_count();
debug!("cfg entry parsed with recovery in cfg_select! predicate");
return None;
}

@jieyouxu jieyouxu Oct 10, 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.

Remark: yes, I am fully aware this is fragile and basically setting us up to get wrong (other similar macro formatting also need to do this).

This in my opinion is more fundamental to the fact that we reuse rustc's AST and parser. rustc's parser, being positioned as a "compiler with good diagnostics and user experience", attempts a lot of recoveries to give good suggestions. The downside of the good experience is that it necessarily requires more impl complexity, and for rustc's parser it means using error counts as out-of-band bookkeeping and not just via parser Result<..> return values.1

That works reasonable well for rustc. Not so fortunate for rustfmt since rustfmt (well, us) need to also account for parser recoveries even if we "don't actually care" as an AST pretty-printer.

View changes since the review

Footnotes

  1. Overall, I still think that the upsides of sharing the rustc AST / parser significantly outweighs the downsides considering our available bandwidth and resources. ↩

@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.

This branch has not been deployed

No deployments
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. S-waiting-on-review Status: awaiting review from the assignee but also interested parties. X-stability-guarantee-exempt Feature: impacts stable, but explicitly exempt from format stability guarantees. See README

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

2 participants