Parser: Uninterpolate when checking for const closures, try bikeshed blocks & in relevant diagnostic code - #162704
Parser: Uninterpolate when checking for const closures, try bikeshed blocks & in relevant diagnostic code#162704fmease wants to merge 3 commits into
Conversation
This makes code like this compile:
#![feature(try_blocks_heterogeneous)]
macro_rules! make { ($kw:ident) => { try $kw Option<()> {} } }
fn scope() { make!(bikeshed); }
|
r? @adwinwhite rustbot has assigned @adwinwhite. Use Why was this reviewer chosen?The reviewer was selected based on:
|
| // 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 |
There was a problem hiding this comment.
Makes us now also emit the suggestion for code like below:
macro_rules! make { ($kw:ident) => { $kw<T> f() {} } }
make!(fn);Compiler Output
error: 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>() {} } }
| Applicability::MachineApplicable, | ||
| ); | ||
| } | ||
| if let Some((ident, IdentIsRaw::No)) = self.prev_token.ident() |
There was a problem hiding this comment.
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.
| Applicability::MachineApplicable, | ||
| ); | ||
| } | ||
| if let Some((ident, IdentIsRaw::No)) = self.prev_token.ident() |
There was a problem hiding this comment.
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)
| 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() { |
There was a problem hiding this comment.
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;] } }
| +
| *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) |
There was a problem hiding this comment.
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`.
| }) | ||
| .map(|(ident, _)| ident); | ||
| .non_reserved_ident() | ||
| .filter(|_| self.look_ahead(1, |&tok| tok == token::Colon)); |
There was a problem hiding this comment.
Drive-by cleanup. Taken straight from PR #161775 which incidentally also introduces Token::non_reserved_ident (it's just so useful! :D)
| 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 { |
There was a problem hiding this comment.
See the added UI test for what this makes us accept.
| t.is_metavar_block() | ||
| || t.kind == token::OpenBrace | ||
| || t.is_non_raw_ident_where(|ident| { | ||
| matches!(ident.name, kw::For | kw::Loop | kw::While) |
There was a problem hiding this comment.
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)
| } | ||
| }) | ||
| // If we encounter a type like `impl 'a Sized`, suggest `impl 'a + Sized`. | ||
| if self.token.is_lifetime() |
There was a problem hiding this comment.
Apart from the "interpolated identifier" business this makes the diagnostic aware of keywords. Previously, it would also trigger on inputs like impl 'a if.
| } | ||
| }) | ||
| // If we encounter a type like `impl 'a Sized`, suggest `impl 'a + Sized`. | ||
| if self.token.is_lifetime() |
There was a problem hiding this comment.
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; } }
| +
|
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 :) |
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::Identin the parser to see if each place was intentionally ignoringNtIdentor not. I found out that the code pertaining to const closures (unstable featureconst_closures), to try bikeshed blocks (unstable featuretry_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
NtIdentas you can simply utilize helpers likeToken::uninterpolate,Token::ident,Token::is_keyword,Token::is_non_raw_identwhich 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)