Skip to content

Implement forced keywords (k#) - #161775

Open
fmease wants to merge 3 commits into
rust-lang:mainfrom
fmease:kay-hash
Open

fmease wants to merge 3 commits into
rust-lang:mainfrom
fmease:kay-hash

Conversation

@fmease

@fmease fmease commented Aug 25, 2026

Copy link
Copy Markdown
Member

View all comments

Part of #153839. CC @dianne

Introduces a new token kind to Rust >=2021 that looks like k#ident and that is called forced keyword (identifier). This is gated behind a new unstable feature called forced_keywords. This is backed by compiler MCP 945.

The ident in k#ident must be a keyword (from any edition) or a weak/contextual keyword for the token to be valid.


What won't be done in this PR:

  1. implementing forced keyword lifetimes ('k#static)
  2. migrating away from built-in syntax builtin # $ident($($tt)*) / introducing k#-exclusive (weak) keywords
  3. allowing the user to force edition-dependent & context-dependent keywords to be keywords
    • re. edition-dependent: forcing them to be keywords even in editions where they're usually not a (strong) keyword (today, that only affects gen anyway IINM)
    • re. context-dependent: E.g., union is only "active" if it's followed by a non-reserved identifier and under this PR the same rules apply to k#union even though that's not necessary; changing it would result in better diagnostics (e.g., for k#union struct {}: error: expected item, found `k#union` => expected identifier, found keyword `struct`) and maybe also allow for disambiguation for other context-dependent keywords
  4. extending the proc_macro::Ident API to allow users to programmatically create forced keywords (might never be added)

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

@rustbot rustbot added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-rust-analyzer Relevant to the rust-analyzer team, which will review and decide on the PR/issue. T-rustdoc Relevant to the rustdoc team, which will review and decide on the PR/issue. T-rustdoc-frontend Relevant to the rustdoc-frontend team, which will review and decide on the web UI/UX output. labels Aug 25, 2026
@fmease

fmease commented Aug 25, 2026

Copy link
Copy Markdown
Member Author

@bors try @rust-timer queue

@rust-timer

This comment has been minimized.

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

This comment has been minimized.

rust-bors Bot pushed a commit that referenced this pull request Aug 25, 2026
Implement forced keywords (`k#`)
@rust-log-analyzer

This comment has been minimized.

/// Traits for function pointers and items
(unstable, fn_static, "CURRENT_RUSTC_VERSION", Some(148768)),
/// Allows using forced keywords `k#fn`.
(unstable, forced_keywords, "CURRENT_RUSTC_VERSION", Some(153839)),

@fmease fmease Aug 25, 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.

I've intentionally not marked it internal even though this feature is only backed by a T-compiler MCP and not by a T-lang RFC or in-tree experiment:

I would find it a bit weird if we (in a hypothetical future) told users to "please try out" "k#only bounds" or "&k#own types & exprs" (features that would obviously be non-internal) but they'd have to enable an internal feature in tandem (could be perceived as off-putting).

However, I'm okay with switching it to incomplete or even internal if requested.

View changes since the review

@@ -103,6 +104,26 @@ impl ToInternal<tk::LitKind> for LitKind {
}
}

@fmease fmease Aug 25, 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.

We can't test the roundtripping in the current form of this PR because I haven't added any k#-exclusive weak keywords where it'd be observable if we accidentally dropped k# during encoding&decoding.

I have however tested locally that it works by introducing a new dummy keyword. A hypothetical UI test would have a setup like

use proc_macro::TokenStream;

#[proc_macro]
pub fn perform(_: TokenStream) -> TokenStream {
    let stream: TokenStream = "const _: Option<k#never> = None;").parse().unwrap(); // positive
    //let stream: TokenStream = "fn k#branded() {}").parse().unwrap(); // negative

    stream.into_iter().collect() // forces encoding+decoding
}

View changes since the review

@rust-log-analyzer

This comment has been minimized.

@rust-bors

rust-bors Bot commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

☀️ Try build successful (CI)
Build commit: 4eb8eac (4eb8eac3e01e4dbc9d58546df979f51a2ac2e5d5)
Base parent: b751e7a (b751e7a48501be14dcd57b4a430b73a0e51a0c52)

@rust-timer

This comment has been minimized.

@rust-timer

Copy link
Copy Markdown
Collaborator

Finished benchmarking commit (4eb8eac): comparison URL.

Overall result: ✅ improvements - no action needed

Benchmarking means the PR may be perf-sensitive. Consider adding rollup=never if this change is not fit for rolling up.

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

Instruction count

Our most reliable metric. Used to determine the overall result above. However, even this metric can be noisy.

mean range count
Regressions ❌
(primary)
- - 0
Regressions ❌
(secondary)
- - 0
Improvements ✅
(primary)
- - 0
Improvements ✅
(secondary)
-0.2% [-0.3%, -0.2%] 2
All ❌✅ (primary) - - 0

Max RSS (memory usage)

Results (primary -3.0%, secondary 1.1%)

A less reliable metric. May be of interest, but not used to determine the overall result above.

mean range count
Regressions ❌
(primary)
- - 0
Regressions ❌
(secondary)
2.8% [2.5%, 3.1%] 2
Improvements ✅
(primary)
-3.0% [-4.1%, -1.9%] 3
Improvements ✅
(secondary)
-2.2% [-2.2%, -2.2%] 1
All ❌✅ (primary) -3.0% [-4.1%, -1.9%] 3

Cycles

Results (primary 2.1%, secondary -1.4%)

A less reliable metric. May be of interest, but not used to determine the overall result above.

mean range count
Regressions ❌
(primary)
2.1% [2.1%, 2.1%] 1
Regressions ❌
(secondary)
2.2% [2.2%, 2.2%] 1
Improvements ✅
(primary)
- - 0
Improvements ✅
(secondary)
-3.2% [-4.1%, -2.3%] 2
All ❌✅ (primary) 2.1% [2.1%, 2.1%] 1

Binary size

Results (primary 0.0%, secondary -0.0%)

A less reliable metric. May be of interest, but not used to determine the overall result above.

mean range count
Regressions ❌
(primary)
0.1% [0.0%, 0.1%] 4
Regressions ❌
(secondary)
0.0% [0.0%, 0.0%] 1
Improvements ✅
(primary)
-0.0% [-0.1%, -0.0%] 4
Improvements ✅
(secondary)
-0.0% [-0.1%, -0.0%] 2
All ❌✅ (primary) 0.0% [-0.1%, 0.1%] 8

Bootstrap: 477.576s -> 469.017s (-1.79%)
Artifact size: 400.35 MiB -> 400.38 MiB (0.01%)

@rustbot rustbot removed the S-waiting-on-perf Status: Waiting on a perf run to be completed. label Aug 25, 2026
@rust-bors

This comment has been minimized.

@fmease
fmease force-pushed the kay-hash branch 2 times, most recently from 9e590de to 1de8681 Compare August 27, 2026 14:25
@fmease fmease added the S-blocked Status: Blocked on something else such as an RFC or other implementation work. label Aug 27, 2026
@fmease fmease changed the title Implement forced keywords (k#) [STACKED] Implement forced keywords (k#) Aug 27, 2026
@fmease

fmease commented Aug 27, 2026

Copy link
Copy Markdown
Member Author

Ready for review.

Caution

Only review the commits created after ------------------------- BRANCH SEPARATOR -------------------------.
That's because this PR is stacked on top of PR #161792. Hence, it's marked S-blocked Status: Blocked on something else such as an RFC or other implementation work. .

Best reviewed commit by commit.

@fmease
fmease marked this pull request as ready for review August 27, 2026 14:33
@rustbot

rustbot commented Aug 27, 2026

Copy link
Copy Markdown
Collaborator

Some changes occurred in compiler/rustc_builtin_macros/src/autodiff.rs

cc @ZuseZ4

rust-analyzer is developed in its own repository. If possible, consider making this change to rust-lang/rust-analyzer instead.

cc @rust-lang/rust-analyzer

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

This comment has been minimized.

@rust-bors

This comment has been minimized.

@rustbot

This comment has been minimized.

@rust-bors

This comment has been minimized.

@rustbot

This comment has been minimized.


/// Returns `true` if this symbol can be a forced keyword.
pub fn can_be_forced_keyword(self) -> bool {
self.is_reserved(|| Edition::EditionFuture) || self.is_weak()

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

Either in this PR or in a follow-up PR I'll do something less hazardous than blindly using is_weak wrt. stability:

Let's assume forced_keywords was stable, then we certainly wouldn't want to insta-stabilize k#WEAK if somebody introduced an unstable keyword WEAK but that's what would happen in the current implementation as we'd immediately start accepting #[cfg(false)] M!(k#WEAK);.

(On main, declaring a weak keyword in symbol.rs doesn't have any semantic consequences, it just defines a new interned symbol for which is_weak (only used in diagnostics & tools atm) returns true. It's more of a convention, so you can write things like .is_keyword(kw::Weak) instead of .is_keyword(sym::weak) in the parser.)

Anyways, I plan on splitting splitting weak keywords into two categories, "stable" and "non-committal" (…). For the latter category, I'm either gonna issue a feature gate in lexer/mod.rs if forced with k# or I'm just not gonna allow using k# until stabilization. Emitting a feature gate for the token, too, means we'd start emitting two feature gates for unstable syntax (one in the lexer, one in the parser) which most likely wouldn't get deduplicated by our diagnostic infra since the spans will differ. There might be some tricks for avoiding it though. Will think about that some other time.

View changes since the review

@petrochenkov petrochenkov self-assigned this Sep 17, 2026
@petrochenkov

Copy link
Copy Markdown
Contributor

Could you move all the commits here except fffba8a to a separate PR?
They don't require any language process unlike the primary change, and I could review and merge them quickly, and it would make this PR much smaller.
@rustbot author

@rustbot rustbot added S-waiting-on-author Status: This is 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 Sep 18, 2026
JonathanBrouwer added a commit to JonathanBrouwer/rust that referenced this pull request Sep 18, 2026
…rochenkov

Move parse error recovery for expression operators "out of line" & refactor in the area

**Background**: While we do have 3k-line module `rustc_parse/src/parser/diagnostics.rs`[^1] dedicated to syntax error diagnostics & parse error recovery, the rest of the parser is still "littered" or "interwoven" through and through with complex or verbose diagnostic code. We support numerous recoveries from syntaxes found in other languages right next to code that actually decides what is and what isn't part of Rust syntactically. This makes the parser code very messy, illegible, bug prone and otherwise hard to maintain.

So my long-term plan is to push all this diagnostic & recovery code "out of line", namely into new `rustc_parse/src/parser/$fragment/diagnostics.rs` files, to keep the "inline code" of parsing routines in `$fragment.rs` focused on actually parsing Rust code.

Why not just use the pre-existing `parser/diagnostics.rs` module? Well, making diagnostic code for expressions, patterns, types etc. share the same module is the definition of a hodgepodge. Moreover, moving all diagnostic code there would make the file way too large. Of course, for common code (if any) `parser/diagnostics.rs` would remain suitable.

Best reviewed commit by commit.

Commit [Refactor check_assoc_op to make it more legible](rust-lang@66311aa) was cherry-picked from my PR rust-lang#161775.

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

[^1]: Not to be confused with `rustc_parse/src/diagnostics.rs` which holds diagnostic structs.
Zalathar added a commit to Zalathar/rust that referenced this pull request Sep 19, 2026
…rochenkov

Move parse error recovery for expression operators "out of line" & refactor in the area

**Background**: While we do have 3k-line module `rustc_parse/src/parser/diagnostics.rs`[^1] dedicated to syntax error diagnostics & parse error recovery, the rest of the parser is still "littered" or "interwoven" through and through with complex or verbose diagnostic code. We support numerous recoveries from syntaxes found in other languages right next to code that actually decides what is and what isn't part of Rust syntactically. This makes the parser code very messy, illegible, bug prone and otherwise hard to maintain.

So my long-term plan is to push all this diagnostic & recovery code "out of line", namely into new `rustc_parse/src/parser/$fragment/diagnostics.rs` files, to keep the "inline code" of parsing routines in `$fragment.rs` focused on actually parsing Rust code.

Why not just use the pre-existing `parser/diagnostics.rs` module? Well, making diagnostic code for expressions, patterns, types etc. share the same module is the definition of a hodgepodge. Moreover, moving all diagnostic code there would make the file way too large. Of course, for common code (if any) `parser/diagnostics.rs` would remain suitable.

Best reviewed commit by commit.

Commit [Refactor check_assoc_op to make it more legible](rust-lang@66311aa) was cherry-picked from my PR rust-lang#161775.

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

[^1]: Not to be confused with `rustc_parse/src/diagnostics.rs` which holds diagnostic structs.
JonathanBrouwer added a commit to JonathanBrouwer/rust that referenced this pull request Sep 19, 2026
…rochenkov

Move parse error recovery for expression operators "out of line" & refactor in the area

**Background**: While we do have 3k-line module `rustc_parse/src/parser/diagnostics.rs`[^1] dedicated to syntax error diagnostics & parse error recovery, the rest of the parser is still "littered" or "interwoven" through and through with complex or verbose diagnostic code. We support numerous recoveries from syntaxes found in other languages right next to code that actually decides what is and what isn't part of Rust syntactically. This makes the parser code very messy, illegible, bug prone and otherwise hard to maintain.

So my long-term plan is to push all this diagnostic & recovery code "out of line", namely into new `rustc_parse/src/parser/$fragment/diagnostics.rs` files, to keep the "inline code" of parsing routines in `$fragment.rs` focused on actually parsing Rust code.

Why not just use the pre-existing `parser/diagnostics.rs` module? Well, making diagnostic code for expressions, patterns, types etc. share the same module is the definition of a hodgepodge. Moreover, moving all diagnostic code there would make the file way too large. Of course, for common code (if any) `parser/diagnostics.rs` would remain suitable.

Best reviewed commit by commit.

Commit [Refactor check_assoc_op to make it more legible](rust-lang@66311aa) was cherry-picked from my PR rust-lang#161775.

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

[^1]: Not to be confused with `rustc_parse/src/diagnostics.rs` which holds diagnostic structs.
JonathanBrouwer added a commit to JonathanBrouwer/rust that referenced this pull request Sep 19, 2026
…rochenkov

Move parse error recovery for expression operators "out of line" & refactor in the area

**Background**: While we do have 3k-line module `rustc_parse/src/parser/diagnostics.rs`[^1] dedicated to syntax error diagnostics & parse error recovery, the rest of the parser is still "littered" or "interwoven" through and through with complex or verbose diagnostic code. We support numerous recoveries from syntaxes found in other languages right next to code that actually decides what is and what isn't part of Rust syntactically. This makes the parser code very messy, illegible, bug prone and otherwise hard to maintain.

So my long-term plan is to push all this diagnostic & recovery code "out of line", namely into new `rustc_parse/src/parser/$fragment/diagnostics.rs` files, to keep the "inline code" of parsing routines in `$fragment.rs` focused on actually parsing Rust code.

Why not just use the pre-existing `parser/diagnostics.rs` module? Well, making diagnostic code for expressions, patterns, types etc. share the same module is the definition of a hodgepodge. Moreover, moving all diagnostic code there would make the file way too large. Of course, for common code (if any) `parser/diagnostics.rs` would remain suitable.

Best reviewed commit by commit.

Commit [Refactor check_assoc_op to make it more legible](rust-lang@66311aa) was cherry-picked from my PR rust-lang#161775.

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

[^1]: Not to be confused with `rustc_parse/src/diagnostics.rs` which holds diagnostic structs.
rust-bors Bot pushed a commit that referenced this pull request Sep 19, 2026
Rollup merge of #162591 - fmease:out-of-line-recovery, r=petrochenkov

Move parse error recovery for expression operators "out of line" & refactor in the area

**Background**: While we do have 3k-line module `rustc_parse/src/parser/diagnostics.rs`[^1] dedicated to syntax error diagnostics & parse error recovery, the rest of the parser is still "littered" or "interwoven" through and through with complex or verbose diagnostic code. We support numerous recoveries from syntaxes found in other languages right next to code that actually decides what is and what isn't part of Rust syntactically. This makes the parser code very messy, illegible, bug prone and otherwise hard to maintain.

So my long-term plan is to push all this diagnostic & recovery code "out of line", namely into new `rustc_parse/src/parser/$fragment/diagnostics.rs` files, to keep the "inline code" of parsing routines in `$fragment.rs` focused on actually parsing Rust code.

Why not just use the pre-existing `parser/diagnostics.rs` module? Well, making diagnostic code for expressions, patterns, types etc. share the same module is the definition of a hodgepodge. Moreover, moving all diagnostic code there would make the file way too large. Of course, for common code (if any) `parser/diagnostics.rs` would remain suitable.

Best reviewed commit by commit.

Commit [Refactor check_assoc_op to make it more legible](66311aa) was cherry-picked from my PR #161775.

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

[^1]: Not to be confused with `rustc_parse/src/diagnostics.rs` which holds diagnostic structs.
@rust-bors

This comment has been minimized.

@rustbot

rustbot commented Sep 19, 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.

pull Bot pushed a commit to xtqqczze/rust-lang-miri that referenced this pull request Sep 20, 2026
Move parse error recovery for expression operators "out of line" & refactor in the area

**Background**: While we do have 3k-line module `rustc_parse/src/parser/diagnostics.rs`[^1] dedicated to syntax error diagnostics & parse error recovery, the rest of the parser is still "littered" or "interwoven" through and through with complex or verbose diagnostic code. We support numerous recoveries from syntaxes found in other languages right next to code that actually decides what is and what isn't part of Rust syntactically. This makes the parser code very messy, illegible, bug prone and otherwise hard to maintain.

So my long-term plan is to push all this diagnostic & recovery code "out of line", namely into new `rustc_parse/src/parser/$fragment/diagnostics.rs` files, to keep the "inline code" of parsing routines in `$fragment.rs` focused on actually parsing Rust code.

Why not just use the pre-existing `parser/diagnostics.rs` module? Well, making diagnostic code for expressions, patterns, types etc. share the same module is the definition of a hodgepodge. Moreover, moving all diagnostic code there would make the file way too large. Of course, for common code (if any) `parser/diagnostics.rs` would remain suitable.

Best reviewed commit by commit.

Commit [Refactor check_assoc_op to make it more legible](rust-lang/rust@66311aa) was cherry-picked from my PR rust-lang/rust#161775.

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

[^1]: Not to be confused with `rustc_parse/src/diagnostics.rs` which holds diagnostic structs.
@rust-bors

rust-bors Bot commented Sep 20, 2026

Copy link
Copy Markdown
Contributor

☔ The latest upstream changes (presumably #163043) made this pull request unmergeable. Please resolve the merge conflicts by rebasing.

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

Labels

S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. T-rust-analyzer Relevant to the rust-analyzer team, which will review and decide on the PR/issue. T-rustdoc Relevant to the rustdoc team, which will review and decide on the PR/issue. T-rustdoc-frontend Relevant to the rustdoc-frontend team, which will review and decide on the web UI/UX output.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants