Add initial support for raw lifetimes

This commit is contained in:
Michael Goulet 2024-09-05 05:43:55 -04:00
parent 3b3e43a386
commit 97910580aa
18 changed files with 116 additions and 40 deletions

View File

@ -752,7 +752,7 @@ fn visit_lazy_tts<T: MutVisitor>(vis: &mut T, lazy_tts: &mut Option<LazyAttrToke
pub fn visit_token<T: MutVisitor>(vis: &mut T, t: &mut Token) { pub fn visit_token<T: MutVisitor>(vis: &mut T, t: &mut Token) {
let Token { kind, span } = t; let Token { kind, span } = t;
match kind { match kind {
token::Ident(name, _ /*raw*/) | token::Lifetime(name) => { token::Ident(name, _is_raw) | token::Lifetime(name, _is_raw) => {
let mut ident = Ident::new(*name, *span); let mut ident = Ident::new(*name, *span);
vis.visit_ident(&mut ident); vis.visit_ident(&mut ident);
*name = ident.name; *name = ident.name;
@ -762,7 +762,7 @@ pub fn visit_token<T: MutVisitor>(vis: &mut T, t: &mut Token) {
token::NtIdent(ident, _is_raw) => { token::NtIdent(ident, _is_raw) => {
vis.visit_ident(ident); vis.visit_ident(ident);
} }
token::NtLifetime(ident) => { token::NtLifetime(ident, _is_raw) => {
vis.visit_ident(ident); vis.visit_ident(ident);
} }
token::Interpolated(nt) => { token::Interpolated(nt) => {

View File

@ -331,11 +331,11 @@ pub enum TokenKind {
/// Do not forget about `NtLifetime` when you want to match on lifetime identifiers. /// Do not forget about `NtLifetime` when you want to match on lifetime identifiers.
/// It's recommended to use `Token::(lifetime,uninterpolate,uninterpolated_span)` to /// It's recommended to use `Token::(lifetime,uninterpolate,uninterpolated_span)` to
/// treat regular and interpolated lifetime identifiers in the same way. /// treat regular and interpolated lifetime identifiers in the same way.
Lifetime(Symbol), Lifetime(Symbol, IdentIsRaw),
/// This identifier (and its span) is the lifetime passed to the /// This identifier (and its span) is the lifetime passed to the
/// declarative macro. The span in the surrounding `Token` is the span of /// declarative macro. The span in the surrounding `Token` is the span of
/// the `lifetime` metavariable in the macro's RHS. /// the `lifetime` metavariable in the macro's RHS.
NtLifetime(Ident), NtLifetime(Ident, IdentIsRaw),
/// An embedded AST node, as produced by a macro. This only exists for /// An embedded AST node, as produced by a macro. This only exists for
/// historical reasons. We'd like to get rid of it, for multiple reasons. /// historical reasons. We'd like to get rid of it, for multiple reasons.
@ -458,7 +458,7 @@ impl Token {
/// if they keep spans or perform edition checks. /// if they keep spans or perform edition checks.
pub fn uninterpolated_span(&self) -> Span { pub fn uninterpolated_span(&self) -> Span {
match self.kind { match self.kind {
NtIdent(ident, _) | NtLifetime(ident) => ident.span, NtIdent(ident, _) | NtLifetime(ident, _) => ident.span,
Interpolated(ref nt) => nt.use_span(), Interpolated(ref nt) => nt.use_span(),
_ => self.span, _ => self.span,
} }
@ -661,7 +661,9 @@ impl Token {
pub fn uninterpolate(&self) -> Cow<'_, Token> { pub fn uninterpolate(&self) -> Cow<'_, Token> {
match self.kind { match self.kind {
NtIdent(ident, is_raw) => Cow::Owned(Token::new(Ident(ident.name, is_raw), ident.span)), NtIdent(ident, is_raw) => Cow::Owned(Token::new(Ident(ident.name, is_raw), ident.span)),
NtLifetime(ident) => Cow::Owned(Token::new(Lifetime(ident.name), ident.span)), NtLifetime(ident, is_raw) => {
Cow::Owned(Token::new(Lifetime(ident.name, is_raw), ident.span))
}
_ => Cow::Borrowed(self), _ => Cow::Borrowed(self),
} }
} }
@ -679,11 +681,11 @@ impl Token {
/// Returns a lifetime identifier if this token is a lifetime. /// Returns a lifetime identifier if this token is a lifetime.
#[inline] #[inline]
pub fn lifetime(&self) -> Option<Ident> { pub fn lifetime(&self) -> Option<(Ident, IdentIsRaw)> {
// We avoid using `Token::uninterpolate` here because it's slow. // We avoid using `Token::uninterpolate` here because it's slow.
match self.kind { match self.kind {
Lifetime(name) => Some(Ident::new(name, self.span)), Lifetime(name, is_raw) => Some((Ident::new(name, self.span), is_raw)),
NtLifetime(ident) => Some(ident), NtLifetime(ident, is_raw) => Some((ident, is_raw)),
_ => None, _ => None,
} }
} }
@ -865,7 +867,7 @@ impl Token {
_ => return None, _ => return None,
}, },
SingleQuote => match joint.kind { SingleQuote => match joint.kind {
Ident(name, IdentIsRaw::No) => Lifetime(Symbol::intern(&format!("'{name}"))), Ident(name, is_raw) => Lifetime(Symbol::intern(&format!("'{name}")), is_raw),
_ => return None, _ => return None,
}, },

View File

@ -482,11 +482,11 @@ impl TokenStream {
token::NtIdent(ident, is_raw) => { token::NtIdent(ident, is_raw) => {
TokenTree::Token(Token::new(token::Ident(ident.name, is_raw), ident.span), spacing) TokenTree::Token(Token::new(token::Ident(ident.name, is_raw), ident.span), spacing)
} }
token::NtLifetime(ident) => TokenTree::Delimited( token::NtLifetime(ident, is_raw) => TokenTree::Delimited(
DelimSpan::from_single(token.span), DelimSpan::from_single(token.span),
DelimSpacing::new(Spacing::JointHidden, spacing), DelimSpacing::new(Spacing::JointHidden, spacing),
Delimiter::Invisible, Delimiter::Invisible,
TokenStream::token_alone(token::Lifetime(ident.name), ident.span), TokenStream::token_alone(token::Lifetime(ident.name, is_raw), ident.span),
), ),
token::Interpolated(ref nt) => TokenTree::Delimited( token::Interpolated(ref nt) => TokenTree::Delimited(
DelimSpan::from_single(token.span), DelimSpan::from_single(token.span),

View File

@ -11,7 +11,9 @@ use std::borrow::Cow;
use ast::TraitBoundModifiers; use ast::TraitBoundModifiers;
use rustc_ast::attr::AttrIdGenerator; use rustc_ast::attr::AttrIdGenerator;
use rustc_ast::ptr::P; use rustc_ast::ptr::P;
use rustc_ast::token::{self, BinOpToken, CommentKind, Delimiter, Nonterminal, Token, TokenKind}; use rustc_ast::token::{
self, BinOpToken, CommentKind, Delimiter, IdentIsRaw, Nonterminal, Token, TokenKind,
};
use rustc_ast::tokenstream::{Spacing, TokenStream, TokenTree}; use rustc_ast::tokenstream::{Spacing, TokenStream, TokenTree};
use rustc_ast::util::classify; use rustc_ast::util::classify;
use rustc_ast::util::comments::{Comment, CommentStyle}; use rustc_ast::util::comments::{Comment, CommentStyle};
@ -947,8 +949,13 @@ pub trait PrintState<'a>: std::ops::Deref<Target = pp::Printer> + std::ops::Dere
token::NtIdent(ident, is_raw) => { token::NtIdent(ident, is_raw) => {
IdentPrinter::for_ast_ident(ident, is_raw.into()).to_string().into() IdentPrinter::for_ast_ident(ident, is_raw.into()).to_string().into()
} }
token::Lifetime(name) => name.to_string().into(),
token::NtLifetime(ident) => ident.name.to_string().into(), token::Lifetime(name, IdentIsRaw::No)
| token::NtLifetime(Ident { name, .. }, IdentIsRaw::No) => name.to_string().into(),
token::Lifetime(name, IdentIsRaw::Yes)
| token::NtLifetime(Ident { name, .. }, IdentIsRaw::Yes) => {
format!("'r#{}", &name.as_str()[1..]).into()
}
/* Other */ /* Other */
token::DocComment(comment_kind, attr_style, data) => { token::DocComment(comment_kind, attr_style, data) => {

View File

@ -398,8 +398,10 @@ pub(crate) enum NamedMatch {
fn token_name_eq(t1: &Token, t2: &Token) -> bool { fn token_name_eq(t1: &Token, t2: &Token) -> bool {
if let (Some((ident1, is_raw1)), Some((ident2, is_raw2))) = (t1.ident(), t2.ident()) { if let (Some((ident1, is_raw1)), Some((ident2, is_raw2))) = (t1.ident(), t2.ident()) {
ident1.name == ident2.name && is_raw1 == is_raw2 ident1.name == ident2.name && is_raw1 == is_raw2
} else if let (Some(ident1), Some(ident2)) = (t1.lifetime(), t2.lifetime()) { } else if let (Some((ident1, is_raw1)), Some((ident2, is_raw2))) =
ident1.name == ident2.name (t1.lifetime(), t2.lifetime())
{
ident1.name == ident2.name && is_raw1 == is_raw2
} else { } else {
t1.kind == t2.kind t1.kind == t2.kind
} }

View File

@ -283,9 +283,9 @@ pub(super) fn transcribe<'a>(
let kind = token::NtIdent(*ident, *is_raw); let kind = token::NtIdent(*ident, *is_raw);
TokenTree::token_alone(kind, sp) TokenTree::token_alone(kind, sp)
} }
MatchedSingle(ParseNtResult::Lifetime(ident)) => { MatchedSingle(ParseNtResult::Lifetime(ident, is_raw)) => {
marker.visit_span(&mut sp); marker.visit_span(&mut sp);
let kind = token::NtLifetime(*ident); let kind = token::NtLifetime(*ident, *is_raw);
TokenTree::token_alone(kind, sp) TokenTree::token_alone(kind, sp)
} }
MatchedSingle(ParseNtResult::Nt(nt)) => { MatchedSingle(ParseNtResult::Nt(nt)) => {

View File

@ -229,15 +229,16 @@ impl FromInternal<(TokenStream, &mut Rustc<'_, '_>)> for Vec<TokenTree<TokenStre
span: ident.span, span: ident.span,
})), })),
Lifetime(name) => { Lifetime(name, is_raw) => {
let ident = symbol::Ident::new(name, span).without_first_quote(); let ident = symbol::Ident::new(name, span).without_first_quote();
trees.extend([ trees.extend([
TokenTree::Punct(Punct { ch: b'\'', joint: true, span }), TokenTree::Punct(Punct { ch: b'\'', joint: true, span }),
TokenTree::Ident(Ident { sym: ident.name, is_raw: false, span }), TokenTree::Ident(Ident { sym: ident.name, is_raw: is_raw.into(), span }),
]); ]);
} }
NtLifetime(ident) => { NtLifetime(ident, is_raw) => {
let stream = TokenStream::token_alone(token::Lifetime(ident.name), ident.span); let stream =
TokenStream::token_alone(token::Lifetime(ident.name, is_raw), ident.span);
trees.push(TokenTree::Group(Group { trees.push(TokenTree::Group(Group {
delimiter: pm::Delimiter::None, delimiter: pm::Delimiter::None,
stream: Some(stream), stream: Some(stream),

View File

@ -97,6 +97,9 @@ pub enum TokenKind {
/// and not the separator. /// and not the separator.
UnknownPrefixLifetime, UnknownPrefixLifetime,
/// `'r#lt`, which in edition < 2021 is split into several tokens: `'r # lt`.
RawLifetime,
/// Similar to the above, but *always* an error on every edition. This is used /// Similar to the above, but *always* an error on every edition. This is used
/// for emoji identifier recovery, as those are not meant to be ever accepted. /// for emoji identifier recovery, as those are not meant to be ever accepted.
InvalidPrefix, InvalidPrefix,
@ -683,9 +686,17 @@ impl Cursor<'_> {
return Literal { kind, suffix_start }; return Literal { kind, suffix_start };
} }
if self.first() == 'r' && self.second() == '#' && is_id_start(self.third()) {
// Eat "r" and `#`, and identifier start characters.
self.bump();
self.bump();
self.bump();
self.eat_while(is_id_continue);
return RawLifetime;
}
// Either a lifetime or a character literal with // Either a lifetime or a character literal with
// length greater than 1. // length greater than 1.
let starts_with_number = self.first().is_ascii_digit(); let starts_with_number = self.first().is_ascii_digit();
// Skip the literal contents. // Skip the literal contents.

View File

@ -707,6 +707,10 @@ lint_range_endpoint_out_of_range = range endpoint is out of range for `{$ty}`
lint_range_use_inclusive_range = use an inclusive range instead lint_range_use_inclusive_range = use an inclusive range instead
lint_raw_prefix = prefix `'r` is reserved
.label = reserved prefix
.suggestion = insert whitespace here to avoid this being parsed as a prefix in Rust 2021
lint_reason_must_be_string_literal = reason must be a string literal lint_reason_must_be_string_literal = reason must be a string literal
lint_reason_must_come_last = reason in lint attribute must come last lint_reason_must_come_last = reason in lint attribute must come last

View File

@ -172,6 +172,10 @@ pub(super) fn decorate_lint(sess: &Session, diagnostic: BuiltinLintDiag, diag: &
} }
.decorate_lint(diag); .decorate_lint(diag);
} }
BuiltinLintDiag::RawPrefix(label_span) => {
lints::RawPrefix { label: label_span, suggestion: label_span.shrink_to_hi() }
.decorate_lint(diag);
}
BuiltinLintDiag::UnusedBuiltinAttribute { attr_name, macro_name, invoc_span } => { BuiltinLintDiag::UnusedBuiltinAttribute { attr_name, macro_name, invoc_span } => {
lints::UnusedBuiltinAttribute { invoc_span, attr_name, macro_name }.decorate_lint(diag); lints::UnusedBuiltinAttribute { invoc_span, attr_name, macro_name }.decorate_lint(diag);
} }

View File

@ -2772,6 +2772,15 @@ pub(crate) struct ReservedPrefix {
pub prefix: String, pub prefix: String,
} }
#[derive(LintDiagnostic)]
#[diag(lint_raw_prefix)]
pub(crate) struct RawPrefix {
#[label]
pub label: Span,
#[suggestion(code = " ", applicability = "machine-applicable")]
pub suggestion: Span,
}
#[derive(LintDiagnostic)] #[derive(LintDiagnostic)]
#[diag(lint_unused_builtin_attribute)] #[diag(lint_unused_builtin_attribute)]
pub(crate) struct UnusedBuiltinAttribute { pub(crate) struct UnusedBuiltinAttribute {

View File

@ -609,6 +609,8 @@ pub enum BuiltinLintDiag {
LegacyDeriveHelpers(Span), LegacyDeriveHelpers(Span),
OrPatternsBackCompat(Span, String), OrPatternsBackCompat(Span, String),
ReservedPrefix(Span, String), ReservedPrefix(Span, String),
/// `'r#` in edition < 2021.
RawPrefix(Span),
TrailingMacro(bool, Ident), TrailingMacro(bool, Ident),
BreakWithLabelAndLoop(Span), BreakWithLabelAndLoop(Span),
UnicodeTextFlow(Span, String), UnicodeTextFlow(Span, String),

View File

@ -13,7 +13,6 @@ use rustc_session::lint::builtin::{
}; };
use rustc_session::lint::BuiltinLintDiag; use rustc_session::lint::BuiltinLintDiag;
use rustc_session::parse::ParseSess; use rustc_session::parse::ParseSess;
use rustc_span::edition::Edition;
use rustc_span::symbol::Symbol; use rustc_span::symbol::Symbol;
use rustc_span::{BytePos, Pos, Span}; use rustc_span::{BytePos, Pos, Span};
use tracing::debug; use tracing::debug;
@ -284,7 +283,41 @@ impl<'psess, 'src> StringReader<'psess, 'src> {
.stash(span, StashKey::LifetimeIsChar); .stash(span, StashKey::LifetimeIsChar);
} }
let ident = Symbol::intern(lifetime_name); let ident = Symbol::intern(lifetime_name);
token::Lifetime(ident) token::Lifetime(ident, IdentIsRaw::No)
}
rustc_lexer::TokenKind::RawLifetime => {
self.last_lifetime = Some(self.mk_sp(start, start + BytePos(1)));
let ident_start = start + BytePos(3);
let prefix_span = self.mk_sp(start, ident_start);
if prefix_span.at_least_rust_2021() {
let lifetime_name_without_tick = self.str_from(ident_start);
// Put the `'` back onto the lifetime name.
let mut lifetime_name = String::with_capacity(lifetime_name_without_tick.len() + 1);
lifetime_name.push('\'');
lifetime_name += lifetime_name_without_tick;
let sym = Symbol::intern(&lifetime_name);
token::Lifetime(sym, IdentIsRaw::Yes)
} else {
// Otherwise, this is just `'r`. Warn about it though.
self.psess.buffer_lint(
RUST_2021_PREFIXES_INCOMPATIBLE_SYNTAX,
prefix_span,
ast::CRATE_NODE_ID,
BuiltinLintDiag::RawPrefix(prefix_span),
);
// Reset the state so we just lex the `'r`.
let lt_start = start + BytePos(2);
self.pos = lt_start;
self.cursor = Cursor::new(&str_before[2 as usize..]);
let lifetime_name = self.str_from(start);
let ident = Symbol::intern(lifetime_name);
token::Lifetime(ident, IdentIsRaw::No)
}
} }
rustc_lexer::TokenKind::Semi => token::Semi, rustc_lexer::TokenKind::Semi => token::Semi,
rustc_lexer::TokenKind::Comma => token::Comma, rustc_lexer::TokenKind::Comma => token::Comma,
@ -712,7 +745,7 @@ impl<'psess, 'src> StringReader<'psess, 'src> {
let expn_data = prefix_span.ctxt().outer_expn_data(); let expn_data = prefix_span.ctxt().outer_expn_data();
if expn_data.edition >= Edition::Edition2021 { if expn_data.edition.at_least_rust_2021() {
// In Rust 2021, this is a hard error. // In Rust 2021, this is a hard error.
let sugg = if prefix == "rb" { let sugg = if prefix == "rb" {
Some(errors::UnknownPrefixSugg::UseBr(prefix_span)) Some(errors::UnknownPrefixSugg::UseBr(prefix_span))

View File

@ -2050,7 +2050,7 @@ impl<'a> Parser<'a> {
}; };
// On an error path, eagerly consider a lifetime to be an unclosed character lit, if that // On an error path, eagerly consider a lifetime to be an unclosed character lit, if that
// makes sense. // makes sense.
if let Some(ident) = self.token.lifetime() if let Some((ident, IdentIsRaw::No)) = self.token.lifetime()
&& could_be_unclosed_char_literal(ident) && could_be_unclosed_char_literal(ident)
{ {
let lt = self.expect_lifetime(); let lt = self.expect_lifetime();
@ -2925,9 +2925,9 @@ impl<'a> Parser<'a> {
} }
pub(crate) fn eat_label(&mut self) -> Option<Label> { pub(crate) fn eat_label(&mut self) -> Option<Label> {
if let Some(ident) = self.token.lifetime() { if let Some((ident, is_raw)) = self.token.lifetime() {
// Disallow `'fn`, but with a better error message than `expect_lifetime`. // Disallow `'fn`, but with a better error message than `expect_lifetime`.
if ident.without_first_quote().is_reserved() { if matches!(is_raw, IdentIsRaw::No) && ident.without_first_quote().is_reserved() {
self.dcx().emit_err(errors::InvalidLabel { span: ident.span, name: ident.name }); self.dcx().emit_err(errors::InvalidLabel { span: ident.span, name: ident.name });
} }

View File

@ -1666,7 +1666,7 @@ enum FlatToken {
pub enum ParseNtResult { pub enum ParseNtResult {
Tt(TokenTree), Tt(TokenTree),
Ident(Ident, IdentIsRaw), Ident(Ident, IdentIsRaw),
Lifetime(Ident), Lifetime(Ident, IdentIsRaw),
/// This case will eventually be removed, along with `Token::Interpolate`. /// This case will eventually be removed, along with `Token::Interpolate`.
Nt(Lrc<Nonterminal>), Nt(Lrc<Nonterminal>),

View File

@ -88,7 +88,7 @@ impl<'a> Parser<'a> {
}, },
NonterminalKind::Pat(pat_kind) => token.can_begin_pattern(pat_kind), NonterminalKind::Pat(pat_kind) => token.can_begin_pattern(pat_kind),
NonterminalKind::Lifetime => match &token.kind { NonterminalKind::Lifetime => match &token.kind {
token::Lifetime(_) | token::NtLifetime(..) => true, token::Lifetime(..) | token::NtLifetime(..) => true,
_ => false, _ => false,
}, },
NonterminalKind::TT | NonterminalKind::Item | NonterminalKind::Stmt => { NonterminalKind::TT | NonterminalKind::Item | NonterminalKind::Stmt => {
@ -171,9 +171,9 @@ impl<'a> Parser<'a> {
NonterminalKind::Lifetime => { NonterminalKind::Lifetime => {
// We want to keep `'keyword` parsing, just like `keyword` is still // We want to keep `'keyword` parsing, just like `keyword` is still
// an ident for nonterminal purposes. // an ident for nonterminal purposes.
return if let Some(ident) = self.token.lifetime() { return if let Some((ident, is_raw)) = self.token.lifetime() {
self.bump(); self.bump();
Ok(ParseNtResult::Lifetime(ident)) Ok(ParseNtResult::Lifetime(ident, is_raw))
} else { } else {
Err(self.dcx().create_err(UnexpectedNonterminal::Lifetime { Err(self.dcx().create_err(UnexpectedNonterminal::Lifetime {
span: self.token.span, span: self.token.span,

View File

@ -1,6 +1,6 @@
use rustc_ast::mut_visit::{walk_pat, MutVisitor}; use rustc_ast::mut_visit::{walk_pat, MutVisitor};
use rustc_ast::ptr::P; use rustc_ast::ptr::P;
use rustc_ast::token::{self, BinOpToken, Delimiter, Token}; use rustc_ast::token::{self, BinOpToken, Delimiter, IdentIsRaw, Token};
use rustc_ast::{ use rustc_ast::{
self as ast, AttrVec, BindingMode, ByRef, Expr, ExprKind, MacCall, Mutability, Pat, PatField, self as ast, AttrVec, BindingMode, ByRef, Expr, ExprKind, MacCall, Mutability, Pat, PatField,
PatFieldsRest, PatKind, Path, QSelf, RangeEnd, RangeSyntax, PatFieldsRest, PatKind, Path, QSelf, RangeEnd, RangeSyntax,
@ -548,7 +548,7 @@ impl<'a> Parser<'a> {
None => PatKind::Path(qself, path), None => PatKind::Path(qself, path),
} }
} }
} else if let Some(lt) = self.token.lifetime() } else if let Some((lt, IdentIsRaw::No)) = self.token.lifetime()
// In pattern position, we're totally fine with using "next token isn't colon" // In pattern position, we're totally fine with using "next token isn't colon"
// as a heuristic. We could probably just always try to recover if it's a lifetime, // as a heuristic. We could probably just always try to recover if it's a lifetime,
// because we never have `'a: label {}` in a pattern position anyways, but it does // because we never have `'a: label {}` in a pattern position anyways, but it does
@ -689,7 +689,7 @@ impl<'a> Parser<'a> {
/// Parse `&pat` / `&mut pat`. /// Parse `&pat` / `&mut pat`.
fn parse_pat_deref(&mut self, expected: Option<Expected>) -> PResult<'a, PatKind> { fn parse_pat_deref(&mut self, expected: Option<Expected>) -> PResult<'a, PatKind> {
self.expect_and()?; self.expect_and()?;
if let Some(lifetime) = self.token.lifetime() { if let Some((lifetime, _)) = self.token.lifetime() {
self.bump(); // `'a` self.bump(); // `'a`
self.dcx().emit_err(UnexpectedLifetimeInPattern { self.dcx().emit_err(UnexpectedLifetimeInPattern {

View File

@ -1,5 +1,5 @@
use rustc_ast::ptr::P; use rustc_ast::ptr::P;
use rustc_ast::token::{self, BinOpToken, Delimiter, Token, TokenKind}; use rustc_ast::token::{self, BinOpToken, Delimiter, IdentIsRaw, Token, TokenKind};
use rustc_ast::util::case::Case; use rustc_ast::util::case::Case;
use rustc_ast::{ use rustc_ast::{
self as ast, BareFnTy, BoundAsyncness, BoundConstness, BoundPolarity, FnRetTy, GenericBound, self as ast, BareFnTy, BoundAsyncness, BoundConstness, BoundPolarity, FnRetTy, GenericBound,
@ -1285,8 +1285,9 @@ impl<'a> Parser<'a> {
/// Parses a single lifetime `'a` or panics. /// Parses a single lifetime `'a` or panics.
pub(super) fn expect_lifetime(&mut self) -> Lifetime { pub(super) fn expect_lifetime(&mut self) -> Lifetime {
if let Some(ident) = self.token.lifetime() { if let Some((ident, is_raw)) = self.token.lifetime() {
if ident.without_first_quote().is_reserved() if matches!(is_raw, IdentIsRaw::No)
&& ident.without_first_quote().is_reserved()
&& ![kw::UnderscoreLifetime, kw::StaticLifetime].contains(&ident.name) && ![kw::UnderscoreLifetime, kw::StaticLifetime].contains(&ident.name)
{ {
self.dcx().emit_err(errors::KeywordLifetime { span: ident.span }); self.dcx().emit_err(errors::KeywordLifetime { span: ident.span });