Skip to content

Preparatory changes for macro parsing BFS->DFS#158974

Open
bal-e wants to merge 14 commits into
rust-lang:mainfrom
bal-e:macro-parsing-dfs-prep
Open

Preparatory changes for macro parsing BFS->DFS#158974
bal-e wants to merge 14 commits into
rust-lang:mainfrom
bal-e:macro-parsing-dfs-prep

Conversation

@bal-e

@bal-e bal-e commented Jul 8, 2026

Copy link
Copy Markdown

This PR contains some miscellaneous commits I accumulated while working on the BFS->DFS change. Their goal is to simplify the code and clarifying existing behavior. It is a conceptual follow-up to #158577. High-level overview:

  • Adds context about the current match arm to Tracker through the new Tracker::prepare(), so that the WhichMatcher parameter is available implicitly. In a later PR, this will be used to reference MatcherLocs by index.

  • Reformulates matching failures to work more like ambiguity errors wrt. Tracker; Tracker::build_failure() (which would build a failure consumed by Tracker::after_arm()) becomes Tracker::failure() which eagerly processes the error. This removes the need for ParseResult::Failure to store any data at all. This relies on the match arm context provided by Tracker::prepare().

  • There is a subtle edge case involving token::Eof and non-terminal parsing; Parser::nonterminal_may_begin_with() would sometimes return true for token::Eof, even though the non-terminal parse would never be attempted. TtParser did not check bb_mps when handling token::Eof, so non-terminal parses at EOF were being silently dropped. I changed Parser::nonterminal_may_begin_with() to always return false for token::Eof, making this behavior much more visible. I added a test case to make sure meta-variables return the same error uniformly when parsed against EOF.

Contains #158894, which should be merged soon (after which I'll rebase onto main).

Best reviewed commit-by-commit.

r? @nnethercote

arya dradjica added 14 commits July 3, 2026 09:29
None of the existing tests captured a case where non-terminal binds
appeared differently ordered than their definition in their rule, so I
wrote a test for it.

The existing macro parsing code is quite resilient to this kind of
mis-ordering; it took quite a convoluted macro to trigger a case where
the ordering is incorrect today. I've documented the parse tree (based
on my own mental model) that the convoluted macro causes.
The order of `bb_mps` and `next_mps` depends on arbitrary implementation
choices, e.g. the order in which `$(a)? b` causes `a b` and `b` to be
explored. To stop depending on these implementation details, this commit
sorts `bb_mps` and `next_mps` by `mp.idx` (corresponding to the position
in the rule) before presenting error messages.

This makes it easier to refactor the macro parsing implementation.
In the next commit, the work done in `Tracker::after_arm()` upon a
`Failure` will be moved into `Tracker::build_failure()`. For this
to work, the `WhichMatcher` parameter to `after_arm()` needs to be
available to `build_failure()`. Rather than threading it through
`TtParser`, this commit stores it in `CollectTrackerAndEmitter` and
keeps it updated using a new `Tracker::prepare()` method.

`tests/ui/macros` pass.
Rather than preparing a failure using `Tracker::build_failure()`
and consuming it from `Tracker::after_arm()`, the parser now calls
`Tracker::failure()` the moment a failure is detected, and this will
eagerly perform all the necessary processing.

This makes the contents of `Failure` redundant, so they can be removed
in the next commit.
Now that everything is processed in `Tracker::failure()`, this data does
not need to be propagated. This also removes some generics.
The appropriate token (span) and the diagnostic message are now computed
in `diagnostics.rs`. Following this commit, _all_ diagnostic messages
have been moved from `macro_parser.rs` to `diagnostics.rs`. Yay!
It is being used by `Parser::nonterminal_may_begin_with()`.
This commit deliberately causes panics. See the next commit for more
details and a fix.
Until now, `:tt`, `:item`, and `:stmt` metavars could be
attempted against `token::Eof`. *HOWEVER*, when a `token::Eof` is
processed, `parse_tt_inner()` ignores `bb_mps`, not even checking
for ambiguity. It's as if those `bb_mps` never existed; as if
`nonterminal_may_begin_with()` returned `false` for `Eof`. This commit
makes that behavior more explicit.

This does not change user visible behavior.

`tests/ui/macros` pass.
The previous commit fixed a case where some meta-variables were being
treated as if they *could* match `token::Eof` and then silently ignored.
This commit adds a test to ensure meta-variables continue to have a
consistent response to an `Eof` (in theory, they could vary between
fatal non-terminal parsing errors and non-fatal matching failures).
This makes it easier to change the matching logic and to use the same
logic from multiple places.
@rustbot

rustbot commented Jul 8, 2026

Copy link
Copy Markdown
Collaborator

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

cc @fmease

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. labels Jul 8, 2026
@rustbot

rustbot commented Jul 8, 2026

Copy link
Copy Markdown
Collaborator

Thanks for the pull request, and welcome! The Rust Project is excited to review your changes, and you should hear from @nnethercote (or someone else) some time within the next two weeks.

Please see the contribution instructions for more information. Namely, in order to ensure the minimum review times lag, PR authors and assigned reviewers should ensure that the review label (S-waiting-on-review and S-waiting-on-author) stays updated, invoking these commands when appropriate:

  • @rustbot author: the review is finished, PR author should check the comments and take action accordingly
  • @rustbot review: the author is ready for a review, this PR will be queued again in the reviewer's queue

@petrochenkov

Copy link
Copy Markdown
Contributor

while working on the BFS->DFS change

Could you tell more, what is the BFS->DFS change and its motivation and goals?

@petrochenkov petrochenkov removed their assignment Jul 9, 2026
@bal-e

bal-e commented Jul 9, 2026

Copy link
Copy Markdown
Author

Hey @petrochenkov! I initially got interested in the macro parser when I learned about degenerate cases causing exponential-time (and exponential-space) complexity here. I wrote a blog post about it with more details: https://bal-e.org/blog/2026/oops-cubic-macro/. In short, the macro parser explores ambiguity using a BFS, but it only cares about one result (if there are other results, it only cares about their existence, not the data within; at least on the fast path). A DFS feels simpler here:

  1. It makes it possible to nicely handle the degenerate cases (first by reducing exponential-space usage into linear-space, then making it easy to deduplicate MatcherPos-es to get linear runtime). This is not super important because the degenerate cases rarely occur in practice.

  2. I think it would be faster. With a DFS, you can avoid pushing to and popping off the mps vecs; most of the time you just need a single mp in a local variable and work on that. I suspect LLVM would also have a better time optimizing the control flow, but that's hard to prove.

  3. It makes it possible to share the NamedMatches data between MatcherPos-es. I think it is possible to eliminate the Rcs involved, and later I also want to flatten the data structures to avoid nested heap allocations. This is the biggest motivation.

I'm quite confident the macro parser can be changed to DFS without affecting user behavior (#158894 added a user-visible change, but only for a minor detail in error messages). In any case, I'll write about it more when I get to the main PR. I think these preparatory PRs are worth merging independently of the BFS->DFS change. Happy to answer in more detail too :)

@jdonszelmann

Copy link
Copy Markdown
Contributor

@bors try @rust-timer queue

@rust-timer

Copy link
Copy Markdown
Collaborator

Awaiting bors try build completion.

@rustbot label: +S-waiting-on-perf

@rustbot rustbot added the S-waiting-on-perf Status: Waiting on a perf run to be completed. label Jul 9, 2026
@rust-bors

rust-bors Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

⌛ Trying commit 3041d62 with merge 71875d2

To cancel the try build, run the command @bors try cancel.

Workflow: https://github.com/rust-lang/rust/actions/runs/29032045657

rust-bors Bot pushed a commit that referenced this pull request Jul 9, 2026
Preparatory changes for macro parsing BFS->DFS
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

S-waiting-on-perf Status: Waiting on a perf run to be completed. S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants