about summary refs log tree commit diff
path: root/compiler/rustc_parse/src
diff options
context:
space:
mode:
authorNicholas Nethercote <n.nethercote@gmail.com>2024-12-04 15:55:06 +1100
committerNicholas Nethercote <n.nethercote@gmail.com>2024-12-19 16:05:41 +1100
commitb9bf0b4b10148aa914243a527d9010aba9b7b827 (patch)
tree2478d44acd2d710543d168353b729e0716aefb70 /compiler/rustc_parse/src
parentd5370d981f58ebadf575f075a6f0d8c35bc704e8 (diff)
downloadrust-b9bf0b4b10148aa914243a527d9010aba9b7b827.tar.gz
rust-b9bf0b4b10148aa914243a527d9010aba9b7b827.zip
Speed up `Parser::expected_token_types`.
The parser pushes a `TokenType` to `Parser::expected_token_types` on
every call to the various `check`/`eat` methods, and clears it on every
call to `bump`. Some of those `TokenType` values are full tokens that
require cloning and dropping. This is a *lot* of work for something
that is only used in error messages and it accounts for a significant
fraction of parsing execution time.

This commit overhauls `TokenType` so that `Parser::expected_token_types`
can be implemented as a bitset. This requires changing `TokenType` to a
C-style parameterless enum, and adding `TokenTypeSet` which uses a
`u128` for the bits. (The new `TokenType` has 105 variants.)

The new types `ExpTokenPair` and `ExpKeywordPair` are now arguments to
the `check`/`eat` methods. This is for maximum speed. The elements in
the pairs are always statically known; e.g. a
`token::BinOp(token::Star)` is always paired with a `TokenType::Star`.
So we now compute `TokenType`s in advance and pass them in to
`check`/`eat` rather than the current approach of constructing them on
insertion into `expected_token_types`.

Values of these pair types can be produced by the new `exp!` macro,
which is used at every `check`/`eat` call site. The macro is for
convenience, allowing any pair to be generated from a single identifier.

The ident/keyword filtering in `expected_one_of_not_found` is no longer
necessary. It was there to account for some sloppiness in
`TokenKind`/`TokenType` comparisons.

The existing `TokenType` is moved to a new file `token_type.rs`, and all
its new infrastructure is added to that file. There is more boilerplate
code than I would like, but I can't see how to make it shorter.
Diffstat (limited to 'compiler/rustc_parse/src')
-rw-r--r--compiler/rustc_parse/src/parser/attr.rs41
-rw-r--r--compiler/rustc_parse/src/parser/diagnostics.rs139
-rw-r--r--compiler/rustc_parse/src/parser/expr.rs308
-rw-r--r--compiler/rustc_parse/src/parser/generics.rs33
-rw-r--r--compiler/rustc_parse/src/parser/item.rs370
-rw-r--r--compiler/rustc_parse/src/parser/mod.rs226
-rw-r--r--compiler/rustc_parse/src/parser/pat.rs77
-rw-r--r--compiler/rustc_parse/src/parser/path.rs34
-rw-r--r--compiler/rustc_parse/src/parser/stmt.rs27
-rw-r--r--compiler/rustc_parse/src/parser/token_type.rs620
-rw-r--r--compiler/rustc_parse/src/parser/ty.rs92
11 files changed, 1263 insertions, 704 deletions
diff --git a/compiler/rustc_parse/src/parser/attr.rs b/compiler/rustc_parse/src/parser/attr.rs
index 9da4ab5a788..2691e6f56d6 100644
--- a/compiler/rustc_parse/src/parser/attr.rs
+++ b/compiler/rustc_parse/src/parser/attr.rs
@@ -1,8 +1,7 @@
-use rustc_ast::token::{self, Delimiter};
-use rustc_ast::{self as ast, Attribute, attr};
+use rustc_ast::{self as ast, Attribute, attr, token};
 use rustc_errors::codes::*;
 use rustc_errors::{Diag, PResult};
-use rustc_span::{BytePos, Span, kw};
+use rustc_span::{BytePos, Span};
 use thin_vec::ThinVec;
 use tracing::debug;
 
@@ -10,7 +9,7 @@ use super::{
     AttrWrapper, Capturing, FnParseMode, ForceCollect, Parser, ParserRange, PathStyle, Trailing,
     UsePreAttrPos,
 };
-use crate::{errors, fluent_generated as fluent, maybe_whole};
+use crate::{errors, exp, fluent_generated as fluent, maybe_whole};
 
 // Public for rustfmt usage
 #[derive(Debug)]
@@ -45,7 +44,7 @@ impl<'a> Parser<'a> {
         let mut just_parsed_doc_comment = false;
         let start_pos = self.num_bump_calls;
         loop {
-            let attr = if self.check(&token::Pound) {
+            let attr = if self.check(exp!(Pound)) {
                 let prev_outer_attr_sp = outer_attrs.last().map(|attr: &Attribute| attr.span);
 
                 let inner_error_reason = if just_parsed_doc_comment {
@@ -126,14 +125,14 @@ impl<'a> Parser<'a> {
         let lo = self.token.span;
         // Attributes can't have attributes of their own [Editor's note: not with that attitude]
         self.collect_tokens_no_attrs(|this| {
-            assert!(this.eat(&token::Pound), "parse_attribute called in non-attribute position");
+            assert!(this.eat(exp!(Pound)), "parse_attribute called in non-attribute position");
 
             let style =
-                if this.eat(&token::Not) { ast::AttrStyle::Inner } else { ast::AttrStyle::Outer };
+                if this.eat(exp!(Not)) { ast::AttrStyle::Inner } else { ast::AttrStyle::Outer };
 
-            this.expect(&token::OpenDelim(Delimiter::Bracket))?;
+            this.expect(exp!(OpenBracket))?;
             let item = this.parse_attr_item(ForceCollect::No)?;
-            this.expect(&token::CloseDelim(Delimiter::Bracket))?;
+            this.expect(exp!(CloseBracket))?;
             let attr_sp = lo.to(this.prev_token.span);
 
             // Emit error if inner attribute is encountered and forbidden.
@@ -274,10 +273,10 @@ impl<'a> Parser<'a> {
 
         // Attr items don't have attributes.
         self.collect_tokens(None, AttrWrapper::empty(), force_collect, |this, _empty_attrs| {
-            let is_unsafe = this.eat_keyword(kw::Unsafe);
+            let is_unsafe = this.eat_keyword(exp!(Unsafe));
             let unsafety = if is_unsafe {
                 let unsafe_span = this.prev_token.span;
-                this.expect(&token::OpenDelim(Delimiter::Parenthesis))?;
+                this.expect(exp!(OpenParen))?;
                 ast::Safety::Unsafe(unsafe_span)
             } else {
                 ast::Safety::Default
@@ -286,7 +285,7 @@ impl<'a> Parser<'a> {
             let path = this.parse_path(PathStyle::Mod)?;
             let args = this.parse_attr_args()?;
             if is_unsafe {
-                this.expect(&token::CloseDelim(Delimiter::Parenthesis))?;
+                this.expect(exp!(CloseParen))?;
             }
             Ok((
                 ast::AttrItem { unsafety, path, args, tokens: None },
@@ -306,7 +305,7 @@ impl<'a> Parser<'a> {
         loop {
             let start_pos = self.num_bump_calls;
             // Only try to parse if it is an inner attribute (has `!`).
-            let attr = if self.check(&token::Pound) && self.look_ahead(1, |t| t == &token::Not) {
+            let attr = if self.check(exp!(Pound)) && self.look_ahead(1, |t| t == &token::Not) {
                 Some(self.parse_attribute(InnerAttrPolicy::Permitted)?)
             } else if let token::DocComment(comment_kind, attr_style, data) = self.token.kind {
                 if attr_style == ast::AttrStyle::Inner {
@@ -358,7 +357,7 @@ impl<'a> Parser<'a> {
         &mut self,
     ) -> PResult<'a, (ast::MetaItemInner, Vec<(ast::AttrItem, Span)>)> {
         let cfg_predicate = self.parse_meta_item_inner()?;
-        self.expect(&token::Comma)?;
+        self.expect(exp!(Comma))?;
 
         // Presumably, the majority of the time there will only be one attr.
         let mut expanded_attrs = Vec::with_capacity(1);
@@ -366,7 +365,7 @@ impl<'a> Parser<'a> {
             let lo = self.token.span;
             let item = self.parse_attr_item(ForceCollect::Yes)?;
             expanded_attrs.push((item, lo.to(self.prev_token.span)));
-            if !self.eat(&token::Comma) {
+            if !self.eat(exp!(Comma)) {
                 break;
             }
         }
@@ -380,7 +379,7 @@ impl<'a> Parser<'a> {
         let mut nmis = ThinVec::with_capacity(1);
         while self.token != token::Eof {
             nmis.push(self.parse_meta_item_inner()?);
-            if !self.eat(&token::Comma) {
+            if !self.eat(exp!(Comma)) {
                 break;
             }
         }
@@ -413,13 +412,13 @@ impl<'a> Parser<'a> {
 
         let lo = self.token.span;
         let is_unsafe = if unsafe_allowed == AllowLeadingUnsafe::Yes {
-            self.eat_keyword(kw::Unsafe)
+            self.eat_keyword(exp!(Unsafe))
         } else {
             false
         };
         let unsafety = if is_unsafe {
             let unsafe_span = self.prev_token.span;
-            self.expect(&token::OpenDelim(Delimiter::Parenthesis))?;
+            self.expect(exp!(OpenParen))?;
 
             ast::Safety::Unsafe(unsafe_span)
         } else {
@@ -429,7 +428,7 @@ impl<'a> Parser<'a> {
         let path = self.parse_path(PathStyle::Mod)?;
         let kind = self.parse_meta_item_kind()?;
         if is_unsafe {
-            self.expect(&token::CloseDelim(Delimiter::Parenthesis))?;
+            self.expect(exp!(CloseParen))?;
         }
         let span = lo.to(self.prev_token.span);
 
@@ -437,9 +436,9 @@ impl<'a> Parser<'a> {
     }
 
     pub(crate) fn parse_meta_item_kind(&mut self) -> PResult<'a, ast::MetaItemKind> {
-        Ok(if self.eat(&token::Eq) {
+        Ok(if self.eat(exp!(Eq)) {
             ast::MetaItemKind::NameValue(self.parse_unsuffixed_meta_item_lit()?)
-        } else if self.check(&token::OpenDelim(Delimiter::Parenthesis)) {
+        } else if self.check(exp!(OpenParen)) {
             let (list, _) = self.parse_paren_comma_seq(|p| p.parse_meta_item_inner())?;
             ast::MetaItemKind::List(list)
         } else {
diff --git a/compiler/rustc_parse/src/parser/diagnostics.rs b/compiler/rustc_parse/src/parser/diagnostics.rs
index 1a8f8f6069a..7e9b9219e7a 100644
--- a/compiler/rustc_parse/src/parser/diagnostics.rs
+++ b/compiler/rustc_parse/src/parser/diagnostics.rs
@@ -29,7 +29,8 @@ use tracing::{debug, trace};
 
 use super::pat::Expected;
 use super::{
-    BlockMode, CommaRecoveryMode, Parser, PathStyle, Restrictions, SemiColonMode, SeqSep, TokenType,
+    BlockMode, CommaRecoveryMode, ExpTokenPair, Parser, PathStyle, Restrictions, SemiColonMode,
+    SeqSep, TokenType,
 };
 use crate::errors::{
     AddParen, AmbiguousPlus, AsyncMoveBlockIn2015, AttributeOnParamType, AwaitSuggestion,
@@ -47,7 +48,7 @@ use crate::errors::{
     UnexpectedConstParamDeclarationSugg, UnmatchedAngleBrackets, UseEqInstead, WrapType,
 };
 use crate::parser::attr::InnerAttrPolicy;
-use crate::{fluent_generated as fluent, parser};
+use crate::{exp, fluent_generated as fluent};
 
 /// Creates a placeholder argument.
 pub(super) fn dummy_arg(ident: Ident, guar: ErrorGuaranteed) -> Param {
@@ -462,8 +463,8 @@ impl<'a> Parser<'a> {
 
     pub(super) fn expected_one_of_not_found(
         &mut self,
-        edible: &[TokenKind],
-        inedible: &[TokenKind],
+        edible: &[ExpTokenPair<'_>],
+        inedible: &[ExpTokenPair<'_>],
     ) -> PResult<'a, ErrorGuaranteed> {
         debug!("expected_one_of_not_found(edible: {:?}, inedible: {:?})", edible, inedible);
         fn tokens_to_string(tokens: &[TokenType]) -> String {
@@ -483,50 +484,17 @@ impl<'a> Parser<'a> {
             })
         }
 
-        self.expected_token_types
-            .extend(edible.iter().chain(inedible).cloned().map(TokenType::Token));
-        let mut expected = self
-            .expected_token_types
-            .iter()
-            .filter(|token| {
-                // Filter out suggestions that suggest the same token which was found and deemed incorrect.
-                fn is_ident_eq_keyword(found: &TokenKind, expected: &TokenType) -> bool {
-                    if let TokenKind::Ident(current_sym, _) = found
-                        && let TokenType::Keyword(suggested_sym) = expected
-                    {
-                        return current_sym == suggested_sym;
-                    }
-                    false
-                }
-
-                if **token != parser::TokenType::Token(self.token.kind.clone()) {
-                    let eq = is_ident_eq_keyword(&self.token.kind, &token);
-                    // If the suggestion is a keyword and the found token is an ident,
-                    // the content of which are equal to the suggestion's content,
-                    // we can remove that suggestion (see the `return false` below).
-
-                    // If this isn't the case however, and the suggestion is a token the
-                    // content of which is the same as the found token's, we remove it as well.
-                    if !eq {
-                        if let TokenType::Token(kind) = token {
-                            if self.token == *kind {
-                                return false;
-                            }
-                        }
-                        return true;
-                    }
-                }
-                false
-            })
-            .cloned()
-            .collect::<Vec<_>>();
+        for exp in edible.iter().chain(inedible.iter()) {
+            self.expected_token_types.insert(exp.token_type);
+        }
+        let mut expected: Vec<_> = self.expected_token_types.iter().collect();
         expected.sort_by_cached_key(|x| x.to_string());
         expected.dedup();
 
         let sm = self.psess.source_map();
 
         // Special-case "expected `;`" errors.
-        if expected.contains(&TokenType::Token(token::Semi)) {
+        if expected.contains(&TokenType::Semi) {
             // If the user is trying to write a ternary expression, recover it and
             // return an Err to prevent a cascade of irrelevant diagnostics.
             if self.prev_token == token::Question
@@ -578,7 +546,7 @@ impl<'a> Parser<'a> {
                     || (sm.is_multiline(
                         self.prev_token.span.shrink_to_hi().until(self.token.span.shrink_to_lo()),
                     ) && t == &token::Pound)
-            }) && !expected.contains(&TokenType::Token(token::Comma))
+            }) && !expected.contains(&TokenType::Comma)
             {
                 // Missing semicolon typo. This is triggered if the next token could either start a
                 // new statement or is a block close. For example:
@@ -598,7 +566,7 @@ impl<'a> Parser<'a> {
 
         if self.token == TokenKind::EqEq
             && self.prev_token.is_ident()
-            && expected.iter().any(|tok| matches!(tok, TokenType::Token(TokenKind::Eq)))
+            && expected.contains(&TokenType::Eq)
         {
             // Likely typo: `=` → `==` in let expr or enum item
             return Err(self.dcx().create_err(UseEqInstead { span: self.token.span }));
@@ -637,15 +605,8 @@ impl<'a> Parser<'a> {
 
         // Look for usages of '=>' where '>=' was probably intended
         if self.token == token::FatArrow
-            && expected
-                .iter()
-                .any(|tok| matches!(tok, TokenType::Operator | TokenType::Token(TokenKind::Le)))
-            && !expected.iter().any(|tok| {
-                matches!(
-                    tok,
-                    TokenType::Token(TokenKind::FatArrow) | TokenType::Token(TokenKind::Comma)
-                )
-            })
+            && expected.iter().any(|tok| matches!(tok, TokenType::Operator | TokenType::Le))
+            && !expected.iter().any(|tok| matches!(tok, TokenType::FatArrow | TokenType::Comma))
         {
             err.span_suggestion(
                 self.token.span,
@@ -742,7 +703,7 @@ impl<'a> Parser<'a> {
         };
 
         if self.check_too_many_raw_str_terminators(&mut err) {
-            if expected.contains(&TokenType::Token(token::Semi)) && self.eat(&token::Semi) {
+            if expected.contains(&TokenType::Semi) && self.eat(exp!(Semi)) {
                 let guar = err.emit();
                 return Ok(guar);
             } else {
@@ -788,10 +749,8 @@ impl<'a> Parser<'a> {
         };
         let expected_token_types: &[TokenType] =
             expected.len().checked_sub(10).map_or(&expected, |index| &expected[index..]);
-        let expected_keywords: Vec<Symbol> = expected_token_types
-            .iter()
-            .filter_map(|token| if let TokenType::Keyword(kw) = token { Some(*kw) } else { None })
-            .collect();
+        let expected_keywords: Vec<Symbol> =
+            expected_token_types.iter().filter_map(|token| token.is_keyword()).collect();
 
         // When there are a few keywords in the last ten elements of `self.expected_token_types`
         // and the current token is an identifier, it's probably a misspelled keyword. This handles
@@ -1053,7 +1012,7 @@ impl<'a> Parser<'a> {
                 (Err(snapshot_err), Err(err)) => {
                     // We don't know what went wrong, emit the normal error.
                     snapshot_err.cancel();
-                    self.consume_block(Delimiter::Brace, ConsumeClosingDelim::Yes);
+                    self.consume_block(exp!(OpenBrace), exp!(CloseBrace), ConsumeClosingDelim::Yes);
                     Err(err)
                 }
                 (Ok(_), Ok(mut tail)) => {
@@ -1090,7 +1049,7 @@ impl<'a> Parser<'a> {
                     Applicability::MaybeIncorrect,
                 );
                 let guar = err.emit();
-                self.eat_to_tokens(&[&token::CloseDelim(Delimiter::Brace)]);
+                self.eat_to_tokens(&[exp!(CloseBrace)]);
                 guar
             }
             token::OpenDelim(Delimiter::Parenthesis)
@@ -1098,7 +1057,7 @@ impl<'a> Parser<'a> {
             {
                 // We are within a function call or tuple, we can emit the error
                 // and recover.
-                self.eat_to_tokens(&[&token::CloseDelim(Delimiter::Parenthesis), &token::Comma]);
+                self.eat_to_tokens(&[exp!(CloseParen), exp!(Comma)]);
 
                 err.multipart_suggestion_verbose(
                     "you might have meant to open the body of the closure",
@@ -1127,7 +1086,7 @@ impl<'a> Parser<'a> {
 
     /// Eats and discards tokens until one of `closes` is encountered. Respects token trees,
     /// passes through any errors encountered. Used for error recovery.
-    pub(super) fn eat_to_tokens(&mut self, closes: &[&TokenKind]) {
+    pub(super) fn eat_to_tokens(&mut self, closes: &[ExpTokenPair<'_>]) {
         if let Err(err) = self
             .parse_seq_to_before_tokens(closes, &[], SeqSep::none(), |p| Ok(p.parse_token_tree()))
         {
@@ -1148,7 +1107,7 @@ impl<'a> Parser<'a> {
     pub(super) fn check_trailing_angle_brackets(
         &mut self,
         segment: &PathSegment,
-        end: &[&TokenKind],
+        end: &[ExpTokenPair<'_>],
     ) -> Option<ErrorGuaranteed> {
         if !self.may_recover() {
             return None;
@@ -1231,7 +1190,7 @@ impl<'a> Parser<'a> {
         // second case.
         if self.look_ahead(position, |t| {
             trace!("check_trailing_angle_brackets: t={:?}", t);
-            end.contains(&&t.kind)
+            end.iter().any(|exp| exp.tok == &t.kind)
         }) {
             // Eat from where we started until the end token so that parsing can continue
             // as if we didn't have those extra angle brackets.
@@ -1299,11 +1258,11 @@ impl<'a> Parser<'a> {
     ) -> PResult<'a, ErrorGuaranteed> {
         if let ExprKind::Binary(binop, _, _) = &expr.kind
             && let ast::BinOpKind::Lt = binop.node
-            && self.eat(&token::Comma)
+            && self.eat(exp!(Comma))
         {
             let x = self.parse_seq_to_before_end(
-                &token::Gt,
-                SeqSep::trailing_allowed(token::Comma),
+                exp!(Gt),
+                SeqSep::trailing_allowed(exp!(Comma)),
                 |p| match p.parse_generic_arg(None)? {
                     Some(arg) => Ok(arg),
                     // If we didn't eat a generic arg, then we should error.
@@ -1312,7 +1271,7 @@ impl<'a> Parser<'a> {
             );
             match x {
                 Ok((_, _, Recovered::No)) => {
-                    if self.eat(&token::Gt) {
+                    if self.eat(exp!(Gt)) {
                         // We made sense of it. Improve the error message.
                         e.span_suggestion_verbose(
                             binop.span.shrink_to_lo(),
@@ -1875,7 +1834,7 @@ impl<'a> Parser<'a> {
         ty_span: Span,
         ty: P<Ty>,
     ) -> PResult<'a, P<T>> {
-        self.expect(&token::PathSep)?;
+        self.expect(exp!(PathSep))?;
 
         let mut path = ast::Path { segments: ThinVec::new(), span: DUMMY_SP, tokens: None };
         self.parse_path_segments(&mut path.segments, T::PATH_STYLE, None)?;
@@ -1957,10 +1916,10 @@ impl<'a> Parser<'a> {
     }
 
     pub(super) fn expect_semi(&mut self) -> PResult<'a, ()> {
-        if self.eat(&token::Semi) || self.recover_colon_as_semi() {
+        if self.eat(exp!(Semi)) || self.recover_colon_as_semi() {
             return Ok(());
         }
-        self.expect(&token::Semi).map(drop) // Error unconditionally
+        self.expect(exp!(Semi)).map(drop) // Error unconditionally
     }
 
     pub(super) fn recover_colon_as_semi(&mut self) -> bool {
@@ -2005,15 +1964,15 @@ impl<'a> Parser<'a> {
     }
 
     fn recover_await_macro(&mut self) -> PResult<'a, (Span, P<Expr>, bool)> {
-        self.expect(&token::Not)?;
-        self.expect(&token::OpenDelim(Delimiter::Parenthesis))?;
+        self.expect(exp!(Not))?;
+        self.expect(exp!(OpenParen))?;
         let expr = self.parse_expr()?;
-        self.expect(&token::CloseDelim(Delimiter::Parenthesis))?;
+        self.expect(exp!(CloseParen))?;
         Ok((self.prev_token.span, expr, false))
     }
 
     fn recover_await_prefix(&mut self, await_sp: Span) -> PResult<'a, (Span, P<Expr>, bool)> {
-        let is_question = self.eat(&token::Question); // Handle `await? <expr>`.
+        let is_question = self.eat(exp!(Question)); // Handle `await? <expr>`.
         let expr = if self.token == token::OpenDelim(Delimiter::Brace) {
             // Handle `await { <expr> }`.
             // This needs to be handled separately from the next arm to avoid
@@ -2075,7 +2034,7 @@ impl<'a> Parser<'a> {
             let try_span = lo.to(self.token.span); //we take the try!( span
             self.bump(); //remove (
             let is_empty = self.token == token::CloseDelim(Delimiter::Parenthesis); //check if the block is empty
-            self.consume_block(Delimiter::Parenthesis, ConsumeClosingDelim::No); //eat the block
+            self.consume_block(exp!(OpenParen), exp!(CloseParen), ConsumeClosingDelim::No); //eat the block
             let hi = self.token.span;
             self.bump(); //remove )
             let mut err = self.dcx().struct_span_err(lo.to(hi), "use of deprecated `try` macro");
@@ -2131,13 +2090,14 @@ impl<'a> Parser<'a> {
 
     pub(super) fn recover_seq_parse_error(
         &mut self,
-        delim: Delimiter,
+        open: ExpTokenPair<'_>,
+        close: ExpTokenPair<'_>,
         lo: Span,
         err: Diag<'a>,
     ) -> P<Expr> {
         let guar = err.emit();
         // Recover from parse error, callers expect the closing delim to be consumed.
-        self.consume_block(delim, ConsumeClosingDelim::Yes);
+        self.consume_block(open, close, ConsumeClosingDelim::Yes);
         self.mk_expr(lo.to(self.prev_token.span), ExprKind::Err(guar))
     }
 
@@ -2226,7 +2186,7 @@ impl<'a> Parser<'a> {
     }
 
     pub(super) fn check_for_for_in_in_typo(&mut self, in_span: Span) {
-        if self.eat_keyword(kw::In) {
+        if self.eat_keyword(exp!(In)) {
             // a common typo: `for _ in in bar {}`
             self.dcx().emit_err(InInTypo {
                 span: self.prev_token.span,
@@ -2367,7 +2327,7 @@ impl<'a> Parser<'a> {
 
     pub(super) fn recover_arg_parse(&mut self) -> PResult<'a, (P<ast::Pat>, P<ast::Ty>)> {
         let pat = self.parse_pat_no_top_alt(Some(Expected::ArgumentName), None)?;
-        self.expect(&token::Colon)?;
+        self.expect(exp!(Colon))?;
         let ty = self.parse_ty()?;
 
         self.dcx().emit_err(PatternMethodParamWithoutBody { span: pat.span });
@@ -2385,12 +2345,17 @@ impl<'a> Parser<'a> {
         Ok(param)
     }
 
-    pub(super) fn consume_block(&mut self, delim: Delimiter, consume_close: ConsumeClosingDelim) {
+    pub(super) fn consume_block(
+        &mut self,
+        open: ExpTokenPair<'_>,
+        close: ExpTokenPair<'_>,
+        consume_close: ConsumeClosingDelim,
+    ) {
         let mut brace_depth = 0;
         loop {
-            if self.eat(&token::OpenDelim(delim)) {
+            if self.eat(open) {
                 brace_depth += 1;
-            } else if self.check(&token::CloseDelim(delim)) {
+            } else if self.check(close) {
                 if brace_depth == 0 {
                     if let ConsumeClosingDelim::Yes = consume_close {
                         // Some of the callers of this method expect to be able to parse the
@@ -2546,7 +2511,7 @@ impl<'a> Parser<'a> {
         match self.recover_const_arg(arg.span(), err) {
             Ok(arg) => {
                 args.push(AngleBracketedArg::Arg(arg));
-                if self.eat(&token::Comma) {
+                if self.eat(exp!(Comma)) {
                     return Ok(true); // Continue
                 }
             }
@@ -3017,7 +2982,7 @@ impl<'a> Parser<'a> {
     /// Check for exclusive ranges written as `..<`
     pub(crate) fn maybe_err_dotdotlt_syntax(&self, maybe_lt: Token, mut err: Diag<'a>) -> Diag<'a> {
         if maybe_lt == token::Lt
-            && (self.expected_token_types.contains(&TokenType::Token(token::Gt))
+            && (self.expected_token_types.contains(TokenType::Gt)
                 || matches!(self.token.kind, token::Literal(..)))
         {
             err.span_suggestion(
@@ -3147,9 +3112,9 @@ impl<'a> Parser<'a> {
     /// Parse and throw away a parenthesized comma separated
     /// sequence of patterns until `)` is reached.
     fn skip_pat_list(&mut self) -> PResult<'a, ()> {
-        while !self.check(&token::CloseDelim(Delimiter::Parenthesis)) {
+        while !self.check(exp!(CloseParen)) {
             self.parse_pat_no_top_alt(None, None)?;
-            if !self.eat(&token::Comma) {
+            if !self.eat(exp!(Comma)) {
                 return Ok(());
             }
         }
diff --git a/compiler/rustc_parse/src/parser/expr.rs b/compiler/rustc_parse/src/parser/expr.rs
index 1e84b2a0cf8..2f4adf2af9e 100644
--- a/compiler/rustc_parse/src/parser/expr.rs
+++ b/compiler/rustc_parse/src/parser/expr.rs
@@ -35,10 +35,10 @@ use super::diagnostics::SnapshotParser;
 use super::pat::{CommaRecoveryMode, Expected, RecoverColon, RecoverComma};
 use super::ty::{AllowPlus, RecoverQPath, RecoverReturnSign};
 use super::{
-    AttrWrapper, BlockMode, ClosureSpans, ForceCollect, Parser, PathStyle, Restrictions,
-    SemiColonMode, SeqSep, TokenType, Trailing, UsePreAttrPos,
+    AttrWrapper, BlockMode, ClosureSpans, ExpTokenPair, ForceCollect, Parser, PathStyle,
+    Restrictions, SemiColonMode, SeqSep, TokenType, Trailing, UsePreAttrPos,
 };
-use crate::{errors, maybe_recover_from_interpolated_ty_qpath};
+use crate::{errors, exp, maybe_recover_from_interpolated_ty_qpath};
 
 #[derive(Debug)]
 pub(super) enum DestructuredFloat {
@@ -153,7 +153,7 @@ impl<'a> Parser<'a> {
             return Ok((lhs, parsed_something));
         }
 
-        self.expected_token_types.push(TokenType::Operator);
+        self.expected_token_types.insert(TokenType::Operator);
         while let Some(op) = self.check_assoc_op() {
             let lhs_span = self.interpolated_or_expr_span(&lhs);
             let cur_op_span = self.token.span;
@@ -873,9 +873,9 @@ impl<'a> Parser<'a> {
 
     /// Parse `mut?` or `raw [ const | mut ]`.
     fn parse_borrow_modifiers(&mut self) -> (ast::BorrowKind, ast::Mutability) {
-        if self.check_keyword(kw::Raw) && self.look_ahead(1, Token::is_mutability) {
+        if self.check_keyword(exp!(Raw)) && self.look_ahead(1, Token::is_mutability) {
             // `raw [ const | mut ]`.
-            let found_raw = self.eat_keyword(kw::Raw);
+            let found_raw = self.eat_keyword(exp!(Raw));
             assert!(found_raw);
             let mutability = self.parse_const_or_mut().unwrap();
             (ast::BorrowKind::Raw, mutability)
@@ -908,7 +908,7 @@ impl<'a> Parser<'a> {
                         // a `return` which could be suggested otherwise.
                         self.eat_noexpect(&token::Question)
                     } else {
-                        self.eat(&token::Question)
+                        self.eat(exp!(Question))
                     };
                 if has_question {
                     // `expr?`
@@ -926,7 +926,7 @@ impl<'a> Parser<'a> {
                     self.dcx().emit_err(errors::ExprRArrowCall { span });
                     true
                 } else {
-                    self.eat(&token::Dot)
+                    self.eat(exp!(Dot))
                 };
                 if has_dot {
                     // expr.f
@@ -1251,7 +1251,7 @@ impl<'a> Parser<'a> {
             .map(|args| self.mk_expr(lo.to(self.prev_token.span), self.mk_call(fun, args)));
         match self.maybe_recover_struct_lit_bad_delims(lo, open_paren, seq, snapshot) {
             Ok(expr) => expr,
-            Err(err) => self.recover_seq_parse_error(Delimiter::Parenthesis, lo, err),
+            Err(err) => self.recover_seq_parse_error(exp!(OpenParen), exp!(CloseParen), lo, err),
         }
     }
 
@@ -1268,10 +1268,8 @@ impl<'a> Parser<'a> {
         match (self.may_recover(), seq, snapshot) {
             (true, Err(err), Some((mut snapshot, ExprKind::Path(None, path)))) => {
                 snapshot.bump(); // `(`
-                match snapshot.parse_struct_fields(path.clone(), false, Delimiter::Parenthesis) {
-                    Ok((fields, ..))
-                        if snapshot.eat(&token::CloseDelim(Delimiter::Parenthesis)) =>
-                    {
+                match snapshot.parse_struct_fields(path.clone(), false, exp!(CloseParen)) {
+                    Ok((fields, ..)) if snapshot.eat(exp!(CloseParen)) => {
                         // We are certain we have `Enum::Foo(a: 3, b: 4)`, suggest
                         // `Enum::Foo { a: 3, b: 4 }` or `Enum::Foo(3, 4)`.
                         self.restore_snapshot(snapshot);
@@ -1328,7 +1326,7 @@ impl<'a> Parser<'a> {
         self.bump(); // `[`
         let index = self.parse_expr()?;
         self.suggest_missing_semicolon_before_array(prev_span, open_delim_span)?;
-        self.expect(&token::CloseDelim(Delimiter::Bracket))?;
+        self.expect(exp!(CloseBracket))?;
         Ok(self.mk_expr(
             lo.to(self.prev_token.span),
             self.mk_index(base, index, open_delim_span.to(self.prev_token.span)),
@@ -1337,12 +1335,12 @@ impl<'a> Parser<'a> {
 
     /// Assuming we have just parsed `.`, continue parsing into an expression.
     fn parse_dot_suffix(&mut self, self_arg: P<Expr>, lo: Span) -> PResult<'a, P<Expr>> {
-        if self.token.uninterpolated_span().at_least_rust_2018() && self.eat_keyword(kw::Await) {
+        if self.token.uninterpolated_span().at_least_rust_2018() && self.eat_keyword(exp!(Await)) {
             return Ok(self.mk_await_expr(self_arg, lo));
         }
 
         // Post-fix match
-        if self.eat_keyword(kw::Match) {
+        if self.eat_keyword(exp!(Match)) {
             let match_span = self.prev_token.span;
             self.psess.gated_spans.gate(sym::postfix_match, match_span);
             return self.parse_match_block(lo, match_span, self_arg, MatchKind::Postfix);
@@ -1350,10 +1348,10 @@ impl<'a> Parser<'a> {
 
         let fn_span_lo = self.token.span;
         let mut seg = self.parse_path_segment(PathStyle::Expr, None)?;
-        self.check_trailing_angle_brackets(&seg, &[&token::OpenDelim(Delimiter::Parenthesis)]);
+        self.check_trailing_angle_brackets(&seg, &[exp!(OpenParen)]);
         self.check_turbofish_missing_angle_brackets(&mut seg);
 
-        if self.check(&token::OpenDelim(Delimiter::Parenthesis)) {
+        if self.check(exp!(OpenParen)) {
             // Method call `expr.f()`
             let args = self.parse_expr_paren_seq()?;
             let fn_span = fn_span_lo.to(self.prev_token.span);
@@ -1415,18 +1413,18 @@ impl<'a> Parser<'a> {
 
         let restrictions = self.restrictions;
         self.with_res(restrictions - Restrictions::ALLOW_LET, |this| {
-            // Note: when adding new syntax here, don't forget to adjust `TokenKind::can_begin_expr()`.
+            // Note: adding new syntax here? Don't forget to adjust `TokenKind::can_begin_expr()`.
             let lo = this.token.span;
             if let token::Literal(_) = this.token.kind {
                 // This match arm is a special-case of the `_` match arm below and
                 // could be removed without changing functionality, but it's faster
                 // to have it here, especially for programs with large constants.
                 this.parse_expr_lit()
-            } else if this.check(&token::OpenDelim(Delimiter::Parenthesis)) {
+            } else if this.check(exp!(OpenParen)) {
                 this.parse_expr_tuple_parens(restrictions)
-            } else if this.check(&token::OpenDelim(Delimiter::Brace)) {
+            } else if this.check(exp!(OpenBrace)) {
                 this.parse_expr_block(None, lo, BlockCheckMode::Default)
-            } else if this.check(&token::BinOp(token::Or)) || this.check(&token::OrOr) {
+            } else if this.check(exp!(Or)) || this.check(exp!(OrOr)) {
                 this.parse_expr_closure().map_err(|mut err| {
                     // If the input is something like `if a { 1 } else { 2 } | if a { 3 } else { 4 }`
                     // then suggest parens around the lhs.
@@ -1435,41 +1433,41 @@ impl<'a> Parser<'a> {
                     }
                     err
                 })
-            } else if this.check(&token::OpenDelim(Delimiter::Bracket)) {
-                this.parse_expr_array_or_repeat(Delimiter::Bracket)
+            } else if this.check(exp!(OpenBracket)) {
+                this.parse_expr_array_or_repeat(exp!(CloseBracket))
             } else if this.is_builtin() {
                 this.parse_expr_builtin()
             } else if this.check_path() {
                 this.parse_expr_path_start()
-            } else if this.check_keyword(kw::Move)
-                || this.check_keyword(kw::Static)
+            } else if this.check_keyword(exp!(Move))
+                || this.check_keyword(exp!(Static))
                 || this.check_const_closure()
             {
                 this.parse_expr_closure()
-            } else if this.eat_keyword(kw::If) {
+            } else if this.eat_keyword(exp!(If)) {
                 this.parse_expr_if()
-            } else if this.check_keyword(kw::For) {
+            } else if this.check_keyword(exp!(For)) {
                 if this.choose_generics_over_qpath(1) {
                     this.parse_expr_closure()
                 } else {
-                    assert!(this.eat_keyword(kw::For));
+                    assert!(this.eat_keyword(exp!(For)));
                     this.parse_expr_for(None, lo)
                 }
-            } else if this.eat_keyword(kw::While) {
+            } else if this.eat_keyword(exp!(While)) {
                 this.parse_expr_while(None, lo)
             } else if let Some(label) = this.eat_label() {
                 this.parse_expr_labeled(label, true)
-            } else if this.eat_keyword(kw::Loop) {
+            } else if this.eat_keyword(exp!(Loop)) {
                 this.parse_expr_loop(None, lo).map_err(|mut err| {
                     err.span_label(lo, "while parsing this `loop` expression");
                     err
                 })
-            } else if this.eat_keyword(kw::Match) {
+            } else if this.eat_keyword(exp!(Match)) {
                 this.parse_expr_match().map_err(|mut err| {
                     err.span_label(lo, "while parsing this `match` expression");
                     err
                 })
-            } else if this.eat_keyword(kw::Unsafe) {
+            } else if this.eat_keyword(exp!(Unsafe)) {
                 this.parse_expr_block(None, lo, BlockCheckMode::Unsafe(ast::UserProvided)).map_err(
                     |mut err| {
                         err.span_label(lo, "while parsing this `unsafe` expression");
@@ -1481,23 +1479,23 @@ impl<'a> Parser<'a> {
             } else if this.may_recover() && this.is_do_catch_block() {
                 this.recover_do_catch()
             } else if this.is_try_block() {
-                this.expect_keyword(kw::Try)?;
+                this.expect_keyword(exp!(Try))?;
                 this.parse_try_block(lo)
-            } else if this.eat_keyword(kw::Return) {
+            } else if this.eat_keyword(exp!(Return)) {
                 this.parse_expr_return()
-            } else if this.eat_keyword(kw::Continue) {
+            } else if this.eat_keyword(exp!(Continue)) {
                 this.parse_expr_continue(lo)
-            } else if this.eat_keyword(kw::Break) {
+            } else if this.eat_keyword(exp!(Break)) {
                 this.parse_expr_break()
-            } else if this.eat_keyword(kw::Yield) {
+            } else if this.eat_keyword(exp!(Yield)) {
                 this.parse_expr_yield()
             } else if this.is_do_yeet() {
                 this.parse_expr_yeet()
-            } else if this.eat_keyword(kw::Become) {
+            } else if this.eat_keyword(exp!(Become)) {
                 this.parse_expr_become()
-            } else if this.check_keyword(kw::Let) {
+            } else if this.check_keyword(exp!(Let)) {
                 this.parse_expr_let(restrictions)
-            } else if this.eat_keyword(kw::Underscore) {
+            } else if this.eat_keyword(exp!(Underscore)) {
                 Ok(this.mk_expr(this.prev_token.span, ExprKind::Underscore))
             } else if this.token.uninterpolated_span().at_least_rust_2018() {
                 // `Span::at_least_rust_2018()` is somewhat expensive; don't get it repeatedly.
@@ -1505,11 +1503,11 @@ impl<'a> Parser<'a> {
                     // check for `gen {}` and `gen move {}`
                     // or `async gen {}` and `async gen move {}`
                     && (this.is_gen_block(kw::Gen, 0)
-                        || (this.check_keyword(kw::Async) && this.is_gen_block(kw::Gen, 1)))
+                        || (this.check_keyword(exp!(Async)) && this.is_gen_block(kw::Gen, 1)))
                 {
                     // FIXME: (async) gen closures aren't yet parsed.
                     this.parse_gen_block()
-                } else if this.check_keyword(kw::Async) {
+                } else if this.check_keyword(exp!(Async)) {
                     // FIXME(gen_blocks): Parse `gen async` and suggest swap
                     if this.is_gen_block(kw::Async, 0) {
                         // Check for `async {` and `async move {`,
@@ -1541,15 +1539,20 @@ impl<'a> Parser<'a> {
 
     fn parse_expr_tuple_parens(&mut self, restrictions: Restrictions) -> PResult<'a, P<Expr>> {
         let lo = self.token.span;
-        self.expect(&token::OpenDelim(Delimiter::Parenthesis))?;
+        self.expect(exp!(OpenParen))?;
         let (es, trailing_comma) = match self.parse_seq_to_end(
-            &token::CloseDelim(Delimiter::Parenthesis),
-            SeqSep::trailing_allowed(token::Comma),
+            exp!(CloseParen),
+            SeqSep::trailing_allowed(exp!(Comma)),
             |p| p.parse_expr_catch_underscore(restrictions.intersection(Restrictions::ALLOW_LET)),
         ) {
             Ok(x) => x,
             Err(err) => {
-                return Ok(self.recover_seq_parse_error(Delimiter::Parenthesis, lo, err));
+                return Ok(self.recover_seq_parse_error(
+                    exp!(OpenParen),
+                    exp!(CloseParen),
+                    lo,
+                    err,
+                ));
             }
         };
         let kind = if es.len() == 1 && matches!(trailing_comma, Trailing::No) {
@@ -1563,25 +1566,24 @@ impl<'a> Parser<'a> {
         self.maybe_recover_from_bad_qpath(expr)
     }
 
-    fn parse_expr_array_or_repeat(&mut self, close_delim: Delimiter) -> PResult<'a, P<Expr>> {
+    fn parse_expr_array_or_repeat(&mut self, close: ExpTokenPair<'_>) -> PResult<'a, P<Expr>> {
         let lo = self.token.span;
         self.bump(); // `[` or other open delim
 
-        let close = &token::CloseDelim(close_delim);
         let kind = if self.eat(close) {
             // Empty vector
             ExprKind::Array(ThinVec::new())
         } else {
             // Non-empty vector
             let first_expr = self.parse_expr()?;
-            if self.eat(&token::Semi) {
+            if self.eat(exp!(Semi)) {
                 // Repeating array syntax: `[ 0; 512 ]`
                 let count = self.parse_expr_anon_const()?;
                 self.expect(close)?;
                 ExprKind::Repeat(first_expr, count)
-            } else if self.eat(&token::Comma) {
+            } else if self.eat(exp!(Comma)) {
                 // Vector with two or more elements.
-                let sep = SeqSep::trailing_allowed(token::Comma);
+                let sep = SeqSep::trailing_allowed(exp!(Comma));
                 let (mut exprs, _) = self.parse_seq_to_end(close, sep, |p| p.parse_expr())?;
                 exprs.insert(0, first_expr);
                 ExprKind::Array(exprs)
@@ -1615,7 +1617,7 @@ impl<'a> Parser<'a> {
         };
 
         // `!`, as an operator, is prefix, so we know this isn't that.
-        let (span, kind) = if self.eat(&token::Not) {
+        let (span, kind) = if self.eat(exp!(Not)) {
             // MACRO INVOCATION expression
             if qself.is_some() {
                 self.dcx().emit_err(errors::MacroInvocationWithQualifiedPath(path.span));
@@ -1623,7 +1625,7 @@ impl<'a> Parser<'a> {
             let lo = path.span;
             let mac = P(MacCall { path, args: self.parse_delim_args()? });
             (lo.to(self.prev_token.span), ExprKind::MacCall(mac))
-        } else if self.check(&token::OpenDelim(Delimiter::Brace))
+        } else if self.check(exp!(OpenBrace))
             && let Some(expr) = self.maybe_parse_struct_expr(&qself, &path)
         {
             if qself.is_some() {
@@ -1646,13 +1648,13 @@ impl<'a> Parser<'a> {
     ) -> PResult<'a, P<Expr>> {
         let lo = label_.ident.span;
         let label = Some(label_);
-        let ate_colon = self.eat(&token::Colon);
+        let ate_colon = self.eat(exp!(Colon));
         let tok_sp = self.token.span;
-        let expr = if self.eat_keyword(kw::While) {
+        let expr = if self.eat_keyword(exp!(While)) {
             self.parse_expr_while(label, lo)
-        } else if self.eat_keyword(kw::For) {
+        } else if self.eat_keyword(exp!(For)) {
             self.parse_expr_for(label, lo)
-        } else if self.eat_keyword(kw::Loop) {
+        } else if self.eat_keyword(exp!(Loop)) {
             self.parse_expr_loop(label, lo)
         } else if self.check_noexpect(&token::OpenDelim(Delimiter::Brace))
             || self.token.is_whole_block()
@@ -1958,7 +1960,7 @@ impl<'a> Parser<'a> {
         self.psess.gated_spans.gate(sym::builtin_syntax, ident.span);
         self.bump();
 
-        self.expect(&TokenKind::OpenDelim(Delimiter::Parenthesis))?;
+        self.expect(exp!(OpenParen))?;
         let ret = if let Some(res) = parse(self, lo, ident)? {
             Ok(res)
         } else {
@@ -1968,7 +1970,7 @@ impl<'a> Parser<'a> {
             });
             return Err(err);
         };
-        self.expect(&TokenKind::CloseDelim(Delimiter::Parenthesis))?;
+        self.expect(exp!(CloseParen))?;
 
         ret
     }
@@ -1976,14 +1978,12 @@ impl<'a> Parser<'a> {
     /// Built-in macro for `offset_of!` expressions.
     pub(crate) fn parse_expr_offset_of(&mut self, lo: Span) -> PResult<'a, P<Expr>> {
         let container = self.parse_ty()?;
-        self.expect(&TokenKind::Comma)?;
+        self.expect(exp!(Comma))?;
 
         let fields = self.parse_floating_field_access()?;
         let trailing_comma = self.eat_noexpect(&TokenKind::Comma);
 
-        if let Err(mut e) =
-            self.expect_one_of(&[], &[TokenKind::CloseDelim(Delimiter::Parenthesis)])
-        {
+        if let Err(mut e) = self.expect_one_of(&[], &[exp!(CloseParen)]) {
             if trailing_comma {
                 e.note("unexpected third argument to offset_of");
             } else {
@@ -2006,7 +2006,7 @@ impl<'a> Parser<'a> {
     /// Built-in macro for type ascription expressions.
     pub(crate) fn parse_expr_type_ascribe(&mut self, lo: Span) -> PResult<'a, P<Expr>> {
         let expr = self.parse_expr()?;
-        self.expect(&token::Comma)?;
+        self.expect(exp!(Comma))?;
         let ty = self.parse_ty()?;
         let span = lo.to(self.token.span);
         Ok(self.mk_expr(span, ExprKind::Type(expr, ty)))
@@ -2018,7 +2018,7 @@ impl<'a> Parser<'a> {
         kind: UnsafeBinderCastKind,
     ) -> PResult<'a, P<Expr>> {
         let expr = self.parse_expr()?;
-        let ty = if self.eat(&TokenKind::Comma) { Some(self.parse_ty()?) } else { None };
+        let ty = if self.eat(exp!(Comma)) { Some(self.parse_ty()?) } else { None };
         let span = lo.to(self.token.span);
         Ok(self.mk_expr(span, ExprKind::UnsafeBinderCast(kind, expr, ty)))
     }
@@ -2214,7 +2214,7 @@ impl<'a> Parser<'a> {
         }
 
         let lo = self.token.span;
-        let minus_present = self.eat(&token::BinOp(token::Minus));
+        let minus_present = self.eat(exp!(Minus));
         let (token_lit, span) = self.parse_token_lit()?;
         let expr = self.mk_expr(span, ExprKind::Lit(token_lit));
 
@@ -2236,7 +2236,7 @@ impl<'a> Parser<'a> {
     /// expression.
     fn maybe_suggest_brackets_instead_of_braces(&mut self, lo: Span) -> Option<P<Expr>> {
         let mut snapshot = self.create_snapshot_for_diagnostic();
-        match snapshot.parse_expr_array_or_repeat(Delimiter::Brace) {
+        match snapshot.parse_expr_array_or_repeat(exp!(CloseBrace)) {
             Ok(arr) => {
                 let guar = self.dcx().emit_err(errors::ArrayBracketsInsteadOfSpaces {
                     span: arr.span,
@@ -2272,8 +2272,8 @@ impl<'a> Parser<'a> {
             let mut snapshot = self.create_snapshot_for_diagnostic();
             snapshot.bump();
             match snapshot.parse_seq_to_before_end(
-                &token::CloseDelim(Delimiter::Bracket),
-                SeqSep::trailing_allowed(token::Comma),
+                exp!(CloseBracket),
+                SeqSep::trailing_allowed(exp!(Comma)),
                 |p| p.parse_expr(),
             ) {
                 Ok(_)
@@ -2337,7 +2337,7 @@ impl<'a> Parser<'a> {
         let lo = self.token.span;
 
         let before = self.prev_token.clone();
-        let binder = if self.check_keyword(kw::For) {
+        let binder = if self.check_keyword(exp!(For)) {
             let lo = self.token.span;
             let (lifetime_defs, _) = self.parse_late_bound_lifetime_defs()?;
             let span = lo.to(self.prev_token.span);
@@ -2352,7 +2352,7 @@ impl<'a> Parser<'a> {
         let constness = self.parse_closure_constness();
 
         let movability =
-            if self.eat_keyword(kw::Static) { Movability::Static } else { Movability::Movable };
+            if self.eat_keyword(exp!(Static)) { Movability::Static } else { Movability::Movable };
 
         let coroutine_kind = if self.token.uninterpolated_span().at_least_rust_2018() {
             self.parse_coroutine_kind(Case::Sensitive)
@@ -2433,10 +2433,10 @@ impl<'a> Parser<'a> {
 
     /// Parses an optional `move` prefix to a closure-like construct.
     fn parse_capture_clause(&mut self) -> PResult<'a, CaptureBy> {
-        if self.eat_keyword(kw::Move) {
+        if self.eat_keyword(exp!(Move)) {
             let move_kw_span = self.prev_token.span;
             // Check for `move async` and recover
-            if self.check_keyword(kw::Async) {
+            if self.check_keyword(exp!(Async)) {
                 let move_async_span = self.token.span.with_lo(self.prev_token.span.data().lo);
                 Err(self
                     .dcx()
@@ -2453,15 +2453,15 @@ impl<'a> Parser<'a> {
     fn parse_fn_block_decl(&mut self) -> PResult<'a, (P<FnDecl>, Span)> {
         let arg_start = self.token.span.lo();
 
-        let inputs = if self.eat(&token::OrOr) {
+        let inputs = if self.eat(exp!(OrOr)) {
             ThinVec::new()
         } else {
-            self.expect(&token::BinOp(token::Or))?;
+            self.expect(exp!(Or))?;
             let args = self
                 .parse_seq_to_before_tokens(
-                    &[&token::BinOp(token::Or)],
+                    &[exp!(Or)],
                     &[&token::OrOr],
-                    SeqSep::trailing_allowed(token::Comma),
+                    SeqSep::trailing_allowed(exp!(Comma)),
                     |p| p.parse_fn_block_param(),
                 )?
                 .0;
@@ -2481,7 +2481,7 @@ impl<'a> Parser<'a> {
         let attrs = self.parse_outer_attributes()?;
         self.collect_tokens(None, attrs, ForceCollect::No, |this, attrs| {
             let pat = this.parse_pat_no_top_alt(Some(Expected::ParameterName), None)?;
-            let ty = if this.eat(&token::Colon) {
+            let ty = if this.eat(exp!(Colon)) {
                 this.parse_ty()?
             } else {
                 this.mk_ty(pat.span, TyKind::Infer)
@@ -2566,7 +2566,7 @@ impl<'a> Parser<'a> {
         } else {
             let attrs = self.parse_outer_attributes()?; // For recovery.
             let maybe_fatarrow = self.token.clone();
-            let block = if self.check(&token::OpenDelim(Delimiter::Brace)) {
+            let block = if self.check(exp!(OpenBrace)) {
                 self.parse_block()?
             } else if let Some(block) = recover_block_from_condition(self) {
                 block
@@ -2609,7 +2609,7 @@ impl<'a> Parser<'a> {
             self.error_on_if_block_attrs(lo, false, block.span, attrs);
             block
         };
-        let els = if self.eat_keyword(kw::Else) { Some(self.parse_expr_else()?) } else { None };
+        let els = if self.eat_keyword(exp!(Else)) { Some(self.parse_expr_else()?) } else { None };
         Ok(self.mk_expr(lo.to(self.prev_token.span), ExprKind::If(cond, thn, els)))
     }
 
@@ -2662,7 +2662,7 @@ impl<'a> Parser<'a> {
             });
             self.bump();
         } else {
-            self.expect(&token::Eq)?;
+            self.expect(exp!(Eq))?;
         }
         let attrs = self.parse_outer_attributes()?;
         let (expr, _) =
@@ -2675,9 +2675,9 @@ impl<'a> Parser<'a> {
     fn parse_expr_else(&mut self) -> PResult<'a, P<Expr>> {
         let else_span = self.prev_token.span; // `else`
         let attrs = self.parse_outer_attributes()?; // For recovery.
-        let expr = if self.eat_keyword(kw::If) {
+        let expr = if self.eat_keyword(exp!(If)) {
             ensure_sufficient_stack(|| self.parse_expr_if())?
-        } else if self.check(&TokenKind::OpenDelim(Delimiter::Brace)) {
+        } else if self.check(exp!(OpenBrace)) {
             self.parse_simple_block()?
         } else {
             let snapshot = self.create_snapshot_for_diagnostic();
@@ -2719,7 +2719,7 @@ impl<'a> Parser<'a> {
                 //         while true {}
                 //     }
                 //     ^
-                    if self.check(&TokenKind::OpenDelim(Delimiter::Brace))
+                    if self.check(exp!(OpenBrace))
                         && (classify::expr_requires_semi_to_be_stmt(&cond)
                             || matches!(cond.kind, ExprKind::MacCall(..)))
                     =>
@@ -2805,7 +2805,7 @@ impl<'a> Parser<'a> {
             begin_paren,
         ) {
             (Ok(pat), _) => pat, // Happy path.
-            (Err(err), Some((start_span, left))) if self.eat_keyword(kw::In) => {
+            (Err(err), Some((start_span, left))) if self.eat_keyword(exp!(In)) => {
                 // We know for sure we have seen `for ($SOMETHING in`. In the happy path this would
                 // happen right before the return of this method.
                 let attrs = self.parse_outer_attributes()?;
@@ -2839,7 +2839,7 @@ impl<'a> Parser<'a> {
             }
             (Err(err), _) => return Err(err), // Some other error, bubble up.
         };
-        if !self.eat_keyword(kw::In) {
+        if !self.eat_keyword(exp!(In)) {
             self.error_missing_in_for_loop();
         }
         self.check_for_for_in_in_typo(self.prev_token.span);
@@ -2851,7 +2851,7 @@ impl<'a> Parser<'a> {
     /// Parses `for await? <src_pat> in <src_expr> <src_loop_block>` (`for` token already eaten).
     fn parse_expr_for(&mut self, opt_label: Option<Label>, lo: Span) -> PResult<'a, P<Expr>> {
         let is_await =
-            self.token.uninterpolated_span().at_least_rust_2018() && self.eat_keyword(kw::Await);
+            self.token.uninterpolated_span().at_least_rust_2018() && self.eat_keyword(exp!(Await));
 
         if is_await {
             self.psess.gated_spans.gate(sym::async_for_loop, self.prev_token.span);
@@ -2981,7 +2981,7 @@ impl<'a> Parser<'a> {
         scrutinee: P<Expr>,
         match_kind: MatchKind,
     ) -> PResult<'a, P<Expr>> {
-        if let Err(mut e) = self.expect(&token::OpenDelim(Delimiter::Brace)) {
+        if let Err(mut e) = self.expect(exp!(OpenBrace)) {
             if self.token == token::Semi {
                 e.span_suggestion_short(
                     match_span,
@@ -3121,7 +3121,7 @@ impl<'a> Parser<'a> {
 
             let span_before_body = this.prev_token.span;
             let arm_body;
-            let is_fat_arrow = this.check(&token::FatArrow);
+            let is_fat_arrow = this.check(exp!(FatArrow));
             let is_almost_fat_arrow = TokenKind::FatArrow
                 .similar_tokens()
                 .is_some_and(|similar_tokens| similar_tokens.contains(&this.token.kind));
@@ -3134,17 +3134,15 @@ impl<'a> Parser<'a> {
             let mut result = if armless {
                 // A pattern without a body, allowed for never patterns.
                 arm_body = None;
-                this.expect_one_of(&[token::Comma], &[token::CloseDelim(Delimiter::Brace)]).map(
-                    |x| {
-                        // Don't gate twice
-                        if !pat.contains_never_pattern() {
-                            this.psess.gated_spans.gate(sym::never_patterns, pat.span);
-                        }
-                        x
-                    },
-                )
+                this.expect_one_of(&[exp!(Comma)], &[exp!(CloseBrace)]).map(|x| {
+                    // Don't gate twice
+                    if !pat.contains_never_pattern() {
+                        this.psess.gated_spans.gate(sym::never_patterns, pat.span);
+                    }
+                    x
+                })
             } else {
-                if let Err(mut err) = this.expect(&token::FatArrow) {
+                if let Err(mut err) = this.expect(exp!(FatArrow)) {
                     // We might have a `=>` -> `=` or `->` typo (issue #89396).
                     if is_almost_fat_arrow {
                         err.span_suggestion(
@@ -3184,7 +3182,7 @@ impl<'a> Parser<'a> {
                 if !require_comma {
                     arm_body = Some(expr);
                     // Eat a comma if it exists, though.
-                    let _ = this.eat(&token::Comma);
+                    let _ = this.eat(exp!(Comma));
                     Ok(Recovered::No)
                 } else if let Some((span, guar)) =
                     this.parse_arm_body_missing_braces(&expr, arrow_span)
@@ -3195,42 +3193,40 @@ impl<'a> Parser<'a> {
                 } else {
                     let expr_span = expr.span;
                     arm_body = Some(expr);
-                    this.expect_one_of(&[token::Comma], &[token::CloseDelim(Delimiter::Brace)])
-                        .map_err(|mut err| {
-                            if this.token == token::FatArrow {
-                                let sm = this.psess.source_map();
-                                if let Ok(expr_lines) = sm.span_to_lines(expr_span)
-                                    && let Ok(arm_start_lines) = sm.span_to_lines(arm_start_span)
-                                    && arm_start_lines.lines[0].end_col
-                                        == expr_lines.lines[0].end_col
-                                    && expr_lines.lines.len() == 2
-                                {
-                                    // We check whether there's any trailing code in the parse span,
-                                    // if there isn't, we very likely have the following:
-                                    //
-                                    // X |     &Y => "y"
-                                    //   |        --    - missing comma
-                                    //   |        |
-                                    //   |        arrow_span
-                                    // X |     &X => "x"
-                                    //   |      - ^^ self.token.span
-                                    //   |      |
-                                    //   |      parsed until here as `"y" & X`
-                                    err.span_suggestion_short(
-                                        arm_start_span.shrink_to_hi(),
-                                        "missing a comma here to end this `match` arm",
-                                        ",",
-                                        Applicability::MachineApplicable,
-                                    );
-                                }
-                            } else {
-                                err.span_label(
-                                    arrow_span,
-                                    "while parsing the `match` arm starting here",
+                    this.expect_one_of(&[exp!(Comma)], &[exp!(CloseBrace)]).map_err(|mut err| {
+                        if this.token == token::FatArrow {
+                            let sm = this.psess.source_map();
+                            if let Ok(expr_lines) = sm.span_to_lines(expr_span)
+                                && let Ok(arm_start_lines) = sm.span_to_lines(arm_start_span)
+                                && arm_start_lines.lines[0].end_col == expr_lines.lines[0].end_col
+                                && expr_lines.lines.len() == 2
+                            {
+                                // We check whether there's any trailing code in the parse span,
+                                // if there isn't, we very likely have the following:
+                                //
+                                // X |     &Y => "y"
+                                //   |        --    - missing comma
+                                //   |        |
+                                //   |        arrow_span
+                                // X |     &X => "x"
+                                //   |      - ^^ self.token.span
+                                //   |      |
+                                //   |      parsed until here as `"y" & X`
+                                err.span_suggestion_short(
+                                    arm_start_span.shrink_to_hi(),
+                                    "missing a comma here to end this `match` arm",
+                                    ",",
+                                    Applicability::MachineApplicable,
                                 );
                             }
-                            err
-                        })
+                        } else {
+                            err.span_label(
+                                arrow_span,
+                                "while parsing the `match` arm starting here",
+                            );
+                        }
+                        err
+                    })
                 }
             };
 
@@ -3267,7 +3263,7 @@ impl<'a> Parser<'a> {
                         )
                         .map_err(|err| err.cancel())
                         .is_ok();
-                    if pattern_follows && snapshot.check(&TokenKind::FatArrow) {
+                    if pattern_follows && snapshot.check(exp!(FatArrow)) {
                         err.cancel();
                         let guar = this.dcx().emit_err(errors::MissingCommaAfterMatchArm {
                             span: arm_span.shrink_to_hi(),
@@ -3309,7 +3305,7 @@ impl<'a> Parser<'a> {
                 _ => (false, true),
             }
         }
-        if !self.eat_keyword(kw::If) {
+        if !self.eat_keyword(exp!(If)) {
             // No match arm guard present.
             return Ok(None);
         }
@@ -3384,7 +3380,7 @@ impl<'a> Parser<'a> {
                     // errors.
                     self.recover_stmt_(SemiColonMode::Ignore, BlockMode::Ignore);
                     let msg = "you might have meant to start a match arm after the match guard";
-                    if self.eat(&token::CloseDelim(Delimiter::Brace)) {
+                    if self.eat(exp!(CloseBrace)) {
                         let applicability = if self.token != token::FatArrow {
                             // We have high confidence that we indeed didn't have a struct
                             // literal in the match guard, but rather we had some operation
@@ -3409,7 +3405,7 @@ impl<'a> Parser<'a> {
     /// Parses a `try {...}` expression (`try` token already eaten).
     fn parse_try_block(&mut self, span_lo: Span) -> PResult<'a, P<Expr>> {
         let (attrs, body) = self.parse_inner_attrs_and_block()?;
-        if self.eat_keyword(kw::Catch) {
+        if self.eat_keyword(exp!(Catch)) {
             Err(self.dcx().create_err(errors::CatchAfterTry { span: self.prev_token.span }))
         } else {
             let span = span_lo.to(body.span);
@@ -3440,10 +3436,10 @@ impl<'a> Parser<'a> {
     /// Parses an `async move? {...}` or `gen move? {...}` expression.
     fn parse_gen_block(&mut self) -> PResult<'a, P<Expr>> {
         let lo = self.token.span;
-        let kind = if self.eat_keyword(kw::Async) {
-            if self.eat_keyword(kw::Gen) { GenBlockKind::AsyncGen } else { GenBlockKind::Async }
+        let kind = if self.eat_keyword(exp!(Async)) {
+            if self.eat_keyword(exp!(Gen)) { GenBlockKind::AsyncGen } else { GenBlockKind::Async }
         } else {
-            assert!(self.eat_keyword(kw::Gen));
+            assert!(self.eat_keyword(exp!(Gen)));
             GenBlockKind::Gen
         };
         match kind {
@@ -3504,7 +3500,7 @@ impl<'a> Parser<'a> {
     ) -> Option<PResult<'a, P<Expr>>> {
         let struct_allowed = !self.restrictions.contains(Restrictions::NO_STRUCT_LITERAL);
         if struct_allowed || self.is_certainly_not_a_block() {
-            if let Err(err) = self.expect(&token::OpenDelim(Delimiter::Brace)) {
+            if let Err(err) = self.expect(exp!(OpenBrace)) {
                 return Some(Err(err));
             }
             let expr = self.parse_expr_struct(qself.clone(), path.clone(), true);
@@ -3527,7 +3523,7 @@ impl<'a> Parser<'a> {
         &mut self,
         pth: ast::Path,
         recover: bool,
-        close_delim: Delimiter,
+        close: ExpTokenPair<'_>,
     ) -> PResult<
         'a,
         (
@@ -3546,11 +3542,11 @@ impl<'a> Parser<'a> {
             errors::HelpUseLatestEdition::new().add_to_diag(e);
         };
 
-        while self.token != token::CloseDelim(close_delim) {
-            if self.eat(&token::DotDot) || self.recover_struct_field_dots(close_delim) {
+        while self.token != *close.tok {
+            if self.eat(exp!(DotDot)) || self.recover_struct_field_dots(close.tok) {
                 let exp_span = self.prev_token.span;
                 // We permit `.. }` on the left-hand side of a destructuring assignment.
-                if self.check(&token::CloseDelim(close_delim)) {
+                if self.check(close) {
                     base = ast::StructRest::Rest(self.prev_token.span);
                     break;
                 }
@@ -3625,7 +3621,7 @@ impl<'a> Parser<'a> {
                             Applicability::MaybeIncorrect,
                         );
                     }
-                    if in_if_guard && close_delim == Delimiter::Brace {
+                    if in_if_guard && close.token_type == TokenType::CloseBrace {
                         return Err(e);
                     }
 
@@ -3655,9 +3651,9 @@ impl<'a> Parser<'a> {
             let is_shorthand = parsed_field.as_ref().is_ok_and(|f| f.is_shorthand);
             // A shorthand field can be turned into a full field with `:`.
             // We should point this out.
-            self.check_or_expected(!is_shorthand, TokenType::Token(token::Colon));
+            self.check_or_expected(!is_shorthand, TokenType::Colon);
 
-            match self.expect_one_of(&[token::Comma], &[token::CloseDelim(close_delim)]) {
+            match self.expect_one_of(&[exp!(Comma)], &[close]) {
                 Ok(_) => {
                     if let Ok(f) = parsed_field.or_else(|guar| field_ident(self, guar).ok_or(guar))
                     {
@@ -3689,7 +3685,7 @@ impl<'a> Parser<'a> {
                         fields.push(f);
                     }
                     self.recover_stmt_(SemiColonMode::Comma, BlockMode::Ignore);
-                    let _ = self.eat(&token::Comma);
+                    let _ = self.eat(exp!(Comma));
                 }
             }
         }
@@ -3705,9 +3701,9 @@ impl<'a> Parser<'a> {
     ) -> PResult<'a, P<Expr>> {
         let lo = pth.span;
         let (fields, base, recovered_async) =
-            self.parse_struct_fields(pth.clone(), recover, Delimiter::Brace)?;
+            self.parse_struct_fields(pth.clone(), recover, exp!(CloseBrace))?;
         let span = lo.to(self.token.span);
-        self.expect(&token::CloseDelim(Delimiter::Brace))?;
+        self.expect(exp!(CloseBrace))?;
         let expr = if let Some(guar) = recovered_async {
             ExprKind::Err(guar)
         } else {
@@ -3727,10 +3723,8 @@ impl<'a> Parser<'a> {
         self.recover_stmt();
     }
 
-    fn recover_struct_field_dots(&mut self, close_delim: Delimiter) -> bool {
-        if !self.look_ahead(1, |t| *t == token::CloseDelim(close_delim))
-            && self.eat(&token::DotDotDot)
-        {
+    fn recover_struct_field_dots(&mut self, close: &TokenKind) -> bool {
+        if !self.look_ahead(1, |t| t == close) && self.eat(exp!(DotDotDot)) {
             // recover from typo of `...`, suggest `..`
             let span = self.prev_token.span;
             self.dcx().emit_err(errors::MissingDotDot { token_span: span, sugg_span: span });
diff --git a/compiler/rustc_parse/src/parser/generics.rs b/compiler/rustc_parse/src/parser/generics.rs
index 65e390c9a82..b1b84b0b701 100644
--- a/compiler/rustc_parse/src/parser/generics.rs
+++ b/compiler/rustc_parse/src/parser/generics.rs
@@ -13,6 +13,7 @@ use crate::errors::{
     UnexpectedSelfInGenericParameters, WhereClauseBeforeTupleStructBody,
     WhereClauseBeforeTupleStructBodySugg,
 };
+use crate::exp;
 
 enum PredicateKindOrStructBody {
     PredicateKind(ast::WherePredicateKind),
@@ -52,7 +53,7 @@ impl<'a> Parser<'a> {
 
         // Parse optional colon and param bounds.
         let mut colon_span = None;
-        let bounds = if self.eat(&token::Colon) {
+        let bounds = if self.eat(exp!(Colon)) {
             colon_span = Some(self.prev_token.span);
             // recover from `impl Trait` in type param bound
             if self.token.is_keyword(kw::Impl) {
@@ -89,7 +90,7 @@ impl<'a> Parser<'a> {
             Vec::new()
         };
 
-        let default = if self.eat(&token::Eq) { Some(self.parse_ty()?) } else { None };
+        let default = if self.eat(exp!(Eq)) { Some(self.parse_ty()?) } else { None };
         Ok(GenericParam {
             ident,
             id: ast::DUMMY_NODE_ID,
@@ -107,13 +108,13 @@ impl<'a> Parser<'a> {
     ) -> PResult<'a, GenericParam> {
         let const_span = self.token.span;
 
-        self.expect_keyword(kw::Const)?;
+        self.expect_keyword(exp!(Const))?;
         let ident = self.parse_ident()?;
-        self.expect(&token::Colon)?;
+        self.expect(exp!(Colon))?;
         let ty = self.parse_ty()?;
 
         // Parse optional const generics default value.
-        let default = if self.eat(&token::Eq) { Some(self.parse_const_arg()?) } else { None };
+        let default = if self.eat(exp!(Eq)) { Some(self.parse_const_arg()?) } else { None };
 
         Ok(GenericParam {
             ident,
@@ -132,11 +133,11 @@ impl<'a> Parser<'a> {
         mistyped_const_ident: Ident,
     ) -> PResult<'a, GenericParam> {
         let ident = self.parse_ident()?;
-        self.expect(&token::Colon)?;
+        self.expect(exp!(Colon))?;
         let ty = self.parse_ty()?;
 
         // Parse optional const generics default value.
-        let default = if self.eat(&token::Eq) { Some(self.parse_const_arg()?) } else { None };
+        let default = if self.eat(exp!(Eq)) { Some(self.parse_const_arg()?) } else { None };
 
         self.dcx()
             .struct_span_err(
@@ -177,13 +178,13 @@ impl<'a> Parser<'a> {
                         .emit_err(UnexpectedSelfInGenericParameters { span: this.prev_token.span });
 
                     // Eat a trailing comma, if it exists.
-                    let _ = this.eat(&token::Comma);
+                    let _ = this.eat(exp!(Comma));
                 }
 
                 let param = if this.check_lifetime() {
                     let lifetime = this.expect_lifetime();
                     // Parse lifetime parameter.
-                    let (colon_span, bounds) = if this.eat(&token::Colon) {
+                    let (colon_span, bounds) = if this.eat(exp!(Colon)) {
                         (Some(this.prev_token.span), this.parse_lt_param_bounds())
                     } else {
                         (None, Vec::new())
@@ -209,7 +210,7 @@ impl<'a> Parser<'a> {
                         is_placeholder: false,
                         colon_span,
                     })
-                } else if this.check_keyword(kw::Const) {
+                } else if this.check_keyword(exp!(Const)) {
                     // Parse const parameter.
                     Some(this.parse_const_param(attrs)?)
                 } else if this.check_ident() {
@@ -246,7 +247,7 @@ impl<'a> Parser<'a> {
                     return Ok((None, Trailing::No, UsePreAttrPos::No));
                 };
 
-                if !this.eat(&token::Comma) {
+                if !this.eat(exp!(Comma)) {
                     done = true;
                 }
                 // We just ate the comma, so no need to capture the trailing token.
@@ -324,7 +325,7 @@ impl<'a> Parser<'a> {
         };
         let mut tuple_struct_body = None;
 
-        if !self.eat_keyword(kw::Where) {
+        if !self.eat_keyword(exp!(Where)) {
             return Ok((where_clause, None));
         }
         where_clause.has_where_token = true;
@@ -344,7 +345,7 @@ impl<'a> Parser<'a> {
             let kind = if self.check_lifetime() && self.look_ahead(1, |t| !t.is_like_plus()) {
                 let lifetime = self.expect_lifetime();
                 // Bounds starting with a colon are mandatory, but possibly empty.
-                self.expect(&token::Colon)?;
+                self.expect(exp!(Colon))?;
                 let bounds = self.parse_lt_param_bounds();
                 ast::WherePredicateKind::RegionPredicate(ast::WhereRegionPredicate {
                     lifetime,
@@ -370,7 +371,7 @@ impl<'a> Parser<'a> {
             });
 
             let prev_token = self.prev_token.span;
-            let ate_comma = self.eat(&token::Comma);
+            let ate_comma = self.eat(exp!(Comma));
 
             if self.eat_keyword_noexpect(kw::Where) {
                 self.dcx().emit_err(MultipleWhereClauses {
@@ -464,7 +465,7 @@ impl<'a> Parser<'a> {
         // Parse type with mandatory colon and (possibly empty) bounds,
         // or with mandatory equality sign and the second type.
         let ty = self.parse_ty_for_where_clause()?;
-        if self.eat(&token::Colon) {
+        if self.eat(exp!(Colon)) {
             let bounds = self.parse_generic_bounds()?;
             Ok(ast::WherePredicateKind::BoundPredicate(ast::WhereBoundPredicate {
                 bound_generic_params: lifetime_defs,
@@ -473,7 +474,7 @@ impl<'a> Parser<'a> {
             }))
         // FIXME: Decide what should be used here, `=` or `==`.
         // FIXME: We are just dropping the binders in lifetime_defs on the floor here.
-        } else if self.eat(&token::Eq) || self.eat(&token::EqEq) {
+        } else if self.eat(exp!(Eq)) || self.eat(exp!(EqEq)) {
             let rhs_ty = self.parse_ty()?;
             Ok(ast::WherePredicateKind::EqPredicate(ast::WhereEqPredicate { lhs_ty: ty, rhs_ty }))
         } else {
diff --git a/compiler/rustc_parse/src/parser/item.rs b/compiler/rustc_parse/src/parser/item.rs
index 58fc90f3939..f3e56be9f6e 100644
--- a/compiler/rustc_parse/src/parser/item.rs
+++ b/compiler/rustc_parse/src/parser/item.rs
@@ -20,29 +20,29 @@ use tracing::debug;
 use super::diagnostics::{ConsumeClosingDelim, dummy_arg};
 use super::ty::{AllowPlus, RecoverQPath, RecoverReturnSign};
 use super::{
-    AttrWrapper, FollowedByType, ForceCollect, Parser, PathStyle, Trailing, UsePreAttrPos,
+    AttrWrapper, ExpKeywordPair, ExpTokenPair, FollowedByType, ForceCollect, Parser, PathStyle,
+    Trailing, UsePreAttrPos,
 };
 use crate::errors::{self, MacroExpandsToAdtField};
-use crate::{fluent_generated as fluent, maybe_whole};
+use crate::{exp, fluent_generated as fluent, maybe_whole};
 
 impl<'a> Parser<'a> {
     /// Parses a source module as a crate. This is the main entry point for the parser.
     pub fn parse_crate_mod(&mut self) -> PResult<'a, ast::Crate> {
-        let (attrs, items, spans) = self.parse_mod(&token::Eof)?;
+        let (attrs, items, spans) = self.parse_mod(exp!(Eof))?;
         Ok(ast::Crate { attrs, items, spans, id: DUMMY_NODE_ID, is_placeholder: false })
     }
 
     /// Parses a `mod <foo> { ... }` or `mod <foo>;` item.
     fn parse_item_mod(&mut self, attrs: &mut AttrVec) -> PResult<'a, ItemInfo> {
         let safety = self.parse_safety(Case::Sensitive);
-        self.expect_keyword(kw::Mod)?;
+        self.expect_keyword(exp!(Mod))?;
         let id = self.parse_ident()?;
-        let mod_kind = if self.eat(&token::Semi) {
+        let mod_kind = if self.eat(exp!(Semi)) {
             ModKind::Unloaded
         } else {
-            self.expect(&token::OpenDelim(Delimiter::Brace))?;
-            let (inner_attrs, items, inner_span) =
-                self.parse_mod(&token::CloseDelim(Delimiter::Brace))?;
+            self.expect(exp!(OpenBrace))?;
+            let (inner_attrs, items, inner_span) = self.parse_mod(exp!(CloseBrace))?;
             attrs.extend(inner_attrs);
             ModKind::Loaded(items, Inline::Yes, inner_span, Ok(()))
         };
@@ -55,7 +55,7 @@ impl<'a> Parser<'a> {
     /// - `}` for mod items
     pub fn parse_mod(
         &mut self,
-        term: &TokenKind,
+        term: ExpTokenPair<'_>,
     ) -> PResult<'a, (AttrVec, ThinVec<P<Item>>, ModSpans)> {
         let lo = self.token.span;
         let attrs = self.parse_inner_attributes()?;
@@ -209,15 +209,15 @@ impl<'a> Parser<'a> {
         let check_pub = def == &Defaultness::Final;
         let mut def_ = || mem::replace(def, Defaultness::Final);
 
-        let info = if self.eat_keyword_case(kw::Use, case) {
+        let info = if self.eat_keyword_case(exp!(Use), case) {
             self.parse_use_item()?
         } else if self.check_fn_front_matter(check_pub, case) {
             // FUNCTION ITEM
             let (ident, sig, generics, body) =
                 self.parse_fn(attrs, fn_parse_mode, lo, vis, case)?;
             (ident, ItemKind::Fn(Box::new(Fn { defaultness: def_(), sig, generics, body })))
-        } else if self.eat_keyword(kw::Extern) {
-            if self.eat_keyword(kw::Crate) {
+        } else if self.eat_keyword(exp!(Extern)) {
+            if self.eat_keyword(exp!(Crate)) {
                 // EXTERN CRATE
                 self.parse_item_extern_crate()?
             } else {
@@ -227,7 +227,7 @@ impl<'a> Parser<'a> {
         } else if self.is_unsafe_foreign_mod() {
             // EXTERN BLOCK
             let safety = self.parse_safety(Case::Sensitive);
-            self.expect_keyword(kw::Extern)?;
+            self.expect_keyword(exp!(Extern))?;
             self.parse_item_foreign_mod(attrs, safety)?
         } else if self.is_static_global() {
             let safety = self.parse_safety(Case::Sensitive);
@@ -255,28 +255,28 @@ impl<'a> Parser<'a> {
                     })),
                 )
             }
-        } else if self.check_keyword(kw::Trait) || self.check_auto_or_unsafe_trait_item() {
+        } else if self.check_keyword(exp!(Trait)) || self.check_auto_or_unsafe_trait_item() {
             // TRAIT ITEM
             self.parse_item_trait(attrs, lo)?
-        } else if self.check_keyword(kw::Impl)
-            || self.check_keyword(kw::Unsafe) && self.is_keyword_ahead(1, &[kw::Impl])
+        } else if self.check_keyword(exp!(Impl))
+            || self.check_keyword(exp!(Unsafe)) && self.is_keyword_ahead(1, &[kw::Impl])
         {
             // IMPL ITEM
             self.parse_item_impl(attrs, def_())?
         } else if self.is_reuse_path_item() {
             self.parse_item_delegation()?
-        } else if self.check_keyword(kw::Mod)
-            || self.check_keyword(kw::Unsafe) && self.is_keyword_ahead(1, &[kw::Mod])
+        } else if self.check_keyword(exp!(Mod))
+            || self.check_keyword(exp!(Unsafe)) && self.is_keyword_ahead(1, &[kw::Mod])
         {
             // MODULE ITEM
             self.parse_item_mod(attrs)?
-        } else if self.eat_keyword(kw::Type) {
+        } else if self.eat_keyword(exp!(Type)) {
             // TYPE ITEM
             self.parse_type_alias(def_())?
-        } else if self.eat_keyword(kw::Enum) {
+        } else if self.eat_keyword(exp!(Enum)) {
             // ENUM ITEM
             self.parse_item_enum()?
-        } else if self.eat_keyword(kw::Struct) {
+        } else if self.eat_keyword(exp!(Struct)) {
             // STRUCT ITEM
             self.parse_item_struct()?
         } else if self.is_kw_followed_by_ident(kw::Union) {
@@ -286,7 +286,7 @@ impl<'a> Parser<'a> {
         } else if self.is_builtin() {
             // BUILTIN# ITEM
             return self.parse_item_builtin();
-        } else if self.eat_keyword(kw::Macro) {
+        } else if self.eat_keyword(exp!(Macro)) {
             // MACROS 2.0 ITEM
             self.parse_item_decl_macro(lo)?
         } else if let IsMacroRulesItem::Yes { has_bang } = self.is_macro_rules_item() {
@@ -407,13 +407,13 @@ impl<'a> Parser<'a> {
         };
 
         let mut found_generics = false;
-        if self.check(&token::Lt) {
+        if self.check(exp!(Lt)) {
             found_generics = true;
-            self.eat_to_tokens(&[&token::Gt]);
+            self.eat_to_tokens(&[exp!(Gt)]);
             self.bump(); // `>`
         }
 
-        let err = if self.check(&token::OpenDelim(Delimiter::Brace)) {
+        let err = if self.check(exp!(OpenBrace)) {
             // possible struct or enum definition where `struct` or `enum` was forgotten
             if self.look_ahead(1, |t| *t == token::CloseDelim(Delimiter::Brace)) {
                 // `S {}` could be unit enum or struct
@@ -426,25 +426,23 @@ impl<'a> Parser<'a> {
             } else {
                 Some(errors::MissingKeywordForItemDefinition::Enum { span, insert_span, ident })
             }
-        } else if self.check(&token::OpenDelim(Delimiter::Parenthesis)) {
+        } else if self.check(exp!(OpenParen)) {
             // possible function or tuple struct definition where `fn` or `struct` was forgotten
             self.bump(); // `(`
             let is_method = self.recover_self_param();
 
-            self.consume_block(Delimiter::Parenthesis, ConsumeClosingDelim::Yes);
+            self.consume_block(exp!(OpenParen), exp!(CloseParen), ConsumeClosingDelim::Yes);
 
-            let err = if self.check(&token::RArrow)
-                || self.check(&token::OpenDelim(Delimiter::Brace))
-            {
-                self.eat_to_tokens(&[&token::OpenDelim(Delimiter::Brace)]);
+            let err = if self.check(exp!(RArrow)) || self.check(exp!(OpenBrace)) {
+                self.eat_to_tokens(&[exp!(OpenBrace)]);
                 self.bump(); // `{`
-                self.consume_block(Delimiter::Brace, ConsumeClosingDelim::Yes);
+                self.consume_block(exp!(OpenBrace), exp!(CloseBrace), ConsumeClosingDelim::Yes);
                 if is_method {
                     errors::MissingKeywordForItemDefinition::Method { span, insert_span, ident }
                 } else {
                     errors::MissingKeywordForItemDefinition::Function { span, insert_span, ident }
                 }
-            } else if is_pub && self.check(&token::Semi) {
+            } else if is_pub && self.check(exp!(Semi)) {
                 errors::MissingKeywordForItemDefinition::Struct { span, insert_span, ident }
             } else {
                 errors::MissingKeywordForItemDefinition::Ambiguous {
@@ -479,7 +477,7 @@ impl<'a> Parser<'a> {
     /// Parses an item macro, e.g., `item!();`.
     fn parse_item_macro(&mut self, vis: &Visibility) -> PResult<'a, MacCall> {
         let path = self.parse_path(PathStyle::Mod)?; // `foo::bar`
-        self.expect(&token::Not)?; // `!`
+        self.expect(exp!(Not))?; // `!`
         match self.parse_delim_args() {
             // `( .. )` or `[ .. ]` (followed by `;`), or `{ .. }`.
             Ok(args) => {
@@ -539,7 +537,7 @@ impl<'a> Parser<'a> {
 
     fn parse_polarity(&mut self) -> ast::ImplPolarity {
         // Disambiguate `impl !Trait for Type { ... }` and `impl ! { ... }` for the never type.
-        if self.check(&token::Not) && self.look_ahead(1, |t| t.can_begin_type()) {
+        if self.check(exp!(Not)) && self.look_ahead(1, |t| t.can_begin_type()) {
             self.bump(); // `!`
             ast::ImplPolarity::Negative(self.prev_token.span)
         } else {
@@ -567,7 +565,7 @@ impl<'a> Parser<'a> {
         defaultness: Defaultness,
     ) -> PResult<'a, ItemInfo> {
         let safety = self.parse_safety(Case::Sensitive);
-        self.expect_keyword(kw::Impl)?;
+        self.expect_keyword(exp!(Impl))?;
 
         // First, parse generic parameters if necessary.
         let mut generics = if self.choose_generics_over_qpath(0) {
@@ -617,7 +615,7 @@ impl<'a> Parser<'a> {
         };
 
         // If `for` is missing we try to recover.
-        let has_for = self.eat_keyword(kw::For);
+        let has_for = self.eat_keyword(exp!(For));
         let missing_for_span = self.prev_token.span.between(self.token.span);
 
         let ty_second = if self.token == token::DotDot {
@@ -702,7 +700,7 @@ impl<'a> Parser<'a> {
 
     fn parse_item_delegation(&mut self) -> PResult<'a, ItemInfo> {
         let span = self.token.span;
-        self.expect_keyword(kw::Reuse)?;
+        self.expect_keyword(exp!(Reuse))?;
 
         let (qself, path) = if self.eat_lt() {
             let (qself, path) = self.parse_qpath(PathStyle::Expr)?;
@@ -712,23 +710,23 @@ impl<'a> Parser<'a> {
         };
 
         let rename = |this: &mut Self| {
-            Ok(if this.eat_keyword(kw::As) { Some(this.parse_ident()?) } else { None })
+            Ok(if this.eat_keyword(exp!(As)) { Some(this.parse_ident()?) } else { None })
         };
         let body = |this: &mut Self| {
-            Ok(if this.check(&token::OpenDelim(Delimiter::Brace)) {
+            Ok(if this.check(exp!(OpenBrace)) {
                 Some(this.parse_block()?)
             } else {
-                this.expect(&token::Semi)?;
+                this.expect(exp!(Semi))?;
                 None
             })
         };
 
         let (ident, item_kind) = if self.eat_path_sep() {
-            let suffixes = if self.eat(&token::BinOp(token::Star)) {
+            let suffixes = if self.eat(exp!(Star)) {
                 None
             } else {
                 let parse_suffix = |p: &mut Self| Ok((p.parse_path_segment_ident()?, rename(p)?));
-                Some(self.parse_delim_comma_seq(Delimiter::Brace, parse_suffix)?.0)
+                Some(self.parse_delim_comma_seq(exp!(OpenBrace), exp!(CloseBrace), parse_suffix)?.0)
             };
             let deleg = DelegationMac { qself, prefix: path, suffixes, body: body(self)? };
             (Ident::empty(), ItemKind::DelegationMac(Box::new(deleg)))
@@ -766,11 +764,11 @@ impl<'a> Parser<'a> {
             return Ok(ThinVec::new());
         }
 
-        self.expect(&token::OpenDelim(Delimiter::Brace))?;
+        self.expect(exp!(OpenBrace))?;
         attrs.extend(self.parse_inner_attributes()?);
 
         let mut items = ThinVec::new();
-        while !self.eat(&token::CloseDelim(Delimiter::Brace)) {
+        while !self.eat(exp!(CloseBrace)) {
             if self.recover_doc_comment_before_brace() {
                 continue;
             }
@@ -811,7 +809,7 @@ impl<'a> Parser<'a> {
 
                     let mut err =
                         self.dcx().struct_span_err(non_item_span, "non-item in item list");
-                    self.consume_block(Delimiter::Brace, ConsumeClosingDelim::Yes);
+                    self.consume_block(exp!(OpenBrace), exp!(CloseBrace), ConsumeClosingDelim::Yes);
                     if is_let {
                         err.span_suggestion_verbose(
                             non_item_span,
@@ -837,7 +835,7 @@ impl<'a> Parser<'a> {
                 }
                 Ok(Some(item)) => items.extend(item),
                 Err(err) => {
-                    self.consume_block(Delimiter::Brace, ConsumeClosingDelim::Yes);
+                    self.consume_block(exp!(OpenBrace), exp!(CloseBrace), ConsumeClosingDelim::Yes);
                     err.with_span_label(
                         open_brace_span,
                         "while parsing this item list starting here",
@@ -880,7 +878,7 @@ impl<'a> Parser<'a> {
         // We are interested in `default` followed by another identifier.
         // However, we must avoid keywords that occur as binary operators.
         // Currently, the only applicable keyword is `as` (`default as Ty`).
-        if self.check_keyword(kw::Default)
+        if self.check_keyword(exp!(Default))
             && self.look_ahead(1, |t| t.is_non_raw_ident_where(|i| i.name != kw::As))
         {
             self.bump(); // `default`
@@ -893,33 +891,33 @@ impl<'a> Parser<'a> {
     /// Is this an `(unsafe auto? | auto) trait` item?
     fn check_auto_or_unsafe_trait_item(&mut self) -> bool {
         // auto trait
-        self.check_keyword(kw::Auto) && self.is_keyword_ahead(1, &[kw::Trait])
+        self.check_keyword(exp!(Auto)) && self.is_keyword_ahead(1, &[kw::Trait])
             // unsafe auto trait
-            || self.check_keyword(kw::Unsafe) && self.is_keyword_ahead(1, &[kw::Trait, kw::Auto])
+            || self.check_keyword(exp!(Unsafe)) && self.is_keyword_ahead(1, &[kw::Trait, kw::Auto])
     }
 
     /// Parses `unsafe? auto? trait Foo { ... }` or `trait Foo = Bar;`.
     fn parse_item_trait(&mut self, attrs: &mut AttrVec, lo: Span) -> PResult<'a, ItemInfo> {
         let safety = self.parse_safety(Case::Sensitive);
         // Parse optional `auto` prefix.
-        let is_auto = if self.eat_keyword(kw::Auto) {
+        let is_auto = if self.eat_keyword(exp!(Auto)) {
             self.psess.gated_spans.gate(sym::auto_traits, self.prev_token.span);
             IsAuto::Yes
         } else {
             IsAuto::No
         };
 
-        self.expect_keyword(kw::Trait)?;
+        self.expect_keyword(exp!(Trait))?;
         let ident = self.parse_ident()?;
         let mut generics = self.parse_generics()?;
 
         // Parse optional colon and supertrait bounds.
-        let had_colon = self.eat(&token::Colon);
+        let had_colon = self.eat(exp!(Colon));
         let span_at_colon = self.prev_token.span;
         let bounds = if had_colon { self.parse_generic_bounds()? } else { Vec::new() };
 
         let span_before_eq = self.prev_token.span;
-        if self.eat(&token::Eq) {
+        if self.eat(exp!(Eq)) {
             // It's a trait alias.
             if had_colon {
                 let span = span_at_colon.to(span_before_eq);
@@ -1007,11 +1005,10 @@ impl<'a> Parser<'a> {
         let mut generics = self.parse_generics()?;
 
         // Parse optional colon and param bounds.
-        let bounds =
-            if self.eat(&token::Colon) { self.parse_generic_bounds()? } else { Vec::new() };
+        let bounds = if self.eat(exp!(Colon)) { self.parse_generic_bounds()? } else { Vec::new() };
         let before_where_clause = self.parse_where_clause()?;
 
-        let ty = if self.eat(&token::Eq) { Some(self.parse_ty()?) } else { None };
+        let ty = if self.eat(exp!(Eq)) { Some(self.parse_ty()?) } else { None };
 
         let after_where_clause = self.parse_where_clause()?;
 
@@ -1064,46 +1061,44 @@ impl<'a> Parser<'a> {
 
         let mut prefix =
             ast::Path { segments: ThinVec::new(), span: lo.shrink_to_lo(), tokens: None };
-        let kind = if self.check(&token::OpenDelim(Delimiter::Brace))
-            || self.check(&token::BinOp(token::Star))
-            || self.is_import_coupler()
-        {
-            // `use *;` or `use ::*;` or `use {...};` or `use ::{...};`
-            let mod_sep_ctxt = self.token.span.ctxt();
-            if self.eat_path_sep() {
-                prefix
-                    .segments
-                    .push(PathSegment::path_root(lo.shrink_to_lo().with_ctxt(mod_sep_ctxt)));
-            }
-
-            self.parse_use_tree_glob_or_nested()?
-        } else {
-            // `use path::*;` or `use path::{...};` or `use path;` or `use path as bar;`
-            prefix = self.parse_path(PathStyle::Mod)?;
+        let kind =
+            if self.check(exp!(OpenBrace)) || self.check(exp!(Star)) || self.is_import_coupler() {
+                // `use *;` or `use ::*;` or `use {...};` or `use ::{...};`
+                let mod_sep_ctxt = self.token.span.ctxt();
+                if self.eat_path_sep() {
+                    prefix
+                        .segments
+                        .push(PathSegment::path_root(lo.shrink_to_lo().with_ctxt(mod_sep_ctxt)));
+                }
 
-            if self.eat_path_sep() {
                 self.parse_use_tree_glob_or_nested()?
             } else {
-                // Recover from using a colon as path separator.
-                while self.eat_noexpect(&token::Colon) {
-                    self.dcx()
-                        .emit_err(errors::SingleColonImportPath { span: self.prev_token.span });
-
-                    // We parse the rest of the path and append it to the original prefix.
-                    self.parse_path_segments(&mut prefix.segments, PathStyle::Mod, None)?;
-                    prefix.span = lo.to(self.prev_token.span);
-                }
+                // `use path::*;` or `use path::{...};` or `use path;` or `use path as bar;`
+                prefix = self.parse_path(PathStyle::Mod)?;
 
-                UseTreeKind::Simple(self.parse_rename()?)
-            }
-        };
+                if self.eat_path_sep() {
+                    self.parse_use_tree_glob_or_nested()?
+                } else {
+                    // Recover from using a colon as path separator.
+                    while self.eat_noexpect(&token::Colon) {
+                        self.dcx()
+                            .emit_err(errors::SingleColonImportPath { span: self.prev_token.span });
+
+                        // We parse the rest of the path and append it to the original prefix.
+                        self.parse_path_segments(&mut prefix.segments, PathStyle::Mod, None)?;
+                        prefix.span = lo.to(self.prev_token.span);
+                    }
+
+                    UseTreeKind::Simple(self.parse_rename()?)
+                }
+            };
 
         Ok(UseTree { prefix, kind, span: lo.to(self.prev_token.span) })
     }
 
     /// Parses `*` or `{...}`.
     fn parse_use_tree_glob_or_nested(&mut self) -> PResult<'a, UseTreeKind> {
-        Ok(if self.eat(&token::BinOp(token::Star)) {
+        Ok(if self.eat(exp!(Star)) {
             UseTreeKind::Glob
         } else {
             let lo = self.token.span;
@@ -1120,7 +1115,7 @@ impl<'a> Parser<'a> {
     /// USE_TREE_LIST = ∅ | (USE_TREE `,`)* USE_TREE [`,`]
     /// ```
     fn parse_use_tree_list(&mut self) -> PResult<'a, ThinVec<(UseTree, ast::NodeId)>> {
-        self.parse_delim_comma_seq(Delimiter::Brace, |p| {
+        self.parse_delim_comma_seq(exp!(OpenBrace), exp!(CloseBrace), |p| {
             p.recover_vcs_conflict_marker();
             Ok((p.parse_use_tree()?, DUMMY_NODE_ID))
         })
@@ -1128,7 +1123,11 @@ impl<'a> Parser<'a> {
     }
 
     fn parse_rename(&mut self) -> PResult<'a, Option<Ident>> {
-        if self.eat_keyword(kw::As) { self.parse_ident_or_underscore().map(Some) } else { Ok(None) }
+        if self.eat_keyword(exp!(As)) {
+            self.parse_ident_or_underscore().map(Some)
+        } else {
+            Ok(None)
+        }
     }
 
     fn parse_ident_or_underscore(&mut self) -> PResult<'a, Ident> {
@@ -1168,15 +1167,15 @@ impl<'a> Parser<'a> {
             self.parse_ident()
         }?;
 
-        let dash = token::BinOp(token::BinOpToken::Minus);
-        if self.token != dash {
+        let dash = exp!(Minus);
+        if self.token != *dash.tok {
             return Ok(ident);
         }
 
         // Accept `extern crate name-like-this` for better diagnostics.
         let mut dashes = vec![];
         let mut idents = vec![];
-        while self.eat(&dash) {
+        while self.eat(dash) {
             dashes.push(self.prev_token.span);
             idents.push(self.parse_ident()?);
         }
@@ -1217,9 +1216,9 @@ impl<'a> Parser<'a> {
             && self.token.is_keyword(kw::Unsafe)
             && self.look_ahead(1, |t| *t == token::OpenDelim(Delimiter::Brace))
         {
-            self.expect(&token::OpenDelim(Delimiter::Brace)).unwrap_err().emit();
+            self.expect(exp!(OpenBrace)).unwrap_err().emit();
             safety = Safety::Unsafe(self.token.span);
-            let _ = self.eat_keyword(kw::Unsafe);
+            let _ = self.eat_keyword(exp!(Unsafe));
         }
         let module = ast::ForeignMod {
             extern_span,
@@ -1285,7 +1284,7 @@ impl<'a> Parser<'a> {
     }
 
     fn is_static_global(&mut self) -> bool {
-        if self.check_keyword(kw::Static) {
+        if self.check_keyword(exp!(Static)) {
             // Check if this could be a closure.
             !self.look_ahead(1, |token| {
                 if token.is_keyword(kw::Move) {
@@ -1294,20 +1293,19 @@ impl<'a> Parser<'a> {
                 matches!(token.kind, token::BinOp(token::Or) | token::OrOr)
             })
         } else {
-            let quals: &[Symbol] = &[kw::Unsafe, kw::Safe];
             // `$qual static`
-            quals.iter().any(|&kw| self.check_keyword(kw))
+            (self.check_keyword(exp!(Unsafe)) || self.check_keyword(exp!(Safe)))
                 && self.look_ahead(1, |t| t.is_keyword(kw::Static))
         }
     }
 
     /// Recover on `const mut` with `const` already eaten.
     fn recover_const_mut(&mut self, const_span: Span) {
-        if self.eat_keyword(kw::Mut) {
+        if self.eat_keyword(exp!(Mut)) {
             let span = self.prev_token.span;
             self.dcx()
                 .emit_err(errors::ConstGlobalCannotBeMutable { ident_span: span, const_span });
-        } else if self.eat_keyword(kw::Let) {
+        } else if self.eat_keyword(exp!(Let)) {
             let span = self.prev_token.span;
             self.dcx().emit_err(errors::ConstLetMutuallyExclusive { span: const_span.to(span) });
         }
@@ -1372,14 +1370,13 @@ impl<'a> Parser<'a> {
 
         // Parse the type of a static item. That is, the `":" $ty` fragment.
         // FIXME: This could maybe benefit from `.may_recover()`?
-        let ty = match (self.eat(&token::Colon), self.check(&token::Eq) | self.check(&token::Semi))
-        {
+        let ty = match (self.eat(exp!(Colon)), self.check(exp!(Eq)) | self.check(exp!(Semi))) {
             (true, false) => self.parse_ty()?,
             // If there wasn't a `:` or the colon was followed by a `=` or `;`, recover a missing type.
             (colon, _) => self.recover_missing_global_item_type(colon, Some(mutability)),
         };
 
-        let expr = if self.eat(&token::Eq) { Some(self.parse_expr()?) } else { None };
+        let expr = if self.eat(exp!(Eq)) { Some(self.parse_expr()?) } else { None };
 
         self.expect_semi()?;
 
@@ -1405,8 +1402,8 @@ impl<'a> Parser<'a> {
         // Parse the type of a constant item. That is, the `":" $ty` fragment.
         // FIXME: This could maybe benefit from `.may_recover()`?
         let ty = match (
-            self.eat(&token::Colon),
-            self.check(&token::Eq) | self.check(&token::Semi) | self.check_keyword(kw::Where),
+            self.eat(exp!(Colon)),
+            self.check(exp!(Eq)) | self.check(exp!(Semi)) | self.check_keyword(exp!(Where)),
         ) {
             (true, false) => self.parse_ty()?,
             // If there wasn't a `:` or the colon was followed by a `=`, `;` or `where`, recover a missing type.
@@ -1418,7 +1415,7 @@ impl<'a> Parser<'a> {
         let before_where_clause =
             if self.may_recover() { self.parse_where_clause()? } else { WhereClause::default() };
 
-        let expr = if self.eat(&token::Eq) { Some(self.parse_expr()?) } else { None };
+        let expr = if self.eat(exp!(Eq)) { Some(self.parse_expr()?) } else { None };
 
         let after_where_clause = self.parse_where_clause()?;
 
@@ -1531,31 +1528,33 @@ impl<'a> Parser<'a> {
             self.bump();
             (thin_vec![], Trailing::No)
         } else {
-            self.parse_delim_comma_seq(Delimiter::Brace, |p| p.parse_enum_variant(id.span))
-                .map_err(|mut err| {
-                    err.span_label(id.span, "while parsing this enum");
-                    if self.token == token::Colon {
-                        let snapshot = self.create_snapshot_for_diagnostic();
-                        self.bump();
-                        match self.parse_ty() {
-                            Ok(_) => {
-                                err.span_suggestion_verbose(
-                                    prev_span,
-                                    "perhaps you meant to use `struct` here",
-                                    "struct",
-                                    Applicability::MaybeIncorrect,
-                                );
-                            }
-                            Err(e) => {
-                                e.cancel();
-                            }
+            self.parse_delim_comma_seq(exp!(OpenBrace), exp!(CloseBrace), |p| {
+                p.parse_enum_variant(id.span)
+            })
+            .map_err(|mut err| {
+                err.span_label(id.span, "while parsing this enum");
+                if self.token == token::Colon {
+                    let snapshot = self.create_snapshot_for_diagnostic();
+                    self.bump();
+                    match self.parse_ty() {
+                        Ok(_) => {
+                            err.span_suggestion_verbose(
+                                prev_span,
+                                "perhaps you meant to use `struct` here",
+                                "struct",
+                                Applicability::MaybeIncorrect,
+                            );
+                        }
+                        Err(e) => {
+                            e.cancel();
                         }
-                        self.restore_snapshot(snapshot);
                     }
-                    self.eat_to_tokens(&[&token::CloseDelim(Delimiter::Brace)]);
-                    self.bump(); // }
-                    err
-                })?
+                    self.restore_snapshot(snapshot);
+                }
+                self.eat_to_tokens(&[exp!(CloseBrace)]);
+                self.bump(); // }
+                err
+            })?
         };
 
         let enum_definition = EnumDef { variants: variants.into_iter().flatten().collect() };
@@ -1588,7 +1587,7 @@ impl<'a> Parser<'a> {
                 return Ok((None, Trailing::from(this.token == token::Comma), UsePreAttrPos::No));
             }
 
-            let struct_def = if this.check(&token::OpenDelim(Delimiter::Brace)) {
+            let struct_def = if this.check(exp!(OpenBrace)) {
                 // Parse a struct variant.
                 let (fields, recovered) =
                     match this.parse_record_struct_body("struct", ident.span, false) {
@@ -1598,7 +1597,7 @@ impl<'a> Parser<'a> {
                                 // We handle `enum` to `struct` suggestion in the caller.
                                 return Err(err);
                             }
-                            this.eat_to_tokens(&[&token::CloseDelim(Delimiter::Brace)]);
+                            this.eat_to_tokens(&[exp!(CloseBrace)]);
                             this.bump(); // }
                             err.span_label(span, "while parsing this enum");
                             err.help(help);
@@ -1607,7 +1606,7 @@ impl<'a> Parser<'a> {
                         }
                     };
                 VariantData::Struct { fields, recovered }
-            } else if this.check(&token::OpenDelim(Delimiter::Parenthesis)) {
+            } else if this.check(exp!(OpenParen)) {
                 let body = match this.parse_tuple_struct_body() {
                     Ok(body) => body,
                     Err(mut err) => {
@@ -1615,7 +1614,7 @@ impl<'a> Parser<'a> {
                             // We handle `enum` to `struct` suggestion in the caller.
                             return Err(err);
                         }
-                        this.eat_to_tokens(&[&token::CloseDelim(Delimiter::Parenthesis)]);
+                        this.eat_to_tokens(&[exp!(CloseParen)]);
                         this.bump(); // )
                         err.span_label(span, "while parsing this enum");
                         err.help(help);
@@ -1629,7 +1628,7 @@ impl<'a> Parser<'a> {
             };
 
             let disr_expr =
-                if this.eat(&token::Eq) { Some(this.parse_expr_anon_const()?) } else { None };
+                if this.eat(exp!(Eq)) { Some(this.parse_expr_anon_const()?) } else { None };
 
             let vr = ast::Variant {
                 ident,
@@ -1680,7 +1679,7 @@ impl<'a> Parser<'a> {
                 let body = VariantData::Tuple(body, DUMMY_NODE_ID);
                 self.expect_semi()?;
                 body
-            } else if self.eat(&token::Semi) {
+            } else if self.eat(exp!(Semi)) {
                 // If we see a: `struct Foo<T> where T: Copy;` style decl.
                 VariantData::Unit(DUMMY_NODE_ID)
             } else {
@@ -1693,7 +1692,7 @@ impl<'a> Parser<'a> {
                 VariantData::Struct { fields, recovered }
             }
         // No `where` so: `struct Foo<T>;`
-        } else if self.eat(&token::Semi) {
+        } else if self.eat(exp!(Semi)) {
             VariantData::Unit(DUMMY_NODE_ID)
         // Record-style struct definition
         } else if self.token == token::OpenDelim(Delimiter::Brace) {
@@ -1762,14 +1761,18 @@ impl<'a> Parser<'a> {
     ) -> PResult<'a, (ThinVec<FieldDef>, Recovered)> {
         let mut fields = ThinVec::new();
         let mut recovered = Recovered::No;
-        if self.eat(&token::OpenDelim(Delimiter::Brace)) {
+        if self.eat(exp!(OpenBrace)) {
             while self.token != token::CloseDelim(Delimiter::Brace) {
                 match self.parse_field_def(adt_ty) {
                     Ok(field) => {
                         fields.push(field);
                     }
                     Err(mut err) => {
-                        self.consume_block(Delimiter::Brace, ConsumeClosingDelim::No);
+                        self.consume_block(
+                            exp!(OpenBrace),
+                            exp!(CloseBrace),
+                            ConsumeClosingDelim::No,
+                        );
                         err.span_label(ident_span, format!("while parsing this {adt_ty}"));
                         let guar = err.emit();
                         recovered = Recovered::Yes(guar);
@@ -1777,7 +1780,7 @@ impl<'a> Parser<'a> {
                     }
                 }
             }
-            self.expect(&token::CloseDelim(Delimiter::Brace))?;
+            self.expect(exp!(CloseBrace))?;
         } else {
             let token_str = super::token_descr(&self.token);
             let where_str = if parsed_where { "" } else { "`where`, or " };
@@ -1792,7 +1795,7 @@ impl<'a> Parser<'a> {
 
     fn parse_unsafe_field(&mut self) -> Safety {
         // not using parse_safety as that also accepts `safe`.
-        if self.eat_keyword(kw::Unsafe) {
+        if self.eat_keyword(exp!(Unsafe)) {
             let span = self.prev_token.span;
             self.psess.gated_spans.gate(sym::unsafe_fields, span);
             Safety::Unsafe(span)
@@ -1900,7 +1903,7 @@ impl<'a> Parser<'a> {
         if self.token == token::Comma {
             seen_comma = true;
         }
-        if self.eat(&token::Semi) {
+        if self.eat(exp!(Semi)) {
             let sp = self.prev_token.span;
             let mut err =
                 self.dcx().struct_span_err(sp, format!("{adt_ty} fields are separated by `,`"));
@@ -1924,7 +1927,7 @@ impl<'a> Parser<'a> {
                     missing_comma: None,
                 };
                 self.bump(); // consume the doc comment
-                let comma_after_doc_seen = self.eat(&token::Comma);
+                let comma_after_doc_seen = self.eat(exp!(Comma));
                 // `seen_comma` is always false, because we are inside doc block
                 // condition is here to make code more readable
                 if !seen_comma && comma_after_doc_seen {
@@ -1949,13 +1952,13 @@ impl<'a> Parser<'a> {
                 if let TyKind::Path(_, Path { segments, .. }) = &a_var.ty.kind {
                     if let Some(last_segment) = segments.last() {
                         let guar = self.check_trailing_angle_brackets(last_segment, &[
-                            &token::Comma,
-                            &token::CloseDelim(Delimiter::Brace),
+                            exp!(Comma),
+                            exp!(CloseBrace),
                         ]);
                         if let Some(_guar) = guar {
                             // Handle a case like `Vec<u8>>,` where we can continue parsing fields
                             // after the comma
-                            let _ = self.eat(&token::Comma);
+                            let _ = self.eat(exp!(Comma));
 
                             // `check_trailing_angle_brackets` already emitted a nicer error, as
                             // proven by the presence of `_guar`. We can continue parsing.
@@ -1988,7 +1991,7 @@ impl<'a> Parser<'a> {
     }
 
     fn expect_field_ty_separator(&mut self) -> PResult<'a, ()> {
-        if let Err(err) = self.expect(&token::Colon) {
+        if let Err(err) = self.expect(exp!(Colon)) {
             let sm = self.psess.source_map();
             let eq_typo = self.token == token::Eq && self.look_ahead(1, |t| t.is_path_start());
             let semi_typo = self.token == token::Semi
@@ -2096,7 +2099,7 @@ impl<'a> Parser<'a> {
                         self.expected_ident_found_err()
                     }
                 }
-            } else if self.eat_keyword(kw::Struct) {
+            } else if self.eat_keyword(exp!(Struct)) {
                 match self.parse_item_struct() {
                     Ok((ident, _)) => self
                         .dcx()
@@ -2153,12 +2156,12 @@ impl<'a> Parser<'a> {
     /// ```
     fn parse_item_decl_macro(&mut self, lo: Span) -> PResult<'a, ItemInfo> {
         let ident = self.parse_ident()?;
-        let body = if self.check(&token::OpenDelim(Delimiter::Brace)) {
+        let body = if self.check(exp!(OpenBrace)) {
             self.parse_delim_args()? // `MacBody`
-        } else if self.check(&token::OpenDelim(Delimiter::Parenthesis)) {
+        } else if self.check(exp!(OpenParen)) {
             let params = self.parse_token_tree(); // `MacParams`
             let pspan = params.span();
-            if !self.check(&token::OpenDelim(Delimiter::Brace)) {
+            if !self.check(exp!(OpenBrace)) {
                 self.unexpected()?;
             }
             let body = self.parse_token_tree(); // `MacBody`
@@ -2178,7 +2181,7 @@ impl<'a> Parser<'a> {
 
     /// Is this a possibly malformed start of a `macro_rules! foo` item definition?
     fn is_macro_rules_item(&mut self) -> IsMacroRulesItem {
-        if self.check_keyword(kw::MacroRules) {
+        if self.check_keyword(exp!(MacroRules)) {
             let macro_rules_span = self.token.span;
 
             if self.look_ahead(1, |t| *t == token::Not) && self.look_ahead(2, |t| t.is_ident()) {
@@ -2203,14 +2206,14 @@ impl<'a> Parser<'a> {
         vis: &Visibility,
         has_bang: bool,
     ) -> PResult<'a, ItemInfo> {
-        self.expect_keyword(kw::MacroRules)?; // `macro_rules`
+        self.expect_keyword(exp!(MacroRules))?; // `macro_rules`
 
         if has_bang {
-            self.expect(&token::Not)?; // `!`
+            self.expect(exp!(Not))?; // `!`
         }
         let ident = self.parse_ident()?;
 
-        if self.eat(&token::Not) {
+        if self.eat(exp!(Not)) {
             // Handle macro_rules! foo!
             let span = self.prev_token.span;
             self.dcx().emit_err(errors::MacroNameRemoveBang { span });
@@ -2240,7 +2243,7 @@ impl<'a> Parser<'a> {
     }
 
     fn eat_semi_for_macro_if_needed(&mut self, args: &DelimArgs) {
-        if args.need_semicolon() && !self.eat(&token::Semi) {
+        if args.need_semicolon() && !self.eat(exp!(Semi)) {
             self.report_invalid_macro_expansion_item(args);
         }
     }
@@ -2416,11 +2419,8 @@ impl<'a> Parser<'a> {
         req_body: bool,
         fn_params_end: Option<Span>,
     ) -> PResult<'a, ErrorGuaranteed> {
-        let expected = if req_body {
-            &[token::OpenDelim(Delimiter::Brace)][..]
-        } else {
-            &[token::Semi, token::OpenDelim(Delimiter::Brace)]
-        };
+        let expected: &[_] =
+            if req_body { &[exp!(OpenBrace)] } else { &[exp!(Semi), exp!(OpenBrace)] };
         match self.expected_one_of_not_found(&[], expected) {
             Ok(error_guaranteed) => Ok(error_guaranteed),
             Err(mut err) => {
@@ -2505,14 +2505,14 @@ impl<'a> Parser<'a> {
             self.token == TokenKind::Semi
         } else {
             // Only include `;` in list of expected tokens if body is not required
-            self.check(&TokenKind::Semi)
+            self.check(exp!(Semi))
         };
         let (inner_attrs, body) = if has_semi {
             // Include the trailing semicolon in the span of the signature
             self.expect_semi()?;
             *sig_hi = self.prev_token.span;
             (AttrVec::new(), None)
-        } else if self.check(&token::OpenDelim(Delimiter::Brace)) || self.token.is_whole_block() {
+        } else if self.check(exp!(OpenBrace)) || self.token.is_whole_block() {
             self.parse_block_common(self.token.span, BlockCheckMode::Default, false)
                 .map(|(attrs, body)| (attrs, Some(body)))?
         } else if self.token == token::Eq {
@@ -2540,21 +2540,28 @@ impl<'a> Parser<'a> {
     /// `check_pub` adds additional `pub` to the checks in case users place it
     /// wrongly, can be used to ensure `pub` never comes after `default`.
     pub(super) fn check_fn_front_matter(&mut self, check_pub: bool, case: Case) -> bool {
-        const ALL_QUALS: &[Symbol] =
-            &[kw::Pub, kw::Gen, kw::Const, kw::Async, kw::Unsafe, kw::Safe, kw::Extern];
+        const ALL_QUALS: &[ExpKeywordPair] = &[
+            exp!(Pub),
+            exp!(Gen),
+            exp!(Const),
+            exp!(Async),
+            exp!(Unsafe),
+            exp!(Safe),
+            exp!(Extern),
+        ];
 
         // We use an over-approximation here.
         // `const const`, `fn const` won't parse, but we're not stepping over other syntax either.
         // `pub` is added in case users got confused with the ordering like `async pub fn`,
         // only if it wasn't preceded by `default` as `default pub` is invalid.
-        let quals: &[Symbol] = if check_pub {
+        let quals: &[_] = if check_pub {
             ALL_QUALS
         } else {
-            &[kw::Gen, kw::Const, kw::Async, kw::Unsafe, kw::Safe, kw::Extern]
+            &[exp!(Gen), exp!(Const), exp!(Async), exp!(Unsafe), exp!(Safe), exp!(Extern)]
         };
-        self.check_keyword_case(kw::Fn, case) // Definitely an `fn`.
+        self.check_keyword_case(exp!(Fn), case) // Definitely an `fn`.
             // `$qual fn` or `$qual $qual`:
-            || quals.iter().any(|&kw| self.check_keyword_case(kw, case))
+            || quals.iter().any(|&exp| self.check_keyword_case(exp, case))
                 && self.look_ahead(1, |t| {
                     // `$qual fn`, e.g. `const fn` or `async fn`.
                     t.is_keyword_case(kw::Fn, case)
@@ -2562,12 +2569,14 @@ impl<'a> Parser<'a> {
                     || (
                         (
                             t.is_non_raw_ident_where(|i|
-                                quals.contains(&i.name)
+                                quals.iter().any(|exp| exp.kw == i.name)
                                     // Rule out 2015 `const async: T = val`.
                                     && i.is_reserved()
                             )
                             || case == Case::Insensitive
-                                && t.is_non_raw_ident_where(|i| quals.iter().any(|qual| qual.as_str() == i.name.as_str().to_lowercase()))
+                                && t.is_non_raw_ident_where(|i| quals.iter().any(|exp| {
+                                    exp.kw.as_str() == i.name.as_str().to_lowercase()
+                                }))
                         )
                         // Rule out `unsafe extern {`.
                         && !self.is_unsafe_foreign_mod()
@@ -2575,12 +2584,13 @@ impl<'a> Parser<'a> {
                         && !self.is_async_gen_block())
                 })
             // `extern ABI fn`
-            || self.check_keyword_case(kw::Extern, case)
+            || self.check_keyword_case(exp!(Extern), case)
                 && self.look_ahead(1, |t| t.can_begin_string_literal())
                 && (self.look_ahead(2, |t| t.is_keyword_case(kw::Fn, case)) ||
-                    // this branch is only for better diagnostics; `pub`, `unsafe`, etc. are not allowed here
+                    // This branch is only for better diagnostics; `pub`, `unsafe`, etc. are not
+                    // allowed here.
                     (self.may_recover()
-                        && self.look_ahead(2, |t| ALL_QUALS.iter().any(|&kw| t.is_keyword(kw)))
+                        && self.look_ahead(2, |t| ALL_QUALS.iter().any(|exp| t.is_keyword(exp.kw)))
                         && self.look_ahead(3, |t| t.is_keyword_case(kw::Fn, case))))
     }
 
@@ -2628,7 +2638,7 @@ impl<'a> Parser<'a> {
             Some(CoroutineKind::Async { .. }) | None => {}
         }
 
-        if !self.eat_keyword_case(kw::Fn, case) {
+        if !self.eat_keyword_case(exp!(Fn), case) {
             // It is possible for `expect_one_of` to recover given the contents of
             // `self.expected_token_types`, therefore, do not use `self.unexpected()` which doesn't
             // account for this.
@@ -2648,7 +2658,7 @@ impl<'a> Parser<'a> {
                     let mut recover_safety = safety;
                     // This will allow the machine fix to directly place the keyword in the correct place or to indicate
                     // that the keyword is already present and the second instance should be removed.
-                    let wrong_kw = if self.check_keyword(kw::Const) {
+                    let wrong_kw = if self.check_keyword(exp!(Const)) {
                         match constness {
                             Const::Yes(sp) => Some(WrongKw::Duplicated(sp)),
                             Const::No => {
@@ -2656,7 +2666,7 @@ impl<'a> Parser<'a> {
                                 Some(WrongKw::Misplaced(async_start_sp))
                             }
                         }
-                    } else if self.check_keyword(kw::Async) {
+                    } else if self.check_keyword(exp!(Async)) {
                         match coroutine_kind {
                             Some(CoroutineKind::Async { span, .. }) => {
                                 Some(WrongKw::Duplicated(span))
@@ -2682,7 +2692,7 @@ impl<'a> Parser<'a> {
                                 Some(WrongKw::Misplaced(unsafe_start_sp))
                             }
                         }
-                    } else if self.check_keyword(kw::Unsafe) {
+                    } else if self.check_keyword(exp!(Unsafe)) {
                         match safety {
                             Safety::Unsafe(sp) => Some(WrongKw::Duplicated(sp)),
                             Safety::Safe(sp) => {
@@ -2694,7 +2704,7 @@ impl<'a> Parser<'a> {
                                 Some(WrongKw::Misplaced(ext_start_sp))
                             }
                         }
-                    } else if self.check_keyword(kw::Safe) {
+                    } else if self.check_keyword(exp!(Safe)) {
                         match safety {
                             Safety::Safe(sp) => Some(WrongKw::Duplicated(sp)),
                             Safety::Unsafe(sp) => {
@@ -2740,7 +2750,7 @@ impl<'a> Parser<'a> {
                         }
                     }
                     // Recover incorrect visibility order such as `async pub`
-                    else if self.check_keyword(kw::Pub) {
+                    else if self.check_keyword(exp!(Pub)) {
                         let sp = sp_start.to(self.prev_token.span);
                         if let Ok(snippet) = self.span_to_snippet(sp) {
                             let current_vis = match self.parse_visibility(FollowedByType::No) {
@@ -2843,7 +2853,7 @@ impl<'a> Parser<'a> {
                 };
                 p.restore_snapshot(snapshot);
                 // Skip every token until next possible arg or end.
-                p.eat_to_tokens(&[&token::Comma, &token::CloseDelim(Delimiter::Parenthesis)]);
+                p.eat_to_tokens(&[exp!(Comma), exp!(CloseParen)]);
                 // Create a placeholder argument for proper arg count (issue #34264).
                 Ok(dummy_arg(Ident::new(kw::Empty, lo.to(p.prev_token.span)), guar))
             });
@@ -2954,7 +2964,7 @@ impl<'a> Parser<'a> {
         let parse_self_possibly_typed = |this: &mut Self, m| {
             let eself_ident = expect_self_ident(this);
             let eself_hi = this.prev_token.span;
-            let eself = if this.eat(&token::Colon) {
+            let eself = if this.eat(exp!(Colon)) {
                 SelfKind::Explicit(this.parse_ty()?, m)
             } else {
                 SelfKind::Value(m)
diff --git a/compiler/rustc_parse/src/parser/mod.rs b/compiler/rustc_parse/src/parser/mod.rs
index 3e82a9cf1bb..43328c48015 100644
--- a/compiler/rustc_parse/src/parser/mod.rs
+++ b/compiler/rustc_parse/src/parser/mod.rs
@@ -8,6 +8,7 @@ mod nonterminal;
 mod pat;
 mod path;
 mod stmt;
+pub mod token_type;
 mod ty;
 
 use std::assert_matches::debug_assert_matches;
@@ -39,11 +40,14 @@ use rustc_index::interval::IntervalSet;
 use rustc_session::parse::ParseSess;
 use rustc_span::{DUMMY_SP, Ident, Span, Symbol, kw, sym};
 use thin_vec::ThinVec;
+use token_type::TokenTypeSet;
+pub use token_type::{ExpKeywordPair, ExpTokenPair, TokenType};
 use tracing::debug;
 
 use crate::errors::{
     self, IncorrectVisibilityRestriction, MismatchedClosingDelimiter, NonStringAbiLiteral,
 };
+use crate::exp;
 use crate::lexer::UnmatchedDelim;
 
 #[cfg(test)]
@@ -141,7 +145,7 @@ pub struct Parser<'a> {
     pub prev_token: Token,
     pub capture_cfg: bool,
     restrictions: Restrictions,
-    expected_token_types: Vec<TokenType>,
+    expected_token_types: TokenTypeSet,
     token_cursor: TokenCursor,
     // The number of calls to `bump`, i.e. the position in the token stream.
     num_bump_calls: u32,
@@ -367,48 +371,21 @@ impl TokenCursor {
     }
 }
 
-#[derive(Debug, Clone, PartialEq)]
-enum TokenType {
-    Token(TokenKind),
-    Keyword(Symbol),
-    Operator,
-    Lifetime,
-    Ident,
-    Path,
-    Type,
-    Const,
-}
-
-impl TokenType {
-    fn to_string(&self) -> String {
-        match self {
-            TokenType::Token(t) => format!("`{}`", pprust::token_kind_to_string(t)),
-            TokenType::Keyword(kw) => format!("`{kw}`"),
-            TokenType::Operator => "an operator".to_string(),
-            TokenType::Lifetime => "lifetime".to_string(),
-            TokenType::Ident => "identifier".to_string(),
-            TokenType::Path => "path".to_string(),
-            TokenType::Type => "type".to_string(),
-            TokenType::Const => "a const expression".to_string(),
-        }
-    }
-}
-
 /// A sequence separator.
 #[derive(Debug)]
-struct SeqSep {
+struct SeqSep<'a> {
     /// The separator token.
-    sep: Option<TokenKind>,
+    sep: Option<ExpTokenPair<'a>>,
     /// `true` if a trailing separator is allowed.
     trailing_sep_allowed: bool,
 }
 
-impl SeqSep {
-    fn trailing_allowed(t: TokenKind) -> SeqSep {
-        SeqSep { sep: Some(t), trailing_sep_allowed: true }
+impl<'a> SeqSep<'a> {
+    fn trailing_allowed(sep: ExpTokenPair<'a>) -> SeqSep<'a> {
+        SeqSep { sep: Some(sep), trailing_sep_allowed: true }
     }
 
-    fn none() -> SeqSep {
+    fn none() -> SeqSep<'a> {
         SeqSep { sep: None, trailing_sep_allowed: false }
     }
 }
@@ -490,7 +467,7 @@ impl<'a> Parser<'a> {
             prev_token: Token::dummy(),
             capture_cfg: false,
             restrictions: Restrictions::empty(),
-            expected_token_types: Vec::new(),
+            expected_token_types: TokenTypeSet::new(),
             token_cursor: TokenCursor { curr: TokenTreeCursor::new(stream), stack: Vec::new() },
             num_bump_calls: 0,
             break_last_token: 0,
@@ -553,16 +530,16 @@ impl<'a> Parser<'a> {
     }
 
     /// Expects and consumes the token `t`. Signals an error if the next token is not `t`.
-    pub fn expect(&mut self, t: &TokenKind) -> PResult<'a, Recovered> {
+    pub fn expect(&mut self, exp: ExpTokenPair<'_>) -> PResult<'a, Recovered> {
         if self.expected_token_types.is_empty() {
-            if self.token == *t {
+            if self.token == *exp.tok {
                 self.bump();
                 Ok(Recovered::No)
             } else {
-                self.unexpected_try_recover(t)
+                self.unexpected_try_recover(exp.tok)
             }
         } else {
-            self.expect_one_of(slice::from_ref(t), &[])
+            self.expect_one_of(slice::from_ref(&exp), &[])
         }
     }
 
@@ -571,13 +548,13 @@ impl<'a> Parser<'a> {
     /// anything. Signal a fatal error if next token is unexpected.
     fn expect_one_of(
         &mut self,
-        edible: &[TokenKind],
-        inedible: &[TokenKind],
+        edible: &[ExpTokenPair<'_>],
+        inedible: &[ExpTokenPair<'_>],
     ) -> PResult<'a, Recovered> {
-        if edible.contains(&self.token.kind) {
+        if edible.iter().any(|exp| exp.tok == &self.token.kind) {
             self.bump();
             Ok(Recovered::No)
-        } else if inedible.contains(&self.token.kind) {
+        } else if inedible.iter().any(|exp| exp.tok == &self.token.kind) {
             // leave it in the input
             Ok(Recovered::No)
         } else if self.token != token::Eof
@@ -622,10 +599,10 @@ impl<'a> Parser<'a> {
     /// This method will automatically add `tok` to `expected_token_types` if `tok` is not
     /// encountered.
     #[inline]
-    fn check(&mut self, tok: &TokenKind) -> bool {
-        let is_present = self.token == *tok;
+    fn check(&mut self, exp: ExpTokenPair<'_>) -> bool {
+        let is_present = self.token == *exp.tok;
         if !is_present {
-            self.expected_token_types.push(TokenType::Token(tok.clone()));
+            self.expected_token_types.insert(exp.token_type);
         }
         is_present
     }
@@ -653,8 +630,8 @@ impl<'a> Parser<'a> {
     /// Consumes a token 'tok' if it exists. Returns whether the given token was present.
     #[inline]
     #[must_use]
-    pub fn eat(&mut self, tok: &TokenKind) -> bool {
-        let is_present = self.check(tok);
+    pub fn eat(&mut self, exp: ExpTokenPair<'_>) -> bool {
+        let is_present = self.check(exp);
         if is_present {
             self.bump()
         }
@@ -665,23 +642,23 @@ impl<'a> Parser<'a> {
     /// An expectation is also added for diagnostics purposes.
     #[inline]
     #[must_use]
-    fn check_keyword(&mut self, kw: Symbol) -> bool {
-        let is_keyword = self.token.is_keyword(kw);
+    fn check_keyword(&mut self, exp: ExpKeywordPair) -> bool {
+        let is_keyword = self.token.is_keyword(exp.kw);
         if !is_keyword {
-            self.expected_token_types.push(TokenType::Keyword(kw));
+            self.expected_token_types.insert(exp.token_type);
         }
         is_keyword
     }
 
     #[inline]
     #[must_use]
-    fn check_keyword_case(&mut self, kw: Symbol, case: Case) -> bool {
-        if self.check_keyword(kw) {
+    fn check_keyword_case(&mut self, exp: ExpKeywordPair, case: Case) -> bool {
+        if self.check_keyword(exp) {
             true
         // Do an ASCII case-insensitive match, because all keywords are ASCII.
         } else if case == Case::Insensitive
             && let Some((ident, IdentIsRaw::No)) = self.token.ident()
-            && ident.as_str().eq_ignore_ascii_case(kw.as_str())
+            && ident.as_str().eq_ignore_ascii_case(exp.kw.as_str())
         {
             true
         } else {
@@ -694,8 +671,8 @@ impl<'a> Parser<'a> {
     // Public for rustc_builtin_macros and rustfmt usage.
     #[inline]
     #[must_use]
-    pub fn eat_keyword(&mut self, kw: Symbol) -> bool {
-        let is_keyword = self.check_keyword(kw);
+    pub fn eat_keyword(&mut self, exp: ExpKeywordPair) -> bool {
+        let is_keyword = self.check_keyword(exp);
         if is_keyword {
             self.bump();
         }
@@ -707,14 +684,14 @@ impl<'a> Parser<'a> {
     /// This is useful for recovery.
     #[inline]
     #[must_use]
-    fn eat_keyword_case(&mut self, kw: Symbol, case: Case) -> bool {
-        if self.eat_keyword(kw) {
+    fn eat_keyword_case(&mut self, exp: ExpKeywordPair, case: Case) -> bool {
+        if self.eat_keyword(exp) {
             true
         } else if case == Case::Insensitive
             && let Some((ident, IdentIsRaw::No)) = self.token.ident()
-            && ident.as_str().to_lowercase() == kw.as_str().to_lowercase()
+            && ident.as_str().to_lowercase() == exp.kw.as_str().to_lowercase()
         {
-            self.dcx().emit_err(errors::KwBadCase { span: ident.span, kw: kw.as_str() });
+            self.dcx().emit_err(errors::KwBadCase { span: ident.span, kw: exp.kw.as_str() });
             self.bump();
             true
         } else {
@@ -738,8 +715,8 @@ impl<'a> Parser<'a> {
     /// If the given word is not a keyword, signals an error.
     /// If the next token is not the given word, signals an error.
     /// Otherwise, eats it.
-    pub fn expect_keyword(&mut self, kw: Symbol) -> PResult<'a, ()> {
-        if !self.eat_keyword(kw) { self.unexpected() } else { Ok(()) }
+    pub fn expect_keyword(&mut self, exp: ExpKeywordPair) -> PResult<'a, ()> {
+        if !self.eat_keyword(exp) { self.unexpected() } else { Ok(()) }
     }
 
     /// Is the given keyword `kw` followed by a non-reserved identifier?
@@ -748,9 +725,9 @@ impl<'a> Parser<'a> {
     }
 
     #[inline]
-    fn check_or_expected(&mut self, ok: bool, typ: TokenType) -> bool {
+    fn check_or_expected(&mut self, ok: bool, token_type: TokenType) -> bool {
         if !ok {
-            self.expected_token_types.push(typ);
+            self.expected_token_types.insert(token_type);
         }
         ok
     }
@@ -795,22 +772,19 @@ impl<'a> Parser<'a> {
     /// Otherwise returns `false`.
     #[inline]
     fn check_plus(&mut self) -> bool {
-        self.check_or_expected(
-            self.token.is_like_plus(),
-            TokenType::Token(token::BinOp(token::Plus)),
-        )
+        self.check_or_expected(self.token.is_like_plus(), TokenType::Plus)
     }
 
     /// Eats the expected token if it's present possibly breaking
     /// compound tokens like multi-character operators in process.
     /// Returns `true` if the token was eaten.
-    fn break_and_eat(&mut self, expected: TokenKind) -> bool {
-        if self.token == expected {
+    fn break_and_eat(&mut self, exp: ExpTokenPair<'_>) -> bool {
+        if self.token == *exp.tok {
             self.bump();
             return true;
         }
         match self.token.kind.break_two_token_op(1) {
-            Some((first, second)) if first == expected => {
+            Some((first, second)) if first == *exp.tok => {
                 let first_span = self.psess.source_map().start_point(self.token.span);
                 let second_span = self.token.span.with_lo(first_span.hi());
                 self.token = Token::new(first, first_span);
@@ -827,7 +801,7 @@ impl<'a> Parser<'a> {
                 true
             }
             _ => {
-                self.expected_token_types.push(TokenType::Token(expected));
+                self.expected_token_types.insert(exp.token_type);
                 false
             }
         }
@@ -835,24 +809,24 @@ impl<'a> Parser<'a> {
 
     /// Eats `+` possibly breaking tokens like `+=` in process.
     fn eat_plus(&mut self) -> bool {
-        self.break_and_eat(token::BinOp(token::Plus))
+        self.break_and_eat(exp!(Plus))
     }
 
     /// Eats `&` possibly breaking tokens like `&&` in process.
     /// Signals an error if `&` is not eaten.
     fn expect_and(&mut self) -> PResult<'a, ()> {
-        if self.break_and_eat(token::BinOp(token::And)) { Ok(()) } else { self.unexpected() }
+        if self.break_and_eat(exp!(And)) { Ok(()) } else { self.unexpected() }
     }
 
     /// Eats `|` possibly breaking tokens like `||` in process.
     /// Signals an error if `|` was not eaten.
     fn expect_or(&mut self) -> PResult<'a, ()> {
-        if self.break_and_eat(token::BinOp(token::Or)) { Ok(()) } else { self.unexpected() }
+        if self.break_and_eat(exp!(Or)) { Ok(()) } else { self.unexpected() }
     }
 
     /// Eats `<` possibly breaking tokens like `<<` in process.
     fn eat_lt(&mut self) -> bool {
-        let ate = self.break_and_eat(token::Lt);
+        let ate = self.break_and_eat(exp!(Lt));
         if ate {
             // See doc comment for `unmatched_angle_bracket_count`.
             self.unmatched_angle_bracket_count += 1;
@@ -870,7 +844,7 @@ impl<'a> Parser<'a> {
     /// Eats `>` possibly breaking tokens like `>>` in process.
     /// Signals an error if `>` was not eaten.
     fn expect_gt(&mut self) -> PResult<'a, ()> {
-        if self.break_and_eat(token::Gt) {
+        if self.break_and_eat(exp!(Gt)) {
             // See doc comment for `unmatched_angle_bracket_count`.
             if self.unmatched_angle_bracket_count > 0 {
                 self.unmatched_angle_bracket_count -= 1;
@@ -885,10 +859,10 @@ impl<'a> Parser<'a> {
     /// Checks if the next token is contained within `closes`, and returns `true` if so.
     fn expect_any_with_type(
         &mut self,
-        closes_expected: &[&TokenKind],
+        closes_expected: &[ExpTokenPair<'_>],
         closes_not_expected: &[&TokenKind],
     ) -> bool {
-        closes_expected.iter().any(|k| self.check(k))
+        closes_expected.iter().any(|&close| self.check(close))
             || closes_not_expected.iter().any(|k| self.check_noexpect(k))
     }
 
@@ -897,9 +871,9 @@ impl<'a> Parser<'a> {
     /// closing bracket.
     fn parse_seq_to_before_tokens<T>(
         &mut self,
-        closes_expected: &[&TokenKind],
+        closes_expected: &[ExpTokenPair<'_>],
         closes_not_expected: &[&TokenKind],
-        sep: SeqSep,
+        sep: SeqSep<'_>,
         mut f: impl FnMut(&mut Parser<'a>) -> PResult<'a, T>,
     ) -> PResult<'a, (ThinVec<T>, Trailing, Recovered)> {
         let mut first = true;
@@ -911,13 +885,13 @@ impl<'a> Parser<'a> {
             if let token::CloseDelim(..) | token::Eof = self.token.kind {
                 break;
             }
-            if let Some(t) = &sep.sep {
+            if let Some(exp) = sep.sep {
                 if first {
                     // no separator for the first element
                     first = false;
                 } else {
                     // check for separator
-                    match self.expect(t) {
+                    match self.expect(exp) {
                         Ok(Recovered::No) => {
                             self.current_closure.take();
                         }
@@ -928,7 +902,7 @@ impl<'a> Parser<'a> {
                         }
                         Err(mut expect_err) => {
                             let sp = self.prev_token.span.shrink_to_hi();
-                            let token_str = pprust::token_kind_to_string(t);
+                            let token_str = pprust::token_kind_to_string(exp.tok);
 
                             match self.current_closure.take() {
                                 Some(closure_spans) if self.token == TokenKind::Semi => {
@@ -948,7 +922,7 @@ impl<'a> Parser<'a> {
 
                                 _ => {
                                     // Attempt to keep parsing if it was a similar separator.
-                                    if let Some(tokens) = t.similar_tokens() {
+                                    if let Some(tokens) = exp.tok.similar_tokens() {
                                         if tokens.contains(&self.token.kind) {
                                             self.bump();
                                         }
@@ -998,15 +972,17 @@ impl<'a> Parser<'a> {
                                     // Parsing failed, therefore it must be something more serious
                                     // than just a missing separator.
                                     for xx in &e.children {
-                                        // propagate the help message from sub error 'e' to main error 'expect_err;
+                                        // Propagate the help message from sub error `e` to main
+                                        // error `expect_err`.
                                         expect_err.children.push(xx.clone());
                                     }
                                     e.cancel();
                                     if self.token == token::Colon {
-                                        // we will try to recover in `maybe_recover_struct_lit_bad_delims`
+                                        // We will try to recover in
+                                        // `maybe_recover_struct_lit_bad_delims`.
                                         return Err(expect_err);
-                                    } else if let [token::CloseDelim(Delimiter::Parenthesis)] =
-                                        closes_expected
+                                    } else if let [exp] = closes_expected
+                                        && exp.token_type == TokenType::CloseParen
                                     {
                                         return Err(expect_err);
                                     } else {
@@ -1040,7 +1016,7 @@ impl<'a> Parser<'a> {
     ) -> PResult<'a, ()> {
         let initial_semicolon = self.token.span;
 
-        while self.eat(&TokenKind::Semi) {
+        while self.eat(exp!(Semi)) {
             let _ = self.parse_stmt_without_recovery(false, ForceCollect::No).unwrap_or_else(|e| {
                 e.cancel();
                 None
@@ -1096,8 +1072,8 @@ impl<'a> Parser<'a> {
     /// closing bracket.
     fn parse_seq_to_before_end<T>(
         &mut self,
-        close: &TokenKind,
-        sep: SeqSep,
+        close: ExpTokenPair<'_>,
+        sep: SeqSep<'_>,
         f: impl FnMut(&mut Parser<'a>) -> PResult<'a, T>,
     ) -> PResult<'a, (ThinVec<T>, Trailing, Recovered)> {
         self.parse_seq_to_before_tokens(&[close], &[], sep, f)
@@ -1108,8 +1084,8 @@ impl<'a> Parser<'a> {
     /// closing bracket.
     fn parse_seq_to_end<T>(
         &mut self,
-        close: &TokenKind,
-        sep: SeqSep,
+        close: ExpTokenPair<'_>,
+        sep: SeqSep<'_>,
         f: impl FnMut(&mut Parser<'a>) -> PResult<'a, T>,
     ) -> PResult<'a, (ThinVec<T>, Trailing)> {
         let (val, trailing, recovered) = self.parse_seq_to_before_end(close, sep, f)?;
@@ -1127,9 +1103,9 @@ impl<'a> Parser<'a> {
     /// closing bracket.
     fn parse_unspanned_seq<T>(
         &mut self,
-        open: &TokenKind,
-        close: &TokenKind,
-        sep: SeqSep,
+        open: ExpTokenPair<'_>,
+        close: ExpTokenPair<'_>,
+        sep: SeqSep<'_>,
         f: impl FnMut(&mut Parser<'a>) -> PResult<'a, T>,
     ) -> PResult<'a, (ThinVec<T>, Trailing)> {
         self.expect(open)?;
@@ -1141,15 +1117,11 @@ impl<'a> Parser<'a> {
     /// closing bracket.
     fn parse_delim_comma_seq<T>(
         &mut self,
-        delim: Delimiter,
+        open: ExpTokenPair<'_>,
+        close: ExpTokenPair<'_>,
         f: impl FnMut(&mut Parser<'a>) -> PResult<'a, T>,
     ) -> PResult<'a, (ThinVec<T>, Trailing)> {
-        self.parse_unspanned_seq(
-            &token::OpenDelim(delim),
-            &token::CloseDelim(delim),
-            SeqSep::trailing_allowed(token::Comma),
-            f,
-        )
+        self.parse_unspanned_seq(open, close, SeqSep::trailing_allowed(exp!(Comma)), f)
     }
 
     /// Parses a comma-separated sequence delimited by parentheses (e.g. `(x, y)`).
@@ -1159,7 +1131,7 @@ impl<'a> Parser<'a> {
         &mut self,
         f: impl FnMut(&mut Parser<'a>) -> PResult<'a, T>,
     ) -> PResult<'a, (ThinVec<T>, Trailing)> {
-        self.parse_delim_comma_seq(Delimiter::Parenthesis, f)
+        self.parse_delim_comma_seq(exp!(OpenParen), exp!(CloseParen), f)
     }
 
     /// Advance the parser by one token using provided token as the next one.
@@ -1265,11 +1237,11 @@ impl<'a> Parser<'a> {
     /// Parses asyncness: `async` or nothing.
     fn parse_coroutine_kind(&mut self, case: Case) -> Option<CoroutineKind> {
         let span = self.token.uninterpolated_span();
-        if self.eat_keyword_case(kw::Async, case) {
+        if self.eat_keyword_case(exp!(Async), case) {
             // FIXME(gen_blocks): Do we want to unconditionally parse `gen` and then
             // error if edition <= 2024, like we do with async and edition <= 2018?
             if self.token.uninterpolated_span().at_least_rust_2024()
-                && self.eat_keyword_case(kw::Gen, case)
+                && self.eat_keyword_case(exp!(Gen), case)
             {
                 let gen_span = self.prev_token.uninterpolated_span();
                 Some(CoroutineKind::AsyncGen {
@@ -1285,7 +1257,7 @@ impl<'a> Parser<'a> {
                 })
             }
         } else if self.token.uninterpolated_span().at_least_rust_2024()
-            && self.eat_keyword_case(kw::Gen, case)
+            && self.eat_keyword_case(exp!(Gen), case)
         {
             Some(CoroutineKind::Gen {
                 span,
@@ -1299,9 +1271,9 @@ impl<'a> Parser<'a> {
 
     /// Parses fn unsafety: `unsafe`, `safe` or nothing.
     fn parse_safety(&mut self, case: Case) -> Safety {
-        if self.eat_keyword_case(kw::Unsafe, case) {
+        if self.eat_keyword_case(exp!(Unsafe), case) {
             Safety::Unsafe(self.prev_token.uninterpolated_span())
-        } else if self.eat_keyword_case(kw::Safe, case) {
+        } else if self.eat_keyword_case(exp!(Safe), case) {
             Safety::Safe(self.prev_token.uninterpolated_span())
         } else {
             Safety::Default
@@ -1327,7 +1299,7 @@ impl<'a> Parser<'a> {
         if (self.check_const_closure() == is_closure)
             && !self
                 .look_ahead(1, |t| *t == token::OpenDelim(Delimiter::Brace) || t.is_whole_block())
-            && self.eat_keyword_case(kw::Const, case)
+            && self.eat_keyword_case(exp!(Const), case)
         {
             Const::Yes(self.prev_token.uninterpolated_span())
         } else {
@@ -1340,7 +1312,7 @@ impl<'a> Parser<'a> {
         if pat {
             self.psess.gated_spans.gate(sym::inline_const_pat, span);
         }
-        self.expect_keyword(kw::Const)?;
+        self.expect_keyword(exp!(Const))?;
         let (attrs, blk) = self.parse_inner_attrs_and_block()?;
         let anon_const = AnonConst {
             id: DUMMY_NODE_ID,
@@ -1352,19 +1324,19 @@ impl<'a> Parser<'a> {
 
     /// Parses mutability (`mut` or nothing).
     fn parse_mutability(&mut self) -> Mutability {
-        if self.eat_keyword(kw::Mut) { Mutability::Mut } else { Mutability::Not }
+        if self.eat_keyword(exp!(Mut)) { Mutability::Mut } else { Mutability::Not }
     }
 
     /// Parses reference binding mode (`ref`, `ref mut`, or nothing).
     fn parse_byref(&mut self) -> ByRef {
-        if self.eat_keyword(kw::Ref) { ByRef::Yes(self.parse_mutability()) } else { ByRef::No }
+        if self.eat_keyword(exp!(Ref)) { ByRef::Yes(self.parse_mutability()) } else { ByRef::No }
     }
 
     /// Possibly parses mutability (`const` or `mut`).
     fn parse_const_or_mut(&mut self) -> Option<Mutability> {
-        if self.eat_keyword(kw::Mut) {
+        if self.eat_keyword(exp!(Mut)) {
             Some(Mutability::Mut)
-        } else if self.eat_keyword(kw::Const) {
+        } else if self.eat_keyword(exp!(Const)) {
             Some(Mutability::Not)
         } else {
             None
@@ -1395,7 +1367,7 @@ impl<'a> Parser<'a> {
     fn parse_attr_args(&mut self) -> PResult<'a, AttrArgs> {
         Ok(if let Some(args) = self.parse_delim_args_inner() {
             AttrArgs::Delimited(args)
-        } else if self.eat(&token::Eq) {
+        } else if self.eat(exp!(Eq)) {
             let eq_span = self.prev_token.span;
             AttrArgs::Eq { eq_span, expr: self.parse_expr_force_collect()? }
         } else {
@@ -1404,9 +1376,9 @@ impl<'a> Parser<'a> {
     }
 
     fn parse_delim_args_inner(&mut self) -> Option<DelimArgs> {
-        let delimited = self.check(&token::OpenDelim(Delimiter::Parenthesis))
-            || self.check(&token::OpenDelim(Delimiter::Bracket))
-            || self.check(&token::OpenDelim(Delimiter::Brace));
+        let delimited = self.check(exp!(OpenParen))
+            || self.check(exp!(OpenBracket))
+            || self.check(exp!(OpenBrace));
 
         delimited.then(|| {
             let TokenTree::Delimited(dspan, _, delim, tokens) = self.parse_token_tree() else {
@@ -1485,7 +1457,7 @@ impl<'a> Parser<'a> {
     pub fn parse_visibility(&mut self, fbt: FollowedByType) -> PResult<'a, Visibility> {
         maybe_whole!(self, NtVis, |vis| vis.into_inner());
 
-        if !self.eat_keyword(kw::Pub) {
+        if !self.eat_keyword(exp!(Pub)) {
             // We need a span for our `Spanned<VisibilityKind>`, but there's inherently no
             // keyword to grab a span from for inherited visibility; an empty span at the
             // beginning of the current token would seem to be the "Schelling span".
@@ -1497,7 +1469,7 @@ impl<'a> Parser<'a> {
         }
         let lo = self.prev_token.span;
 
-        if self.check(&token::OpenDelim(Delimiter::Parenthesis)) {
+        if self.check(exp!(OpenParen)) {
             // We don't `self.bump()` the `(` yet because this might be a struct definition where
             // `()` or a tuple might be allowed. For example, `struct Struct(pub (), pub (usize));`.
             // Because of this, we only `bump` the `(` if we're assured it is appropriate to do so
@@ -1507,7 +1479,7 @@ impl<'a> Parser<'a> {
                 self.bump(); // `(`
                 self.bump(); // `in`
                 let path = self.parse_path(PathStyle::Mod)?; // `path`
-                self.expect(&token::CloseDelim(Delimiter::Parenthesis))?; // `)`
+                self.expect(exp!(CloseParen))?; // `)`
                 let vis = VisibilityKind::Restricted {
                     path: P(path),
                     id: ast::DUMMY_NODE_ID,
@@ -1524,7 +1496,7 @@ impl<'a> Parser<'a> {
                 // Parse `pub(crate)`, `pub(self)`, or `pub(super)`.
                 self.bump(); // `(`
                 let path = self.parse_path(PathStyle::Mod)?; // `crate`/`super`/`self`
-                self.expect(&token::CloseDelim(Delimiter::Parenthesis))?; // `)`
+                self.expect(exp!(CloseParen))?; // `)`
                 let vis = VisibilityKind::Restricted {
                     path: P(path),
                     id: ast::DUMMY_NODE_ID,
@@ -1550,7 +1522,7 @@ impl<'a> Parser<'a> {
     fn recover_incorrect_vis_restriction(&mut self) -> PResult<'a, ()> {
         self.bump(); // `(`
         let path = self.parse_path(PathStyle::Mod)?;
-        self.expect(&token::CloseDelim(Delimiter::Parenthesis))?; // `)`
+        self.expect(exp!(CloseParen))?; // `)`
 
         let path_str = pprust::path_to_string(&path);
         self.dcx()
@@ -1561,7 +1533,7 @@ impl<'a> Parser<'a> {
 
     /// Parses `extern string_literal?`.
     fn parse_extern(&mut self, case: Case) -> Extern {
-        if self.eat_keyword_case(kw::Extern, case) {
+        if self.eat_keyword_case(exp!(Extern), case) {
             let mut extern_span = self.prev_token.span;
             let abi = self.parse_abi();
             if let Some(abi) = abi {
@@ -1601,7 +1573,7 @@ impl<'a> Parser<'a> {
 
     /// Checks for `::` or, potentially, `:::` and then look ahead after it.
     fn check_path_sep_and_look_ahead(&mut self, looker: impl Fn(&Token) -> bool) -> bool {
-        if self.check(&token::PathSep) {
+        if self.check(exp!(PathSep)) {
             if self.may_recover() && self.look_ahead(1, |t| t.kind == token::Colon) {
                 debug_assert!(!self.look_ahead(1, &looker), "Looker must not match on colon");
                 self.look_ahead(2, looker)
diff --git a/compiler/rustc_parse/src/parser/pat.rs b/compiler/rustc_parse/src/parser/pat.rs
index 9b63100105b..5ad3da2196f 100644
--- a/compiler/rustc_parse/src/parser/pat.rs
+++ b/compiler/rustc_parse/src/parser/pat.rs
@@ -30,7 +30,7 @@ use crate::errors::{
     UnexpectedVertVertInPattern, WrapInParens,
 };
 use crate::parser::expr::{DestructuredFloat, could_be_unclosed_char_literal};
-use crate::{maybe_recover_from_interpolated_ty_qpath, maybe_whole};
+use crate::{exp, maybe_recover_from_interpolated_ty_qpath, maybe_whole};
 
 #[derive(PartialEq, Copy, Clone)]
 pub enum Expected {
@@ -110,7 +110,7 @@ impl<'a> Parser<'a> {
     ) -> PResult<'a, P<Pat>> {
         let pat = self.parse_pat_no_top_guard(expected, rc, ra, rt)?;
 
-        if self.eat_keyword(kw::If) {
+        if self.eat_keyword(exp!(If)) {
             let cond = self.parse_expr()?;
             // Feature-gate guard patterns
             self.psess.gated_spans.gate(sym::guard_patterns, cond.span);
@@ -193,7 +193,7 @@ impl<'a> Parser<'a> {
 
         // If the next token is not a `|`,
         // this is not an or-pattern and we should exit here.
-        if !self.check(&token::BinOp(token::Or)) && self.token != token::OrOr {
+        if !self.check(exp!(Or)) && self.token != token::OrOr {
             // If we parsed a leading `|` which should be gated,
             // then we should really gate the leading `|`.
             // This complicated procedure is done purely for diagnostics UX.
@@ -263,7 +263,7 @@ impl<'a> Parser<'a> {
             CommaRecoveryMode::LikelyTuple,
             Some(syntax_loc),
         )?;
-        let colon = self.eat(&token::Colon);
+        let colon = self.eat(exp!(Colon));
 
         if let PatKind::Or(pats) = &pat.kind {
             let span = pat.span;
@@ -327,7 +327,7 @@ impl<'a> Parser<'a> {
             self.dcx().emit_err(UnexpectedVertVertInPattern { span: self.token.span, start: lo });
             self.bump();
             EatOrResult::AteOr
-        } else if self.eat(&token::BinOp(token::Or)) {
+        } else if self.eat(exp!(Or)) {
             EatOrResult::AteOr
         } else {
             EatOrResult::None
@@ -714,40 +714,41 @@ impl<'a> Parser<'a> {
             lo = self.token.span;
         }
 
-        let pat = if self.check(&token::BinOp(token::And)) || self.token == token::AndAnd {
+        let pat = if self.check(exp!(And)) || self.token == token::AndAnd {
             self.parse_pat_deref(expected)?
-        } else if self.check(&token::OpenDelim(Delimiter::Parenthesis)) {
+        } else if self.check(exp!(OpenParen)) {
             self.parse_pat_tuple_or_parens()?
-        } else if self.check(&token::OpenDelim(Delimiter::Bracket)) {
+        } else if self.check(exp!(OpenBracket)) {
             // Parse `[pat, pat,...]` as a slice pattern.
-            let (pats, _) = self.parse_delim_comma_seq(Delimiter::Bracket, |p| {
-                p.parse_pat_allow_top_guard(
-                    None,
-                    RecoverComma::No,
-                    RecoverColon::No,
-                    CommaRecoveryMode::EitherTupleOrPipe,
-                )
-            })?;
+            let (pats, _) =
+                self.parse_delim_comma_seq(exp!(OpenBracket), exp!(CloseBracket), |p| {
+                    p.parse_pat_allow_top_guard(
+                        None,
+                        RecoverComma::No,
+                        RecoverColon::No,
+                        CommaRecoveryMode::EitherTupleOrPipe,
+                    )
+                })?;
             PatKind::Slice(pats)
-        } else if self.check(&token::DotDot) && !self.is_pat_range_end_start(1) {
+        } else if self.check(exp!(DotDot)) && !self.is_pat_range_end_start(1) {
             // A rest pattern `..`.
             self.bump(); // `..`
             PatKind::Rest
-        } else if self.check(&token::DotDotDot) && !self.is_pat_range_end_start(1) {
+        } else if self.check(exp!(DotDotDot)) && !self.is_pat_range_end_start(1) {
             self.recover_dotdotdot_rest_pat(lo)
         } else if let Some(form) = self.parse_range_end() {
             self.parse_pat_range_to(form)? // `..=X`, `...X`, or `..X`.
-        } else if self.eat(&token::Not) {
+        } else if self.eat(exp!(Not)) {
             // Parse `!`
             self.psess.gated_spans.gate(sym::never_patterns, self.prev_token.span);
             PatKind::Never
-        } else if self.eat_keyword(kw::Underscore) {
+        } else if self.eat_keyword(exp!(Underscore)) {
             // Parse `_`
             PatKind::Wild
-        } else if self.eat_keyword(kw::Mut) {
+        } else if self.eat_keyword(exp!(Mut)) {
             self.parse_pat_ident_mut()?
-        } else if self.eat_keyword(kw::Ref) {
-            if self.check_keyword(kw::Box) {
+        } else if self.eat_keyword(exp!(Ref)) {
+            if self.check_keyword(exp!(Box)) {
                 // Suggest `box ref`.
                 let span = self.prev_token.span.to(self.token.span);
                 self.bump();
@@ -756,7 +757,7 @@ impl<'a> Parser<'a> {
             // Parse ref ident @ pat / ref mut ident @ pat
             let mutbl = self.parse_mutability();
             self.parse_pat_ident(BindingMode(ByRef::Yes(mutbl), Mutability::Not), syntax_loc)?
-        } else if self.eat_keyword(kw::Box) {
+        } else if self.eat_keyword(exp!(Box)) {
             self.parse_pat_box()?
         } else if self.check_inline_const(0) {
             // Parse `const pat`
@@ -793,14 +794,14 @@ impl<'a> Parser<'a> {
             };
             let span = lo.to(self.prev_token.span);
 
-            if qself.is_none() && self.check(&token::Not) {
+            if qself.is_none() && self.check(exp!(Not)) {
                 self.parse_pat_mac_invoc(path)?
             } else if let Some(form) = self.parse_range_end() {
                 let begin = self.mk_expr(span, ExprKind::Path(qself, path));
                 self.parse_pat_range_begin_with(begin, form)?
-            } else if self.check(&token::OpenDelim(Delimiter::Brace)) {
+            } else if self.check(exp!(OpenBrace)) {
                 self.parse_pat_struct(qself, path)?
-            } else if self.check(&token::OpenDelim(Delimiter::Parenthesis)) {
+            } else if self.check(exp!(OpenParen)) {
                 self.parse_pat_tuple_struct(qself, path)?
             } else {
                 match self.maybe_recover_trailing_expr(span, false) {
@@ -1106,7 +1107,7 @@ impl<'a> Parser<'a> {
     /// Eat any extraneous `mut`s and error + recover if we ate any.
     fn recover_additional_muts(&mut self) {
         let lo = self.token.span;
-        while self.eat_keyword(kw::Mut) {}
+        while self.eat_keyword(exp!(Mut)) {}
         if lo == self.token.span {
             return;
         }
@@ -1147,11 +1148,11 @@ impl<'a> Parser<'a> {
 
     /// Parses the range pattern end form `".." | "..." | "..=" ;`.
     fn parse_range_end(&mut self) -> Option<Spanned<RangeEnd>> {
-        let re = if self.eat(&token::DotDotDot) {
+        let re = if self.eat(exp!(DotDotDot)) {
             RangeEnd::Included(RangeSyntax::DotDotDot)
-        } else if self.eat(&token::DotDotEq) {
+        } else if self.eat(exp!(DotDotEq)) {
             RangeEnd::Included(RangeSyntax::DotDotEq)
-        } else if self.eat(&token::DotDot) {
+        } else if self.eat(exp!(DotDot)) {
             RangeEnd::Excluded
         } else {
             return None;
@@ -1271,7 +1272,7 @@ impl<'a> Parser<'a> {
 
         // recover trailing `)`
         if let Some(open_paren) = open_paren {
-            self.expect(&token::CloseDelim(Delimiter::Parenthesis))?;
+            self.expect(exp!(CloseParen))?;
 
             self.dcx().emit_err(UnexpectedParenInRangePat {
                 span: vec![open_paren, self.prev_token.span],
@@ -1331,7 +1332,7 @@ impl<'a> Parser<'a> {
             }));
         }
 
-        let sub = if self.eat(&token::At) {
+        let sub = if self.eat(exp!(At)) {
             Some(self.parse_pat_no_top_alt(Some(Expected::BindingPattern), None)?)
         } else {
             None
@@ -1447,7 +1448,7 @@ impl<'a> Parser<'a> {
 
             // We cannot use `parse_pat_ident()` since it will complain `box`
             // is not an identifier.
-            let sub = if self.eat(&token::At) {
+            let sub = if self.eat(exp!(At)) {
                 Some(self.parse_pat_no_top_alt(Some(Expected::BindingPattern), None)?)
             } else {
                 None
@@ -1504,9 +1505,9 @@ impl<'a> Parser<'a> {
             }
             ate_comma = false;
 
-            if self.check(&token::DotDot)
+            if self.check(exp!(DotDot))
                 || self.check_noexpect(&token::DotDotDot)
-                || self.check_keyword(kw::Underscore)
+                || self.check_keyword(exp!(Underscore))
             {
                 etc = PatFieldsRest::Rest;
                 let mut etc_sp = self.token.span;
@@ -1594,7 +1595,7 @@ impl<'a> Parser<'a> {
                         return Err(err);
                     }
                 }?;
-                ate_comma = this.eat(&token::Comma);
+                ate_comma = this.eat(exp!(Comma));
 
                 last_non_comma_dotdot_span = Some(this.prev_token.span);
 
@@ -1706,7 +1707,7 @@ impl<'a> Parser<'a> {
             (pat, fieldname, false)
         } else {
             // Parsing a pattern of the form `(box) (ref) (mut) fieldname`.
-            let is_box = self.eat_keyword(kw::Box);
+            let is_box = self.eat_keyword(exp!(Box));
             let boxed_span = self.token.span;
             let mutability = self.parse_mutability();
             let by_ref = self.parse_byref();
diff --git a/compiler/rustc_parse/src/parser/path.rs b/compiler/rustc_parse/src/parser/path.rs
index 3505ec88d04..73612d1da29 100644
--- a/compiler/rustc_parse/src/parser/path.rs
+++ b/compiler/rustc_parse/src/parser/path.rs
@@ -17,7 +17,7 @@ use super::ty::{AllowPlus, RecoverQPath, RecoverReturnSign};
 use super::{Parser, Restrictions, TokenType};
 use crate::errors::{PathSingleColon, PathTripleColon};
 use crate::parser::{CommaRecoveryMode, RecoverColon, RecoverComma};
-use crate::{errors, maybe_whole};
+use crate::{errors, exp, maybe_whole};
 
 /// Specifies how to parse a path.
 #[derive(Copy, Clone, PartialEq)]
@@ -80,7 +80,7 @@ impl<'a> Parser<'a> {
         // above). `path_span` has the span of that path, or an empty
         // span in the case of something like `<T>::Bar`.
         let (mut path, path_span);
-        if self.eat_keyword(kw::As) {
+        if self.eat_keyword(exp!(As)) {
             let path_lo = self.token.span;
             path = self.parse_path(PathStyle::Type)?;
             path_span = path_lo.to(self.prev_token.span);
@@ -90,7 +90,7 @@ impl<'a> Parser<'a> {
         }
 
         // See doc comment for `unmatched_angle_bracket_count`.
-        self.expect(&token::Gt)?;
+        self.expect(exp!(Gt))?;
         if self.unmatched_angle_bracket_count > 0 {
             self.unmatched_angle_bracket_count -= 1;
             debug!("parse_qpath: (decrement) count={:?}", self.unmatched_angle_bracket_count);
@@ -98,7 +98,7 @@ impl<'a> Parser<'a> {
 
         let is_import_coupler = self.is_import_coupler();
         if !is_import_coupler && !self.recover_colon_before_qpath_proj() {
-            self.expect(&token::PathSep)?;
+            self.expect(exp!(PathSep))?;
         }
 
         let qself = P(QSelf { ty, path_span, position: path.segments.len() });
@@ -242,7 +242,7 @@ impl<'a> Parser<'a> {
                 // `PathStyle::Expr` is only provided at the root invocation and never in
                 // `parse_path_segment` to recurse and therefore can be checked to maintain
                 // this invariant.
-                self.check_trailing_angle_brackets(&segment, &[&token::PathSep]);
+                self.check_trailing_angle_brackets(&segment, &[exp!(PathSep)]);
             }
             segments.push(segment);
 
@@ -275,7 +275,7 @@ impl<'a> Parser<'a> {
     /// Eat `::` or, potentially, `:::`.
     #[must_use]
     pub(super) fn eat_path_sep(&mut self) -> bool {
-        let result = self.eat(&token::PathSep);
+        let result = self.eat(exp!(PathSep));
         if result && self.may_recover() {
             if self.eat_noexpect(&token::Colon) {
                 self.dcx().emit_err(PathTripleColon { span: self.prev_token.span });
@@ -300,10 +300,8 @@ impl<'a> Parser<'a> {
             )
         };
         let check_args_start = |this: &mut Self| {
-            this.expected_token_types.extend_from_slice(&[
-                TokenType::Token(token::Lt),
-                TokenType::Token(token::OpenDelim(Delimiter::Parenthesis)),
-            ]);
+            this.expected_token_types.insert(TokenType::Lt);
+            this.expected_token_types.insert(TokenType::OpenParen);
             is_args_start(&this.token)
         };
 
@@ -367,7 +365,7 @@ impl<'a> Parser<'a> {
                 {
                     self.bump(); // (
                     self.bump(); // ..
-                    self.expect(&token::CloseDelim(Delimiter::Parenthesis))?;
+                    self.expect(exp!(CloseParen))?;
                     let span = lo.to(self.prev_token.span);
 
                     self.psess.gated_spans.gate(sym::return_type_notation, span);
@@ -661,12 +659,12 @@ impl<'a> Parser<'a> {
         let mut args = ThinVec::new();
         while let Some(arg) = self.parse_angle_arg(ty_generics)? {
             args.push(arg);
-            if !self.eat(&token::Comma) {
+            if !self.eat(exp!(Comma)) {
                 if self.check_noexpect(&TokenKind::Semi)
                     && self.look_ahead(1, |t| t.is_ident() || t.is_lifetime())
                 {
                     // Add `>` to the list of expected tokens.
-                    self.check(&token::Gt);
+                    self.check(exp!(Gt));
                     // Handle `,` to `;` substitution
                     let mut err = self.unexpected().unwrap_err();
                     self.bump();
@@ -705,7 +703,7 @@ impl<'a> Parser<'a> {
                 // is present and then use that info to push the other token onto the tokens list
                 let separated =
                     self.check_noexpect(&token::Colon) || self.check_noexpect(&token::Eq);
-                if separated && (self.check(&token::Colon) | self.check(&token::Eq)) {
+                if separated && (self.check(exp!(Colon)) | self.check(exp!(Eq))) {
                     let arg_span = arg.span();
                     let (binder, ident, gen_args) = match self.get_ident_from_generic_arg(&arg) {
                         Ok(ident_gen_args) => ident_gen_args,
@@ -720,9 +718,9 @@ impl<'a> Parser<'a> {
                             "`for<...>` is not allowed on associated type bounds",
                         ));
                     }
-                    let kind = if self.eat(&token::Colon) {
+                    let kind = if self.eat(exp!(Colon)) {
                         AssocItemConstraintKind::Bound { bounds: self.parse_generic_bounds()? }
-                    } else if self.eat(&token::Eq) {
+                    } else if self.eat(exp!(Eq)) {
                         self.parse_assoc_equality_term(
                             ident,
                             gen_args.as_ref(),
@@ -743,8 +741,8 @@ impl<'a> Parser<'a> {
                     if self.prev_token.is_ident()
                         && (self.token.is_ident() || self.look_ahead(1, |token| token.is_ident()))
                     {
-                        self.check(&token::Colon);
-                        self.check(&token::Eq);
+                        self.check(exp!(Colon));
+                        self.check(exp!(Eq));
                     }
                     Ok(Some(AngleBracketedArg::Arg(arg)))
                 }
diff --git a/compiler/rustc_parse/src/parser/stmt.rs b/compiler/rustc_parse/src/parser/stmt.rs
index 0f32e396273..151abf0be95 100644
--- a/compiler/rustc_parse/src/parser/stmt.rs
+++ b/compiler/rustc_parse/src/parser/stmt.rs
@@ -24,7 +24,7 @@ use super::{
     Trailing, UsePreAttrPos,
 };
 use crate::errors::MalformedLoopLabel;
-use crate::{errors, maybe_whole};
+use crate::{errors, exp, maybe_whole};
 
 impl<'a> Parser<'a> {
     /// Parses a statement. This stops just before trailing semicolons on everything but items.
@@ -71,7 +71,7 @@ impl<'a> Parser<'a> {
 
         let stmt = if self.token.is_keyword(kw::Let) {
             self.collect_tokens(None, attrs, force_collect, |this, attrs| {
-                this.expect_keyword(kw::Let)?;
+                this.expect_keyword(exp!(Let))?;
                 let local = this.parse_local(attrs)?;
                 let trailing = Trailing::from(capture_semi && this.token == token::Semi);
                 Ok((
@@ -140,7 +140,7 @@ impl<'a> Parser<'a> {
             force_collect,
         )? {
             self.mk_stmt(lo.to(item.span), StmtKind::Item(P(item)))
-        } else if self.eat(&token::Semi) {
+        } else if self.eat(exp!(Semi)) {
             // Do not attempt to parse an expression if we're done here.
             self.error_outer_attrs(attrs);
             self.mk_stmt(lo, StmtKind::Empty)
@@ -156,7 +156,7 @@ impl<'a> Parser<'a> {
                     Ok((expr, Trailing::No, UsePreAttrPos::Yes))
                 },
             )?;
-            if matches!(e.kind, ExprKind::Assign(..)) && self.eat_keyword(kw::Else) {
+            if matches!(e.kind, ExprKind::Assign(..)) && self.eat_keyword(exp!(Else)) {
                 let bl = self.parse_block()?;
                 // Destructuring assignment ... else.
                 // This is not allowed, but point it out in a nice way.
@@ -176,7 +176,7 @@ impl<'a> Parser<'a> {
         let stmt = self.collect_tokens(None, attrs, ForceCollect::No, |this, attrs| {
             let path = this.parse_path(PathStyle::Expr)?;
 
-            if this.eat(&token::Not) {
+            if this.eat(exp!(Not)) {
                 let stmt_mac = this.parse_stmt_mac(lo, attrs, path)?;
                 return Ok((
                     stmt_mac,
@@ -185,7 +185,7 @@ impl<'a> Parser<'a> {
                 ));
             }
 
-            let expr = if this.eat(&token::OpenDelim(Delimiter::Brace)) {
+            let expr = if this.eat(exp!(OpenBrace)) {
                 this.parse_expr_struct(None, path, true)?
             } else {
                 let hi = this.prev_token.span;
@@ -370,7 +370,7 @@ impl<'a> Parser<'a> {
         let kind = match init {
             None => LocalKind::Decl,
             Some(init) => {
-                if self.eat_keyword(kw::Else) {
+                if self.eat_keyword(exp!(Else)) {
                     if self.token.is_keyword(kw::If) {
                         // `let...else if`. Emit the same error that `parse_block()` would,
                         // but explicitly point out that this pattern is not allowed.
@@ -449,7 +449,7 @@ impl<'a> Parser<'a> {
                 self.bump();
                 true
             }
-            _ => self.eat(&token::Eq),
+            _ => self.eat(exp!(Eq)),
         };
 
         Ok(if eq_consumed || eq_optional { Some(self.parse_expr()?) } else { None })
@@ -509,7 +509,7 @@ impl<'a> Parser<'a> {
             Ok(Some(Stmt { kind: StmtKind::Empty, .. })) => {}
             Ok(Some(stmt)) => {
                 let stmt_own_line = self.psess.source_map().is_line_before_span_empty(sp);
-                let stmt_span = if stmt_own_line && self.eat(&token::Semi) {
+                let stmt_span = if stmt_own_line && self.eat(exp!(Semi)) {
                     // Expand the span to include the semicolon.
                     stmt.span.with_hi(self.prev_token.span.hi())
                 } else {
@@ -651,7 +651,7 @@ impl<'a> Parser<'a> {
 
         let maybe_ident = self.prev_token.clone();
         self.maybe_recover_unexpected_block_label();
-        if !self.eat(&token::OpenDelim(Delimiter::Brace)) {
+        if !self.eat(exp!(OpenBrace)) {
             return self.error_block_no_opening_brace();
         }
 
@@ -678,7 +678,7 @@ impl<'a> Parser<'a> {
     ) -> PResult<'a, P<Block>> {
         let mut stmts = ThinVec::new();
         let mut snapshot = None;
-        while !self.eat(&token::CloseDelim(Delimiter::Brace)) {
+        while !self.eat(exp!(CloseBrace)) {
             if self.token == token::Eof {
                 break;
             }
@@ -781,8 +781,7 @@ impl<'a> Parser<'a> {
             {
                 // Just check for errors and recover; do not eat semicolon yet.
 
-                let expect_result =
-                    self.expect_one_of(&[], &[token::Semi, token::CloseDelim(Delimiter::Brace)]);
+                let expect_result = self.expect_one_of(&[], &[exp!(Semi), exp!(CloseBrace)]);
 
                 // Try to both emit a better diagnostic, and avoid further errors by replacing
                 // the `expr` with `ExprKind::Err`.
@@ -930,7 +929,7 @@ impl<'a> Parser<'a> {
             }
         }
 
-        if add_semi_to_stmt || (eat_semi && self.eat(&token::Semi)) {
+        if add_semi_to_stmt || (eat_semi && self.eat(exp!(Semi))) {
             stmt = stmt.add_trailing_semicolon();
         }
 
diff --git a/compiler/rustc_parse/src/parser/token_type.rs b/compiler/rustc_parse/src/parser/token_type.rs
new file mode 100644
index 00000000000..09c7268ada9
--- /dev/null
+++ b/compiler/rustc_parse/src/parser/token_type.rs
@@ -0,0 +1,620 @@
+use rustc_ast::token::TokenKind;
+use rustc_span::symbol::{Symbol, kw, sym};
+
+/// Used in "expected"/"expected one of" error messages. Tokens are added here
+/// as necessary. Tokens with values (e.g. literals, identifiers) are
+/// represented by a single variant (e.g. `Literal`, `Ident`).
+///
+/// It's an awkward representation, but it's important for performance. It's a
+/// C-style parameterless enum so that `TokenTypeSet` can be a bitset. This is
+/// important because `Parser::expected_token_types` is very hot. `TokenType`
+/// used to have variants with parameters (e.g. all the keywords were in a
+/// single `Keyword` variant with a `Symbol` parameter) and
+/// `Parser::expected_token_types` was a `Vec<TokenType>` which was much slower
+/// to manipulate.
+///
+/// We really want to keep the number of variants to 128 or fewer, so that
+/// `TokenTypeSet` can be implemented with a `u128`.
+#[derive(Debug, Clone, Copy, PartialEq)]
+pub enum TokenType {
+    // Expression-operator symbols
+    Eq,
+    Lt,
+    Le,
+    EqEq,
+    Gt,
+    AndAnd,
+    OrOr,
+    Not,
+    Tilde,
+
+    // BinOps
+    Plus,
+    Minus,
+    Star,
+    And,
+    Or,
+
+    // Structural symbols
+    At,
+    Dot,
+    DotDot,
+    DotDotDot,
+    DotDotEq,
+    Comma,
+    Semi,
+    Colon,
+    PathSep,
+    RArrow,
+    FatArrow,
+    Pound,
+    Question,
+    OpenParen,
+    CloseParen,
+    OpenBrace,
+    CloseBrace,
+    OpenBracket,
+    CloseBracket,
+    Eof,
+
+    // Token types with some details elided.
+    /// Any operator.
+    Operator,
+    /// Any identifier token.
+    Ident,
+    /// Any lifetime token.
+    Lifetime,
+    /// Any token that can start a path.
+    Path,
+    /// Any token that can start a type.
+    Type,
+    /// Any token that can start a const expression.
+    Const,
+
+    // Keywords
+    // tidy-alphabetical-start
+    KwAs,
+    KwAsync,
+    KwAuto,
+    KwAwait,
+    KwBecome,
+    KwBox,
+    KwBreak,
+    KwCatch,
+    KwConst,
+    KwContinue,
+    KwCrate,
+    KwDefault,
+    KwDyn,
+    KwElse,
+    KwEnum,
+    KwExtern,
+    KwFn,
+    KwFor,
+    KwGen,
+    KwIf,
+    KwImpl,
+    KwIn,
+    KwLet,
+    KwLoop,
+    KwMacro,
+    KwMacroRules,
+    KwMatch,
+    KwMod,
+    KwMove,
+    KwMut,
+    KwPub,
+    KwRaw,
+    KwRef,
+    KwReturn,
+    KwReuse,
+    KwSafe,
+    KwSelfUpper,
+    KwStatic,
+    KwStruct,
+    KwTrait,
+    KwTry,
+    KwType,
+    KwUnderscore,
+    KwUnsafe,
+    KwUse,
+    KwWhere,
+    KwWhile,
+    KwYield,
+    // tidy-alphabetical-end
+
+    // Keyword-like symbols.
+    // tidy-alphabetical-start
+    SymAttSyntax,
+    SymClobberAbi,
+    SymInlateout,
+    SymInout,
+    SymIs,
+    SymLabel,
+    SymLateout,
+    SymMayUnwind,
+    SymNomem,
+    SymNoreturn,
+    SymNostack,
+    SymOptions,
+    SymOut,
+    SymPreservesFlags,
+    SymPure,
+    SymReadonly,
+    SymSym,
+    // tidy-alphabetical-end
+}
+
+impl TokenType {
+    fn from_u32(val: u32) -> TokenType {
+        let token_type = match val {
+            0 => TokenType::Eq,
+            1 => TokenType::Lt,
+            2 => TokenType::Le,
+            3 => TokenType::EqEq,
+            4 => TokenType::Gt,
+            5 => TokenType::AndAnd,
+            6 => TokenType::OrOr,
+            7 => TokenType::Not,
+            8 => TokenType::Tilde,
+
+            9 => TokenType::Plus,
+            10 => TokenType::Minus,
+            11 => TokenType::Star,
+            12 => TokenType::And,
+            13 => TokenType::Or,
+
+            14 => TokenType::At,
+            15 => TokenType::Dot,
+            16 => TokenType::DotDot,
+            17 => TokenType::DotDotDot,
+            18 => TokenType::DotDotEq,
+            19 => TokenType::Comma,
+            20 => TokenType::Semi,
+            21 => TokenType::Colon,
+            22 => TokenType::PathSep,
+            23 => TokenType::RArrow,
+            24 => TokenType::FatArrow,
+            25 => TokenType::Pound,
+            26 => TokenType::Question,
+            27 => TokenType::OpenParen,
+            28 => TokenType::CloseParen,
+            29 => TokenType::OpenBrace,
+            30 => TokenType::CloseBrace,
+            31 => TokenType::OpenBracket,
+            32 => TokenType::CloseBracket,
+            33 => TokenType::Eof,
+
+            34 => TokenType::Operator,
+            35 => TokenType::Ident,
+            36 => TokenType::Lifetime,
+            37 => TokenType::Path,
+            38 => TokenType::Type,
+            39 => TokenType::Const,
+
+            40 => TokenType::KwAs,
+            41 => TokenType::KwAsync,
+            42 => TokenType::KwAuto,
+            43 => TokenType::KwAwait,
+            44 => TokenType::KwBecome,
+            45 => TokenType::KwBox,
+            46 => TokenType::KwBreak,
+            47 => TokenType::KwCatch,
+            48 => TokenType::KwConst,
+            49 => TokenType::KwContinue,
+            50 => TokenType::KwCrate,
+            51 => TokenType::KwDefault,
+            52 => TokenType::KwDyn,
+            53 => TokenType::KwElse,
+            54 => TokenType::KwEnum,
+            55 => TokenType::KwExtern,
+            56 => TokenType::KwFn,
+            57 => TokenType::KwFor,
+            58 => TokenType::KwGen,
+            59 => TokenType::KwIf,
+            60 => TokenType::KwImpl,
+            61 => TokenType::KwIn,
+            62 => TokenType::KwLet,
+            63 => TokenType::KwLoop,
+            64 => TokenType::KwMacro,
+            65 => TokenType::KwMacroRules,
+            66 => TokenType::KwMatch,
+            67 => TokenType::KwMod,
+            68 => TokenType::KwMove,
+            69 => TokenType::KwMut,
+            70 => TokenType::KwPub,
+            71 => TokenType::KwRaw,
+            72 => TokenType::KwRef,
+            73 => TokenType::KwReturn,
+            74 => TokenType::KwReuse,
+            75 => TokenType::KwSafe,
+            76 => TokenType::KwSelfUpper,
+            77 => TokenType::KwStatic,
+            78 => TokenType::KwStruct,
+            79 => TokenType::KwTrait,
+            80 => TokenType::KwTry,
+            81 => TokenType::KwType,
+            82 => TokenType::KwUnderscore,
+            83 => TokenType::KwUnsafe,
+            84 => TokenType::KwUse,
+            85 => TokenType::KwWhere,
+            86 => TokenType::KwWhile,
+            87 => TokenType::KwYield,
+
+            88 => TokenType::SymAttSyntax,
+            89 => TokenType::SymClobberAbi,
+            90 => TokenType::SymInlateout,
+            91 => TokenType::SymInout,
+            92 => TokenType::SymIs,
+            93 => TokenType::SymLabel,
+            94 => TokenType::SymLateout,
+            95 => TokenType::SymMayUnwind,
+            96 => TokenType::SymNomem,
+            97 => TokenType::SymNoreturn,
+            98 => TokenType::SymNostack,
+            99 => TokenType::SymOptions,
+            100 => TokenType::SymOut,
+            101 => TokenType::SymPreservesFlags,
+            102 => TokenType::SymPure,
+            103 => TokenType::SymReadonly,
+            104 => TokenType::SymSym,
+
+            _ => panic!("unhandled value: {val}"),
+        };
+        // This assertion will detect if this method and the type definition get out of sync.
+        assert_eq!(token_type as u32, val);
+        token_type
+    }
+
+    pub(super) fn is_keyword(&self) -> Option<Symbol> {
+        match self {
+            TokenType::KwAs => Some(kw::As),
+            TokenType::KwAsync => Some(kw::Async),
+            TokenType::KwAuto => Some(kw::Auto),
+            TokenType::KwAwait => Some(kw::Await),
+            TokenType::KwBecome => Some(kw::Become),
+            TokenType::KwBox => Some(kw::Box),
+            TokenType::KwBreak => Some(kw::Break),
+            TokenType::KwCatch => Some(kw::Catch),
+            TokenType::KwConst => Some(kw::Const),
+            TokenType::KwContinue => Some(kw::Continue),
+            TokenType::KwCrate => Some(kw::Crate),
+            TokenType::KwDefault => Some(kw::Default),
+            TokenType::KwDyn => Some(kw::Dyn),
+            TokenType::KwElse => Some(kw::Else),
+            TokenType::KwEnum => Some(kw::Enum),
+            TokenType::KwExtern => Some(kw::Extern),
+            TokenType::KwFn => Some(kw::Fn),
+            TokenType::KwFor => Some(kw::For),
+            TokenType::KwGen => Some(kw::Gen),
+            TokenType::KwIf => Some(kw::If),
+            TokenType::KwImpl => Some(kw::Impl),
+            TokenType::KwIn => Some(kw::In),
+            TokenType::KwLet => Some(kw::Let),
+            TokenType::KwLoop => Some(kw::Loop),
+            TokenType::KwMacroRules => Some(kw::MacroRules),
+            TokenType::KwMacro => Some(kw::Macro),
+            TokenType::KwMatch => Some(kw::Match),
+            TokenType::KwMod => Some(kw::Mod),
+            TokenType::KwMove => Some(kw::Move),
+            TokenType::KwMut => Some(kw::Mut),
+            TokenType::KwPub => Some(kw::Pub),
+            TokenType::KwRaw => Some(kw::Raw),
+            TokenType::KwRef => Some(kw::Ref),
+            TokenType::KwReturn => Some(kw::Return),
+            TokenType::KwReuse => Some(kw::Reuse),
+            TokenType::KwSafe => Some(kw::Safe),
+            TokenType::KwSelfUpper => Some(kw::SelfUpper),
+            TokenType::KwStatic => Some(kw::Static),
+            TokenType::KwStruct => Some(kw::Struct),
+            TokenType::KwTrait => Some(kw::Trait),
+            TokenType::KwTry => Some(kw::Try),
+            TokenType::KwType => Some(kw::Type),
+            TokenType::KwUnderscore => Some(kw::Underscore),
+            TokenType::KwUnsafe => Some(kw::Unsafe),
+            TokenType::KwUse => Some(kw::Use),
+            TokenType::KwWhere => Some(kw::Where),
+            TokenType::KwWhile => Some(kw::While),
+            TokenType::KwYield => Some(kw::Yield),
+
+            TokenType::SymAttSyntax => Some(sym::att_syntax),
+            TokenType::SymClobberAbi => Some(sym::clobber_abi),
+            TokenType::SymInlateout => Some(sym::inlateout),
+            TokenType::SymInout => Some(sym::inout),
+            TokenType::SymIs => Some(sym::is),
+            TokenType::SymLabel => Some(sym::label),
+            TokenType::SymLateout => Some(sym::lateout),
+            TokenType::SymMayUnwind => Some(sym::may_unwind),
+            TokenType::SymNomem => Some(sym::nomem),
+            TokenType::SymNoreturn => Some(sym::noreturn),
+            TokenType::SymNostack => Some(sym::nostack),
+            TokenType::SymOptions => Some(sym::options),
+            TokenType::SymOut => Some(sym::out),
+            TokenType::SymPreservesFlags => Some(sym::preserves_flags),
+            TokenType::SymPure => Some(sym::pure),
+            TokenType::SymReadonly => Some(sym::readonly),
+            TokenType::SymSym => Some(sym::sym),
+            _ => None,
+        }
+    }
+
+    // The output should be the same as that produced by
+    // `rustc_ast_pretty::pprust::token_to_string`.
+    pub(super) fn to_string(&self) -> String {
+        match self {
+            TokenType::Eq => "`=`",
+            TokenType::Lt => "`<`",
+            TokenType::Le => "`<=`",
+            TokenType::EqEq => "`==`",
+            TokenType::Gt => "`>`",
+            TokenType::AndAnd => "`&&`",
+            TokenType::OrOr => "`||`",
+            TokenType::Not => "`!`",
+            TokenType::Tilde => "`~`",
+
+            TokenType::Plus => "`+`",
+            TokenType::Minus => "`-`",
+            TokenType::Star => "`*`",
+            TokenType::And => "`&`",
+            TokenType::Or => "`|`",
+
+            TokenType::At => "`@`",
+            TokenType::Dot => "`.`",
+            TokenType::DotDot => "`..`",
+            TokenType::DotDotDot => "`...`",
+            TokenType::DotDotEq => "`..=`",
+            TokenType::Comma => "`,`",
+            TokenType::Semi => "`;`",
+            TokenType::Colon => "`:`",
+            TokenType::PathSep => "`::`",
+            TokenType::RArrow => "`->`",
+            TokenType::FatArrow => "`=>`",
+            TokenType::Pound => "`#`",
+            TokenType::Question => "`?`",
+            TokenType::OpenParen => "`(`",
+            TokenType::CloseParen => "`)`",
+            TokenType::OpenBrace => "`{`",
+            TokenType::CloseBrace => "`}`",
+            TokenType::OpenBracket => "`[`",
+            TokenType::CloseBracket => "`]`",
+            TokenType::Eof => "<eof>",
+
+            TokenType::Operator => "an operator",
+            TokenType::Ident => "identifier",
+            TokenType::Lifetime => "lifetime",
+            TokenType::Path => "path",
+            TokenType::Type => "type",
+            TokenType::Const => "a const expression",
+
+            _ => return format!("`{}`", self.is_keyword().unwrap()),
+        }
+        .to_string()
+    }
+}
+
+/// Used by various `Parser` methods such as `check` and `eat`. The first field
+/// is always by used those methods. The second field is only used when the
+/// first field doesn't match.
+#[derive(Clone, Copy, Debug)]
+pub struct ExpTokenPair<'a> {
+    pub tok: &'a TokenKind,
+    pub token_type: TokenType,
+}
+
+/// Used by various `Parser` methods such as `check_keyword` and `eat_keyword`.
+/// The first field is always used by those methods. The second field is only
+/// used when the first field doesn't match.
+#[derive(Clone, Copy)]
+pub struct ExpKeywordPair {
+    pub kw: Symbol,
+    pub token_type: TokenType,
+}
+
+// Gets a statically-known `ExpTokenPair` pair (for non-keywords) or
+// `ExpKeywordPair` (for keywords), as used with various `check`/`expect`
+// methods in `Parser`.
+//
+// The name is short because it's used a lot.
+#[macro_export]
+// We don't use the normal `#[rustfmt::skip]` here because that triggers a
+// bogus "macro-expanded `macro_export` macros from the current crate cannot be
+// referred to by absolute paths" error, ugh. See #52234.
+#[cfg_attr(rustfmt, rustfmt::skip)]
+macro_rules! exp {
+    // `ExpTokenPair` helper rules.
+    (@tok, $tok:ident) => {
+        $crate::parser::token_type::ExpTokenPair {
+            tok: &rustc_ast::token::$tok,
+            token_type: $crate::parser::token_type::TokenType::$tok
+        }
+    };
+    (@binop, $op:ident) => {
+        $crate::parser::token_type::ExpTokenPair {
+            tok: &rustc_ast::token::BinOp(rustc_ast::token::BinOpToken::$op),
+            token_type: $crate::parser::token_type::TokenType::$op,
+        }
+    };
+    (@open, $delim:ident, $token_type:ident) => {
+        $crate::parser::token_type::ExpTokenPair {
+            tok: &rustc_ast::token::OpenDelim(rustc_ast::token::Delimiter::$delim),
+            token_type: $crate::parser::token_type::TokenType::$token_type,
+        }
+    };
+    (@close, $delim:ident, $token_type:ident) => {
+        $crate::parser::token_type::ExpTokenPair {
+            tok: &rustc_ast::token::CloseDelim(rustc_ast::token::Delimiter::$delim),
+            token_type: $crate::parser::token_type::TokenType::$token_type,
+        }
+    };
+
+    // `ExpKeywordPair` helper rules.
+    (@kw, $kw:ident, $token_type:ident) => {
+        $crate::parser::token_type::ExpKeywordPair {
+            kw: rustc_span::symbol::kw::$kw,
+            token_type: $crate::parser::token_type::TokenType::$token_type,
+        }
+    };
+    (@sym, $kw:ident, $token_type:ident) => {
+        $crate::parser::token_type::ExpKeywordPair {
+            kw: rustc_span::symbol::sym::$kw,
+            token_type: $crate::parser::token_type::TokenType::$token_type,
+        }
+    };
+
+    (Eq)             => { exp!(@tok, Eq) };
+    (Lt)             => { exp!(@tok, Lt) };
+    (Le)             => { exp!(@tok, Le) };
+    (EqEq)           => { exp!(@tok, EqEq) };
+    (Gt)             => { exp!(@tok, Gt) };
+    (AndAnd)         => { exp!(@tok, AndAnd) };
+    (OrOr)           => { exp!(@tok, OrOr) };
+    (Not)            => { exp!(@tok, Not) };
+    (Tilde)          => { exp!(@tok, Tilde) };
+    (At)             => { exp!(@tok, At) };
+    (Dot)            => { exp!(@tok, Dot) };
+    (DotDot)         => { exp!(@tok, DotDot) };
+    (DotDotDot)      => { exp!(@tok, DotDotDot) };
+    (DotDotEq)       => { exp!(@tok, DotDotEq) };
+    (Comma)          => { exp!(@tok, Comma) };
+    (Semi)           => { exp!(@tok, Semi) };
+    (Colon)          => { exp!(@tok, Colon) };
+    (PathSep)        => { exp!(@tok, PathSep) };
+    (RArrow)         => { exp!(@tok, RArrow) };
+    (FatArrow)       => { exp!(@tok, FatArrow) };
+    (Pound)          => { exp!(@tok, Pound) };
+    (Question)       => { exp!(@tok, Question) };
+    (Eof)            => { exp!(@tok, Eof) };
+
+    (Plus)           => { exp!(@binop, Plus) };
+    (Minus)          => { exp!(@binop, Minus) };
+    (Star)           => { exp!(@binop, Star) };
+    (And)            => { exp!(@binop, And) };
+    (Or)             => { exp!(@binop, Or) };
+
+    (OpenParen)      => { exp!(@open,  Parenthesis, OpenParen) };
+    (OpenBrace)      => { exp!(@open,  Brace,       OpenBrace) };
+    (OpenBracket)    => { exp!(@open,  Bracket,     OpenBracket) };
+    (CloseParen)     => { exp!(@close, Parenthesis, CloseParen) };
+    (CloseBrace)     => { exp!(@close, Brace,       CloseBrace) };
+    (CloseBracket)   => { exp!(@close, Bracket,     CloseBracket) };
+
+    (As)             => { exp!(@kw, As,         KwAs) };
+    (Async)          => { exp!(@kw, Async,      KwAsync) };
+    (Auto)           => { exp!(@kw, Auto,       KwAuto) };
+    (Await)          => { exp!(@kw, Await,      KwAwait) };
+    (Become)         => { exp!(@kw, Become,     KwBecome) };
+    (Box)            => { exp!(@kw, Box,        KwBox) };
+    (Break)          => { exp!(@kw, Break,      KwBreak) };
+    (Catch)          => { exp!(@kw, Catch,      KwCatch) };
+    (Const)          => { exp!(@kw, Const,      KwConst) };
+    (Continue)       => { exp!(@kw, Continue,   KwContinue) };
+    (Crate)          => { exp!(@kw, Crate,      KwCrate) };
+    (Default)        => { exp!(@kw, Default,    KwDefault) };
+    (Dyn)            => { exp!(@kw, Dyn,        KwDyn) };
+    (Else)           => { exp!(@kw, Else,       KwElse) };
+    (Enum)           => { exp!(@kw, Enum,       KwEnum) };
+    (Extern)         => { exp!(@kw, Extern,     KwExtern) };
+    (Fn)             => { exp!(@kw, Fn,         KwFn) };
+    (For)            => { exp!(@kw, For,        KwFor) };
+    (Gen)            => { exp!(@kw, Gen,        KwGen) };
+    (If)             => { exp!(@kw, If,         KwIf) };
+    (Impl)           => { exp!(@kw, Impl,       KwImpl) };
+    (In)             => { exp!(@kw, In,         KwIn) };
+    (Let)            => { exp!(@kw, Let,        KwLet) };
+    (Loop)           => { exp!(@kw, Loop,       KwLoop) };
+    (Macro)          => { exp!(@kw, Macro,      KwMacro) };
+    (MacroRules)     => { exp!(@kw, MacroRules, KwMacroRules) };
+    (Match)          => { exp!(@kw, Match,      KwMatch) };
+    (Mod)            => { exp!(@kw, Mod,        KwMod) };
+    (Move)           => { exp!(@kw, Move,       KwMove) };
+    (Mut)            => { exp!(@kw, Mut,        KwMut) };
+    (Pub)            => { exp!(@kw, Pub,        KwPub) };
+    (Raw)            => { exp!(@kw, Raw,        KwRaw) };
+    (Ref)            => { exp!(@kw, Ref,        KwRef) };
+    (Return)         => { exp!(@kw, Return,     KwReturn) };
+    (Reuse)          => { exp!(@kw, Reuse,      KwReuse) };
+    (Safe)           => { exp!(@kw, Safe,       KwSafe) };
+    (SelfUpper)      => { exp!(@kw, SelfUpper,  KwSelfUpper) };
+    (Static)         => { exp!(@kw, Static,     KwStatic) };
+    (Struct)         => { exp!(@kw, Struct,     KwStruct) };
+    (Trait)          => { exp!(@kw, Trait,      KwTrait) };
+    (Try)            => { exp!(@kw, Try,        KwTry) };
+    (Type)           => { exp!(@kw, Type,       KwType) };
+    (Underscore)     => { exp!(@kw, Underscore, KwUnderscore) };
+    (Unsafe)         => { exp!(@kw, Unsafe,     KwUnsafe) };
+    (Use)            => { exp!(@kw, Use,        KwUse) };
+    (Where)          => { exp!(@kw, Where,      KwWhere) };
+    (While)          => { exp!(@kw, While,      KwWhile) };
+    (Yield)          => { exp!(@kw, Yield,      KwYield) };
+
+    (AttSyntax)      => { exp!(@sym, att_syntax,      SymAttSyntax) };
+    (ClobberAbi)     => { exp!(@sym, clobber_abi,     SymClobberAbi) };
+    (Inlateout)      => { exp!(@sym, inlateout,       SymInlateout) };
+    (Inout)          => { exp!(@sym, inout,           SymInout) };
+    (Is)             => { exp!(@sym, is,              SymIs) };
+    (Label)          => { exp!(@sym, label,           SymLabel) };
+    (Lateout)        => { exp!(@sym, lateout,         SymLateout) };
+    (MayUnwind)      => { exp!(@sym, may_unwind,      SymMayUnwind) };
+    (Nomem)          => { exp!(@sym, nomem,           SymNomem) };
+    (Noreturn)       => { exp!(@sym, noreturn,        SymNoreturn) };
+    (Nostack)        => { exp!(@sym, nostack,         SymNostack) };
+    (Options)        => { exp!(@sym, options,         SymOptions) };
+    (Out)            => { exp!(@sym, out,             SymOut) };
+    (PreservesFlags) => { exp!(@sym, preserves_flags, SymPreservesFlags) };
+    (Pure)           => { exp!(@sym, pure,            SymPure) };
+    (Readonly)       => { exp!(@sym, readonly,        SymReadonly) };
+    (Sym)            => { exp!(@sym, sym,             SymSym) };
+}
+
+/// A bitset type designed specifically for `Parser::expected_token_types`,
+/// which is very hot. `u128` is the smallest integer that will fit every
+/// `TokenType` value.
+#[derive(Clone, Copy)]
+pub(super) struct TokenTypeSet(u128);
+
+impl TokenTypeSet {
+    pub(super) fn new() -> TokenTypeSet {
+        TokenTypeSet(0)
+    }
+
+    pub(super) fn is_empty(&self) -> bool {
+        self.0 == 0
+    }
+
+    pub(super) fn insert(&mut self, token_type: TokenType) {
+        self.0 = self.0 | (1u128 << token_type as u32)
+    }
+
+    pub(super) fn clear(&mut self) {
+        self.0 = 0
+    }
+
+    pub(super) fn contains(&self, token_type: TokenType) -> bool {
+        self.0 & (1u128 << token_type as u32) != 0
+    }
+
+    pub(super) fn iter(&self) -> TokenTypeSetIter {
+        TokenTypeSetIter(*self)
+    }
+}
+
+// The `TokenTypeSet` is a copy of the set being iterated. It initially holds
+// the entire set. Each bit is cleared as it is returned. We have finished once
+// it is all zeroes.
+pub(super) struct TokenTypeSetIter(TokenTypeSet);
+
+impl Iterator for TokenTypeSetIter {
+    type Item = TokenType;
+
+    fn next(&mut self) -> Option<TokenType> {
+        let num_bits: u32 = (std::mem::size_of_val(&self.0.0) * 8) as u32;
+        assert_eq!(num_bits, 128);
+        let z = self.0.0.trailing_zeros();
+        if z == num_bits {
+            None
+        } else {
+            self.0.0 &= !(1 << z); // clear the trailing 1 bit
+            Some(TokenType::from_u32(z))
+        }
+    }
+}
diff --git a/compiler/rustc_parse/src/parser/ty.rs b/compiler/rustc_parse/src/parser/ty.rs
index e4f67d97aa5..6497d19a173 100644
--- a/compiler/rustc_parse/src/parser/ty.rs
+++ b/compiler/rustc_parse/src/parser/ty.rs
@@ -18,7 +18,7 @@ use crate::errors::{
     HelpUseLatestEdition, InvalidDynKeyword, LifetimeAfterMut, NeedPlusAfterTraitObjectLifetime,
     NestedCVariadicType, ReturnTypesUseThinArrow,
 };
-use crate::{maybe_recover_from_interpolated_ty_qpath, maybe_whole};
+use crate::{exp, maybe_recover_from_interpolated_ty_qpath, maybe_whole};
 
 /// Signals whether parsing a type should allow `+`.
 ///
@@ -203,7 +203,7 @@ impl<'a> Parser<'a> {
         recover_return_sign: RecoverReturnSign,
     ) -> PResult<'a, FnRetTy> {
         let lo = self.prev_token.span;
-        Ok(if self.eat(&token::RArrow) {
+        Ok(if self.eat(exp!(RArrow)) {
             // FIXME(Centril): Can we unconditionally `allow_plus`?
             let ty = self.parse_ty_common(
                 allow_plus,
@@ -251,28 +251,28 @@ impl<'a> Parser<'a> {
 
         let lo = self.token.span;
         let mut impl_dyn_multi = false;
-        let kind = if self.check(&token::OpenDelim(Delimiter::Parenthesis)) {
+        let kind = if self.check(exp!(OpenParen)) {
             self.parse_ty_tuple_or_parens(lo, allow_plus)?
-        } else if self.eat(&token::Not) {
+        } else if self.eat(exp!(Not)) {
             // Never type `!`
             TyKind::Never
-        } else if self.eat(&token::BinOp(token::Star)) {
+        } else if self.eat(exp!(Star)) {
             self.parse_ty_ptr()?
-        } else if self.eat(&token::OpenDelim(Delimiter::Bracket)) {
+        } else if self.eat(exp!(OpenBracket)) {
             self.parse_array_or_slice_ty()?
-        } else if self.check(&token::BinOp(token::And)) || self.check(&token::AndAnd) {
+        } else if self.check(exp!(And)) || self.check(exp!(AndAnd)) {
             // Reference
             self.expect_and()?;
             self.parse_borrowed_pointee()?
         } else if self.eat_keyword_noexpect(kw::Typeof) {
             self.parse_typeof_ty()?
-        } else if self.eat_keyword(kw::Underscore) {
+        } else if self.eat_keyword(exp!(Underscore)) {
             // A type to be inferred `_`
             TyKind::Infer
         } else if self.check_fn_front_matter(false, Case::Sensitive) {
             // Function pointer type
             self.parse_ty_bare_fn(lo, ThinVec::new(), None, recover_return_sign)?
-        } else if self.check_keyword(kw::For) {
+        } else if self.check_keyword(exp!(For)) {
             // Function pointer type or bound list (trait object type) starting with a poly-trait.
             //   `for<'lt> [unsafe] [extern "ABI"] fn (&'lt S) -> T`
             //   `for<'lt> Trait1<'lt> + Trait2 + 'a`
@@ -324,7 +324,7 @@ impl<'a> Parser<'a> {
                     self.parse_remaining_bounds_path(lifetime_defs, path, lo, parse_plus)?
                 }
             }
-        } else if self.eat_keyword(kw::Impl) {
+        } else if self.eat_keyword(exp!(Impl)) {
             self.parse_impl_ty(&mut impl_dyn_multi)?
         } else if self.is_explicit_dyn_type() {
             self.parse_dyn_ty(&mut impl_dyn_multi)?
@@ -336,7 +336,7 @@ impl<'a> Parser<'a> {
             self.parse_path_start_ty(lo, allow_plus, ty_generics)?
         } else if self.can_begin_bound() {
             self.parse_bare_trait_object(lo, allow_plus)?
-        } else if self.eat(&token::DotDotDot) {
+        } else if self.eat(exp!(DotDotDot)) {
             match allow_c_variadic {
                 AllowCVariadic::Yes => TyKind::CVarArgs,
                 AllowCVariadic::No => {
@@ -347,7 +347,7 @@ impl<'a> Parser<'a> {
                     TyKind::Err(guar)
                 }
             }
-        } else if self.check_keyword(kw::Unsafe)
+        } else if self.check_keyword(exp!(Unsafe))
             && self.look_ahead(1, |tok| matches!(tok.kind, token::Lt))
         {
             self.parse_unsafe_binder_ty()?
@@ -374,7 +374,7 @@ impl<'a> Parser<'a> {
 
     fn parse_unsafe_binder_ty(&mut self) -> PResult<'a, TyKind> {
         let lo = self.token.span;
-        assert!(self.eat_keyword(kw::Unsafe));
+        assert!(self.eat_keyword(exp!(Unsafe)));
         self.expect_lt()?;
         let generic_params = self.parse_generic_params()?;
         self.expect_gt()?;
@@ -487,16 +487,16 @@ impl<'a> Parser<'a> {
             Err(err) => return Err(err),
         };
 
-        let ty = if self.eat(&token::Semi) {
+        let ty = if self.eat(exp!(Semi)) {
             let mut length = self.parse_expr_anon_const()?;
-            if let Err(e) = self.expect(&token::CloseDelim(Delimiter::Bracket)) {
+            if let Err(e) = self.expect(exp!(CloseBracket)) {
                 // Try to recover from `X<Y, ...>` when `X::<Y, ...>` works
                 self.check_mistyped_turbofish_with_multiple_type_params(e, &mut length.value)?;
-                self.expect(&token::CloseDelim(Delimiter::Bracket))?;
+                self.expect(exp!(CloseBracket))?;
             }
             TyKind::Array(elt_ty, length)
         } else {
-            self.expect(&token::CloseDelim(Delimiter::Bracket))?;
+            self.expect(exp!(CloseBracket))?;
             TyKind::Slice(elt_ty)
         };
 
@@ -579,9 +579,9 @@ impl<'a> Parser<'a> {
     // Parses the `typeof(EXPR)`.
     // To avoid ambiguity, the type is surrounded by parentheses.
     fn parse_typeof_ty(&mut self) -> PResult<'a, TyKind> {
-        self.expect(&token::OpenDelim(Delimiter::Parenthesis))?;
+        self.expect(exp!(OpenParen))?;
         let expr = self.parse_expr_anon_const()?;
-        self.expect(&token::CloseDelim(Delimiter::Parenthesis))?;
+        self.expect(exp!(CloseParen))?;
         Ok(TyKind::Typeof(expr))
     }
 
@@ -697,15 +697,15 @@ impl<'a> Parser<'a> {
         let lo = self.token.span;
         self.expect_lt()?;
         let (args, _, _) = self.parse_seq_to_before_tokens(
-            &[&TokenKind::Gt],
+            &[exp!(Gt)],
             &[
                 &TokenKind::Ge,
                 &TokenKind::BinOp(BinOpToken::Shr),
                 &TokenKind::BinOpEq(BinOpToken::Shr),
             ],
-            SeqSep::trailing_allowed(token::Comma),
+            SeqSep::trailing_allowed(exp!(Comma)),
             |self_| {
-                if self_.check_keyword(kw::SelfUpper) {
+                if self_.check_keyword(exp!(SelfUpper)) {
                     self_.bump();
                     Ok(PreciseCapturingArg::Arg(
                         ast::Path::from_ident(self_.prev_token.ident().unwrap().0),
@@ -729,7 +729,7 @@ impl<'a> Parser<'a> {
 
     /// Is a `dyn B0 + ... + Bn` type allowed here?
     fn is_explicit_dyn_type(&mut self) -> bool {
-        self.check_keyword(kw::Dyn)
+        self.check_keyword(exp!(Dyn))
             && (self.token.uninterpolated_span().at_least_rust_2018()
                 || self.look_ahead(1, |t| {
                     (can_begin_dyn_bound_in_edition_2015(t) || *t == TokenKind::BinOp(token::Star))
@@ -745,7 +745,7 @@ impl<'a> Parser<'a> {
         self.bump(); // `dyn`
 
         // parse dyn* types
-        let syntax = if self.eat(&TokenKind::BinOp(token::Star)) {
+        let syntax = if self.eat(exp!(Star)) {
             self.psess.gated_spans.gate(sym::dyn_star, lo.to(self.prev_token.span));
             TraitObjectSyntax::DynStar
         } else {
@@ -772,7 +772,7 @@ impl<'a> Parser<'a> {
     ) -> PResult<'a, TyKind> {
         // Simple path
         let path = self.parse_path_inner(PathStyle::Type, ty_generics)?;
-        if self.eat(&token::Not) {
+        if self.eat(exp!(Not)) {
             // Macro invocation in type position
             Ok(TyKind::MacCall(P(MacCall { path, args: self.parse_delim_args()? })))
         } else if allow_plus == AllowPlus::Yes && self.check_plus() {
@@ -825,14 +825,14 @@ impl<'a> Parser<'a> {
     fn can_begin_bound(&mut self) -> bool {
         self.check_path()
             || self.check_lifetime()
-            || self.check(&token::Not)
-            || self.check(&token::Question)
-            || self.check(&token::Tilde)
-            || self.check_keyword(kw::For)
-            || self.check(&token::OpenDelim(Delimiter::Parenthesis))
-            || self.check_keyword(kw::Const)
-            || self.check_keyword(kw::Async)
-            || self.check_keyword(kw::Use)
+            || self.check(exp!(Not))
+            || self.check(exp!(Question))
+            || self.check(exp!(Tilde))
+            || self.check_keyword(exp!(For))
+            || self.check(exp!(OpenParen))
+            || self.check_keyword(exp!(Const))
+            || self.check_keyword(exp!(Async))
+            || self.check_keyword(exp!(Use))
     }
 
     /// Parses a bound according to the grammar:
@@ -842,11 +842,11 @@ impl<'a> Parser<'a> {
     fn parse_generic_bound(&mut self) -> PResult<'a, GenericBound> {
         let lo = self.token.span;
         let leading_token = self.prev_token.clone();
-        let has_parens = self.eat(&token::OpenDelim(Delimiter::Parenthesis));
+        let has_parens = self.eat(exp!(OpenParen));
 
         let bound = if self.token.is_lifetime() {
             self.parse_generic_lt_bound(lo, has_parens)?
-        } else if self.eat_keyword(kw::Use) {
+        } else if self.eat_keyword(exp!(Use)) {
             // parse precise captures, if any. This is `use<'lt, 'lt, P, P>`; a list of
             // lifetimes and ident params (including SelfUpper). These are validated later
             // for order, duplication, and whether they actually reference params.
@@ -919,7 +919,7 @@ impl<'a> Parser<'a> {
 
     /// Recover on `('lifetime)` with `(` already eaten.
     fn recover_paren_lifetime(&mut self, lo: Span) -> PResult<'a, ()> {
-        self.expect(&token::CloseDelim(Delimiter::Parenthesis))?;
+        self.expect(exp!(CloseParen))?;
         let span = lo.to(self.prev_token.span);
         let sugg = errors::RemoveParens { lo, hi: self.prev_token.span };
 
@@ -940,13 +940,13 @@ impl<'a> Parser<'a> {
     /// See `parse_generic_ty_bound` for the complete grammar of trait bound modifiers.
     fn parse_trait_bound_modifiers(&mut self) -> PResult<'a, TraitBoundModifiers> {
         let modifier_lo = self.token.span;
-        let constness = if self.eat(&token::Tilde) {
+        let constness = if self.eat(exp!(Tilde)) {
             let tilde = self.prev_token.span;
-            self.expect_keyword(kw::Const)?;
+            self.expect_keyword(exp!(Const))?;
             let span = tilde.to(self.prev_token.span);
             self.psess.gated_spans.gate(sym::const_trait_impl, span);
             BoundConstness::Maybe(span)
-        } else if self.eat_keyword(kw::Const) {
+        } else if self.eat_keyword(exp!(Const)) {
             self.psess.gated_spans.gate(sym::const_trait_impl, self.prev_token.span);
             BoundConstness::Always(self.prev_token.span)
         } else {
@@ -954,7 +954,7 @@ impl<'a> Parser<'a> {
         };
 
         let asyncness = if self.token.uninterpolated_span().at_least_rust_2018()
-            && self.eat_keyword(kw::Async)
+            && self.eat_keyword(exp!(Async))
         {
             self.psess.gated_spans.gate(sym::async_trait_bounds, self.prev_token.span);
             BoundAsyncness::Async(self.prev_token.span)
@@ -974,9 +974,9 @@ impl<'a> Parser<'a> {
         };
         let modifier_hi = self.prev_token.span;
 
-        let polarity = if self.eat(&token::Question) {
+        let polarity = if self.eat(exp!(Question)) {
             BoundPolarity::Maybe(self.prev_token.span)
-        } else if self.eat(&token::Not) {
+        } else if self.eat(exp!(Not)) {
             self.psess.gated_spans.gate(sym::negative_bounds, self.prev_token.span);
             BoundPolarity::Negative(self.prev_token.span)
         } else {
@@ -1122,7 +1122,7 @@ impl<'a> Parser<'a> {
             if self.token.is_like_plus() && leading_token.is_keyword(kw::Dyn) {
                 let bounds = vec![];
                 self.parse_remaining_bounds(bounds, true)?;
-                self.expect(&token::CloseDelim(Delimiter::Parenthesis))?;
+                self.expect(exp!(CloseParen))?;
                 self.dcx().emit_err(errors::IncorrectParensTraitBounds {
                     span: vec![lo, self.prev_token.span],
                     sugg: errors::IncorrectParensTraitBoundsSugg {
@@ -1131,7 +1131,7 @@ impl<'a> Parser<'a> {
                     },
                 });
             } else {
-                self.expect(&token::CloseDelim(Delimiter::Parenthesis))?;
+                self.expect(exp!(CloseParen))?;
             }
         }
 
@@ -1176,7 +1176,7 @@ impl<'a> Parser<'a> {
     pub(super) fn parse_late_bound_lifetime_defs(
         &mut self,
     ) -> PResult<'a, (ThinVec<GenericParam>, Option<Span>)> {
-        if self.eat_keyword(kw::For) {
+        if self.eat_keyword(exp!(For)) {
             let lo = self.token.span;
             self.expect_lt()?;
             let params = self.parse_generic_params()?;
@@ -1280,7 +1280,7 @@ impl<'a> Parser<'a> {
     }
 
     pub(super) fn check_lifetime(&mut self) -> bool {
-        self.expected_token_types.push(TokenType::Lifetime);
+        self.expected_token_types.insert(TokenType::Lifetime);
         self.token.is_lifetime()
     }