Skip to content

Parser: Uninterpolate when checking for const closures, try bikeshed blocks & in relevant diagnostic code - #162704

Open
fmease wants to merge 3 commits into
rust-lang:mainfrom
fmease:uninterpolate-more
Open

Parser: Uninterpolate when checking for const closures, try bikeshed blocks & in relevant diagnostic code#162704
fmease wants to merge 3 commits into
rust-lang:mainfrom
fmease:uninterpolate-more

Conversation

@fmease

@fmease fmease commented Sep 12, 2026

Copy link
Copy Markdown
Member

On main, various places in the parser manually check for TokenKind::Ident(…) while in most cases that's not correct / complete as it doesn't account for interpolated identifiers (TokenKind::NtIdent(…), "non-terminal" identifiers, i.e., ones that originate from macro metavariables $ident).

I've looked at all occurrences of TokenKind::Ident & token::Ident in the parser to see if each place was intentionally ignoring NtIdent or not. I found out that the code pertaining to const closures (unstable feature const_closures), to try bikeshed blocks (unstable feature try_blocks_heterogeneous) and to some diagnostics & parse error recoveries did not intend to ignore interpolated identifiers. This PR rectifies that.

Note that in most cases, it's not necessary to manually match against NtIdent as you can simply utilize helpers like Token::uninterpolate, Token::ident, Token::is_keyword, Token::is_non_raw_ident which handle them internally.

I also took the chance to clean up in each vicinity.

(No LLM was or will be used by me during the entire creation process of this PR)

This makes code like this compile:

    #![feature(try_blocks_heterogeneous)]
    macro_rules! make { ($kw:ident) => { try $kw Option<()> {} } }
    fn scope() { make!(bikeshed); }
@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 Sep 12, 2026
@rustbot

rustbot commented Sep 12, 2026

Copy link
Copy Markdown
Collaborator

r? @adwinwhite

rustbot has assigned @adwinwhite.
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

Why was this reviewer chosen?

The reviewer was selected based on:

  • Owners of files modified in this PR: compiler, parser
  • compiler, parser expanded to 76 candidates
  • Random selection from 20 candidates

// Let's check if the previous token could denote the start of an item
// whose kind can have generics.
if let Some((Ident { name, .. }, IdentIsRaw::No)) = self.prev_token.ident()
&& let kw::Fn | kw::Type | kw::Struct | kw::Enum | kw::Union | kw::Trait = name

@fmease fmease Sep 12, 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.

Makes us now also emit the suggestion for code like below:

macro_rules! make { ($kw:ident) => { $kw<T> f() {} } }
make!(fn);
Compiler Outputerror: expected identifier, found `<` --> t.rs:12:41 | 12 | macro_rules! make { ($kw:ident) => { $kw<T> f() {} } } | ^ expected identifier 13 | make!(fn); | --------- in this macro invocation | = note: this error originates in the macro `make` (in Nightly builds, run with -Z macro-backtrace for more info) help: place the generic parameter name after the fn name | 12 - macro_rules! make { ($kw:ident) => { $kw<T> f() {} } } 12 + macro_rules! make { ($kw:ident) => { $kw f<T>() {} } }

View changes since the review

Applicability::MachineApplicable,
);
}
if let Some((ident, IdentIsRaw::No)) = self.prev_token.ident()

@fmease fmease Sep 12, 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.

Apart from the "interpolated identifier" business, this now makes this code aware of r#: It will no longer show the suggestion for code like r#function f() {} as the user has clearly requested function not to act as a keyword.

View changes since the review

Applicability::MachineApplicable,
);
}
if let Some((ident, IdentIsRaw::No)) = self.prev_token.ident()

@fmease fmease Sep 12, 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.

Makes us now also emit the suggestion for code like below:

macro_rules! make { ($kw:ident) => { $kw f() {} } }
make!(function);
Compiler Output
error: expected one of `!` or `::`, found `f`
 --> t.rs:4:42
  |
4 | macro_rules! make { ($kw:ident) => { $kw f() {} } }
  |                                      --- ^ expected one of `!` or `::`
  |                                      |
  |                                      help: write `fn` instead of `function` to declare a function
5 | make!(function);
  | --------------- in this macro invocation
  |
  = note: this error originates in the macro `make` (in Nightly builds, run with -Z macro-backtrace for more info)

View changes since the review

if let TokenKind::Ident(_, _) = prev_token.kind {
e.span_suggestion_verbose(
self.expect(exp!(CloseBracket)).map_err(|mut err| {
if prev_token.is_non_reserved_ident() {

@fmease fmease Sep 12, 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.

Makes us now also emit the suggestion for code like below:

macro_rules! make { ($ident:ident) => { $ident[0;] } }
fn scope() { make!(ident); }
Compiler Output
error: expected one of `.`, `?`, `]`, or an operator, found `;`
  --> t.rs:20:49
   |
20 | macro_rules! make { ($ident:ident) => { $ident[0;] } }
   |                                                 ^ expected one of `.`, `?`, `]`, or an operator
21 | fn scope() { make!(ident); }
   |              ------------ in this macro invocation
   |
   = note: this error originates in the macro `make` (in Nightly builds, run with -Z macro-backtrace for more info)
help: you might have meant to call a macro
   |
20 | macro_rules! make { ($ident:ident) => { $ident![0;] } }
   |                                               +

View changes since the review

*t == token::OpenBrace
|| t.is_metavar_block()
|| t.kind == TokenKind::Ident(sym::bikeshed, IdentIsRaw::No)
*t == token::OpenBrace || t.is_metavar_block() || t.is_keyword(sym::bikeshed)

@fmease fmease Sep 12, 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.

As the commit message already states this makes us accept code like:

#![feature(try_blocks_heterogeneous)]
macro_rules! make { ($kw:ident) => { try $kw Option<()> {} } }
fn scope() { make!(bikeshed); }

Previously, we would error out with expected expression, found reserved keyword `try`.

View changes since the review

})
.map(|(ident, _)| ident);
.non_reserved_ident()
.filter(|_| self.look_ahead(1, |&tok| tok == token::Colon));

@fmease fmease Sep 12, 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.

Drive-by cleanup. Taken straight from PR #161775 which incidentally also introduces Token::non_reserved_ident (it's just so useful! :D)

View changes since the review

self.is_keyword_ahead(0, &[kw::Const])
&& self.look_ahead(1, |t| match &t.kind {
// async closures do not work with const closures, so we do not parse that here.
&& self.look_ahead(1, |t| match t.uninterpolate().kind {

@fmease fmease Sep 12, 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.

See the added UI test for what this makes us accept.

View changes since the review

t.is_metavar_block()
|| t.kind == token::OpenBrace
|| t.is_non_raw_ident_where(|ident| {
matches!(ident.name, kw::For | kw::Loop | kw::While)

@fmease fmease Sep 12, 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.

Makes us now also emit this customized diagnostic plus its suggestion for code like:

macro_rules! make { ($kw:ident) => { label: $kw {} } }
fn scope() { make!(loop); }
Compiler Output
error: malformed loop label
 --> t.rs:8:38
  |
8 | macro_rules! make { ($kw:ident) => { label: $kw {} } }
  |                                      ^^^^^
9 | fn scope() { make!(loop); }
  |              ----------- in this macro invocation
  |
  = note: this error originates in the macro `make` (in Nightly builds, run with -Z macro-backtrace for more info)
help: use the correct loop label format
  |
8 | macro_rules! make { ($kw:ident) => { 'label: $kw {} } }
  |                                      +

warning: unused label
 --> t.rs:8:38
  |
8 | macro_rules! make { ($kw:ident) => { label: $kw {} } }
  |                                      ^^^^^
9 | fn scope() { make!(loop); }
  |              ----------- in this macro invocation
  |
  = note: `#[warn(unused_labels)]` (part of `#[warn(unused)]`) on by default
  = note: this warning originates in the macro `make` (in Nightly builds, run with -Z macro-backtrace for more info)

View changes since the review

}
})
// If we encounter a type like `impl 'a Sized`, suggest `impl 'a + Sized`.
if self.token.is_lifetime()

@fmease fmease Sep 12, 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.

Apart from the "interpolated identifier" business this makes the diagnostic aware of keywords. Previously, it would also trigger on inputs like impl 'a if.

View changes since the review

}
})
// If we encounter a type like `impl 'a Sized`, suggest `impl 'a + Sized`.
if self.token.is_lifetime()

@fmease fmease Sep 12, 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.

Makes us now also emit this custom diagnostic for code like:

macro_rules! make { ($ident:ident) => { impl 'static $ident; } }
fn scope() -> make!(Trait) {}
Compiler Output (Excerpt)
error: expected `+` between lifetime and Trait
  --> t.rs:16:46
   |
16 | macro_rules! make { ($ident:ident) => { impl 'static $ident; } }
   |                                              ^^^^^^^
17 | fn scope() -> make!(Trait) {}
   |               ------------ in this macro invocation
   |
   = note: this error originates in the macro `make` (in Nightly builds, run with -Z macro-backtrace for more info)
help: add `+`
   |
16 | macro_rules! make { ($ident:ident) => { impl 'static + $ident; } }
   |                                                      +

View changes since the review

@fmease

fmease commented Sep 12, 2026

Copy link
Copy Markdown
Member Author

Note that I won't be adding any of these code snippets as UI tests because that would be disproportionate. I hope that's understandable :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

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.

3 participants