Skip to content

Commit d642752

Browse files
Add initial support for raw lifetimes
1 parent 330a5b3 commit d642752

File tree

18 files changed

+116
-40
lines changed

18 files changed

+116
-40
lines changed

compiler/rustc_ast/src/mut_visit.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -752,7 +752,7 @@ fn visit_lazy_tts<T: MutVisitor>(vis: &mut T, lazy_tts: &mut Option<LazyAttrToke
752752
pub fn visit_token<T: MutVisitor>(vis: &mut T, t: &mut Token) {
753753
let Token { kind, span } = t;
754754
match kind {
755-
token::Ident(name, _ /*raw*/) | token::Lifetime(name) => {
755+
token::Ident(name, _ /* raw */) | token::Lifetime(name, _ /* raw */) => {
756756
let mut ident = Ident::new(*name, *span);
757757
vis.visit_ident(&mut ident);
758758
*name = ident.name;
@@ -762,7 +762,7 @@ pub fn visit_token<T: MutVisitor>(vis: &mut T, t: &mut Token) {
762762
token::NtIdent(ident, _is_raw) => {
763763
vis.visit_ident(ident);
764764
}
765-
token::NtLifetime(ident) => {
765+
token::NtLifetime(ident, _is_raw) => {
766766
vis.visit_ident(ident);
767767
}
768768
token::Interpolated(nt) => {

compiler/rustc_ast/src/token.rs

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -331,11 +331,11 @@ pub enum TokenKind {
331331
/// Do not forget about `NtLifetime` when you want to match on lifetime identifiers.
332332
/// It's recommended to use `Token::(lifetime,uninterpolate,uninterpolated_span)` to
333333
/// treat regular and interpolated lifetime identifiers in the same way.
334-
Lifetime(Symbol),
334+
Lifetime(Symbol, IdentIsRaw),
335335
/// This identifier (and its span) is the lifetime passed to the
336336
/// declarative macro. The span in the surrounding `Token` is the span of
337337
/// the `lifetime` metavariable in the macro's RHS.
338-
NtLifetime(Ident),
338+
NtLifetime(Ident, IdentIsRaw),
339339

340340
/// An embedded AST node, as produced by a macro. This only exists for
341341
/// historical reasons. We'd like to get rid of it, for multiple reasons.
@@ -458,7 +458,7 @@ impl Token {
458458
/// if they keep spans or perform edition checks.
459459
pub fn uninterpolated_span(&self) -> Span {
460460
match self.kind {
461-
NtIdent(ident, _) | NtLifetime(ident) => ident.span,
461+
NtIdent(ident, _) | NtLifetime(ident, _) => ident.span,
462462
Interpolated(ref nt) => nt.use_span(),
463463
_ => self.span,
464464
}
@@ -661,7 +661,9 @@ impl Token {
661661
pub fn uninterpolate(&self) -> Cow<'_, Token> {
662662
match self.kind {
663663
NtIdent(ident, is_raw) => Cow::Owned(Token::new(Ident(ident.name, is_raw), ident.span)),
664-
NtLifetime(ident) => Cow::Owned(Token::new(Lifetime(ident.name), ident.span)),
664+
NtLifetime(ident, is_raw) => {
665+
Cow::Owned(Token::new(Lifetime(ident.name, is_raw), ident.span))
666+
}
665667
_ => Cow::Borrowed(self),
666668
}
667669
}
@@ -679,11 +681,11 @@ impl Token {
679681

680682
/// Returns a lifetime identifier if this token is a lifetime.
681683
#[inline]
682-
pub fn lifetime(&self) -> Option<Ident> {
684+
pub fn lifetime(&self) -> Option<(Ident, IdentIsRaw)> {
683685
// We avoid using `Token::uninterpolate` here because it's slow.
684686
match self.kind {
685-
Lifetime(name) => Some(Ident::new(name, self.span)),
686-
NtLifetime(ident) => Some(ident),
687+
Lifetime(name, is_raw) => Some((Ident::new(name, self.span), is_raw)),
688+
NtLifetime(ident, is_raw) => Some((ident, is_raw)),
687689
_ => None,
688690
}
689691
}
@@ -865,7 +867,7 @@ impl Token {
865867
_ => return None,
866868
},
867869
SingleQuote => match joint.kind {
868-
Ident(name, IdentIsRaw::No) => Lifetime(Symbol::intern(&format!("'{name}"))),
870+
Ident(name, is_raw) => Lifetime(Symbol::intern(&format!("'{name}")), is_raw),
869871
_ => return None,
870872
},
871873

compiler/rustc_ast/src/tokenstream.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -482,11 +482,11 @@ impl TokenStream {
482482
token::NtIdent(ident, is_raw) => {
483483
TokenTree::Token(Token::new(token::Ident(ident.name, is_raw), ident.span), spacing)
484484
}
485-
token::NtLifetime(ident) => TokenTree::Delimited(
485+
token::NtLifetime(ident, is_raw) => TokenTree::Delimited(
486486
DelimSpan::from_single(token.span),
487487
DelimSpacing::new(Spacing::JointHidden, spacing),
488488
Delimiter::Invisible,
489-
TokenStream::token_alone(token::Lifetime(ident.name), ident.span),
489+
TokenStream::token_alone(token::Lifetime(ident.name, is_raw), ident.span),
490490
),
491491
token::Interpolated(ref nt) => TokenTree::Delimited(
492492
DelimSpan::from_single(token.span),

compiler/rustc_ast_pretty/src/pprust/state.rs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@ use std::borrow::Cow;
1111
use ast::TraitBoundModifiers;
1212
use rustc_ast::attr::AttrIdGenerator;
1313
use rustc_ast::ptr::P;
14-
use rustc_ast::token::{self, BinOpToken, CommentKind, Delimiter, Nonterminal, Token, TokenKind};
14+
use rustc_ast::token::{
15+
self, BinOpToken, CommentKind, Delimiter, IdentIsRaw, Nonterminal, Token, TokenKind,
16+
};
1517
use rustc_ast::tokenstream::{Spacing, TokenStream, TokenTree};
1618
use rustc_ast::util::classify;
1719
use rustc_ast::util::comments::{Comment, CommentStyle};
@@ -947,8 +949,13 @@ pub trait PrintState<'a>: std::ops::Deref<Target = pp::Printer> + std::ops::Dere
947949
token::NtIdent(ident, is_raw) => {
948950
IdentPrinter::for_ast_ident(ident, is_raw.into()).to_string().into()
949951
}
950-
token::Lifetime(name) => name.to_string().into(),
951-
token::NtLifetime(ident) => ident.name.to_string().into(),
952+
953+
token::Lifetime(name, IdentIsRaw::No)
954+
| token::NtLifetime(Ident { name, .. }, IdentIsRaw::No) => name.to_string().into(),
955+
token::Lifetime(name, IdentIsRaw::Yes)
956+
| token::NtLifetime(Ident { name, .. }, IdentIsRaw::Yes) => {
957+
format!("'r#{}", &name.as_str()[1..]).into()
958+
}
952959

953960
/* Other */
954961
token::DocComment(comment_kind, attr_style, data) => {

compiler/rustc_expand/src/mbe/macro_parser.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -398,8 +398,10 @@ pub(crate) enum NamedMatch {
398398
fn token_name_eq(t1: &Token, t2: &Token) -> bool {
399399
if let (Some((ident1, is_raw1)), Some((ident2, is_raw2))) = (t1.ident(), t2.ident()) {
400400
ident1.name == ident2.name && is_raw1 == is_raw2
401-
} else if let (Some(ident1), Some(ident2)) = (t1.lifetime(), t2.lifetime()) {
402-
ident1.name == ident2.name
401+
} else if let (Some((ident1, is_raw1)), Some((ident2, is_raw2))) =
402+
(t1.lifetime(), t2.lifetime())
403+
{
404+
ident1.name == ident2.name && is_raw1 == is_raw2
403405
} else {
404406
t1.kind == t2.kind
405407
}

compiler/rustc_expand/src/mbe/transcribe.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -283,9 +283,9 @@ pub(super) fn transcribe<'a>(
283283
let kind = token::NtIdent(*ident, *is_raw);
284284
TokenTree::token_alone(kind, sp)
285285
}
286-
MatchedSingle(ParseNtResult::Lifetime(ident)) => {
286+
MatchedSingle(ParseNtResult::Lifetime(ident, is_raw)) => {
287287
marker.visit_span(&mut sp);
288-
let kind = token::NtLifetime(*ident);
288+
let kind = token::NtLifetime(*ident, *is_raw);
289289
TokenTree::token_alone(kind, sp)
290290
}
291291
MatchedSingle(ParseNtResult::Nt(nt)) => {

compiler/rustc_expand/src/proc_macro_server.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -229,15 +229,16 @@ impl FromInternal<(TokenStream, &mut Rustc<'_, '_>)> for Vec<TokenTree<TokenStre
229229
span: ident.span,
230230
})),
231231

232-
Lifetime(name) => {
232+
Lifetime(name, is_raw) => {
233233
let ident = symbol::Ident::new(name, span).without_first_quote();
234234
trees.extend([
235235
TokenTree::Punct(Punct { ch: b'\'', joint: true, span }),
236-
TokenTree::Ident(Ident { sym: ident.name, is_raw: false, span }),
236+
TokenTree::Ident(Ident { sym: ident.name, is_raw: is_raw.into(), span }),
237237
]);
238238
}
239-
NtLifetime(ident) => {
240-
let stream = TokenStream::token_alone(token::Lifetime(ident.name), ident.span);
239+
NtLifetime(ident, is_raw) => {
240+
let stream =
241+
TokenStream::token_alone(token::Lifetime(ident.name, is_raw), ident.span);
241242
trees.push(TokenTree::Group(Group {
242243
delimiter: pm::Delimiter::None,
243244
stream: Some(stream),

compiler/rustc_lexer/src/lib.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,9 @@ pub enum TokenKind {
9797
/// and not the separator.
9898
UnknownPrefixLifetime,
9999

100+
/// `'r#lt`, which in edition < 2021 is split into several tokens: `'r # lt`.
101+
RawLifetime,
102+
100103
/// Similar to the above, but *always* an error on every edition. This is used
101104
/// for emoji identifier recovery, as those are not meant to be ever accepted.
102105
InvalidPrefix,
@@ -683,9 +686,17 @@ impl Cursor<'_> {
683686
return Literal { kind, suffix_start };
684687
}
685688

689+
if self.first() == 'r' && self.second() == '#' && is_id_start(self.third()) {
690+
// Eat "r" and `#`, and identifier start characters.
691+
self.bump();
692+
self.bump();
693+
self.bump();
694+
self.eat_while(is_id_continue);
695+
return RawLifetime;
696+
}
697+
686698
// Either a lifetime or a character literal with
687699
// length greater than 1.
688-
689700
let starts_with_number = self.first().is_ascii_digit();
690701

691702
// Skip the literal contents.

compiler/rustc_lint/messages.ftl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -704,6 +704,10 @@ lint_range_endpoint_out_of_range = range endpoint is out of range for `{$ty}`
704704
lint_range_use_inclusive_range = use an inclusive range instead
705705
706706
707+
lint_raw_prefix = prefix `'r` is reserved
708+
.label = reserved prefix
709+
.suggestion = insert whitespace here to avoid this being parsed as a prefix in Rust 2021
710+
707711
lint_reason_must_be_string_literal = reason must be a string literal
708712
709713
lint_reason_must_come_last = reason in lint attribute must come last

compiler/rustc_lint/src/context/diagnostics.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,10 @@ pub(super) fn decorate_lint(sess: &Session, diagnostic: BuiltinLintDiag, diag: &
172172
}
173173
.decorate_lint(diag);
174174
}
175+
BuiltinLintDiag::RawPrefix(label_span) => {
176+
lints::RawPrefix { label: label_span, suggestion: label_span.shrink_to_hi() }
177+
.decorate_lint(diag);
178+
}
175179
BuiltinLintDiag::UnusedBuiltinAttribute { attr_name, macro_name, invoc_span } => {
176180
lints::UnusedBuiltinAttribute { invoc_span, attr_name, macro_name }.decorate_lint(diag);
177181
}

compiler/rustc_lint/src/lints.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2765,6 +2765,15 @@ pub(crate) struct ReservedPrefix {
27652765
pub prefix: String,
27662766
}
27672767

2768+
#[derive(LintDiagnostic)]
2769+
#[diag(lint_raw_prefix)]
2770+
pub(crate) struct RawPrefix {
2771+
#[label]
2772+
pub label: Span,
2773+
#[suggestion(code = " ", applicability = "machine-applicable")]
2774+
pub suggestion: Span,
2775+
}
2776+
27682777
#[derive(LintDiagnostic)]
27692778
#[diag(lint_unused_builtin_attribute)]
27702779
pub(crate) struct UnusedBuiltinAttribute {

compiler/rustc_lint_defs/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -609,6 +609,8 @@ pub enum BuiltinLintDiag {
609609
LegacyDeriveHelpers(Span),
610610
OrPatternsBackCompat(Span, String),
611611
ReservedPrefix(Span, String),
612+
/// `'r#` in edition < 2021.
613+
RawPrefix(Span),
612614
TrailingMacro(bool, Ident),
613615
BreakWithLabelAndLoop(Span),
614616
UnicodeTextFlow(Span, String),

compiler/rustc_parse/src/lexer/mod.rs

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ use rustc_session::lint::builtin::{
1313
};
1414
use rustc_session::lint::BuiltinLintDiag;
1515
use rustc_session::parse::ParseSess;
16-
use rustc_span::edition::Edition;
1716
use rustc_span::symbol::Symbol;
1817
use rustc_span::{BytePos, Pos, Span};
1918
use tracing::debug;
@@ -284,7 +283,41 @@ impl<'psess, 'src> StringReader<'psess, 'src> {
284283
.stash(span, StashKey::LifetimeIsChar);
285284
}
286285
let ident = Symbol::intern(lifetime_name);
287-
token::Lifetime(ident)
286+
token::Lifetime(ident, IdentIsRaw::No)
287+
}
288+
rustc_lexer::TokenKind::RawLifetime => {
289+
self.last_lifetime = Some(self.mk_sp(start, start + BytePos(1)));
290+
291+
let ident_start = start + BytePos(3);
292+
let prefix_span = self.mk_sp(start, ident_start);
293+
294+
if prefix_span.at_least_rust_2021() {
295+
let lifetime_name_without_tick = self.str_from(ident_start);
296+
// Put the `'` back onto the lifetime name.
297+
let mut lifetime_name = String::with_capacity(lifetime_name_without_tick.len() + 1);
298+
lifetime_name.push('\'');
299+
lifetime_name += lifetime_name_without_tick;
300+
let sym = Symbol::intern(&lifetime_name);
301+
302+
token::Lifetime(sym, IdentIsRaw::Yes)
303+
} else {
304+
// Otherwise, this is just `'r`. Warn about it though.
305+
self.psess.buffer_lint(
306+
RUST_2021_PREFIXES_INCOMPATIBLE_SYNTAX,
307+
prefix_span,
308+
ast::CRATE_NODE_ID,
309+
BuiltinLintDiag::RawPrefix(prefix_span),
310+
);
311+
312+
// Reset the state so we just lex the `'r`.
313+
let lt_start = start + BytePos(2);
314+
self.pos = lt_start;
315+
self.cursor = Cursor::new(&str_before[2 as usize..]);
316+
317+
let lifetime_name = self.str_from(start);
318+
let ident = Symbol::intern(lifetime_name);
319+
token::Lifetime(ident, IdentIsRaw::No)
320+
}
288321
}
289322
rustc_lexer::TokenKind::Semi => token::Semi,
290323
rustc_lexer::TokenKind::Comma => token::Comma,
@@ -712,7 +745,7 @@ impl<'psess, 'src> StringReader<'psess, 'src> {
712745

713746
let expn_data = prefix_span.ctxt().outer_expn_data();
714747

715-
if expn_data.edition >= Edition::Edition2021 {
748+
if expn_data.edition.at_least_rust_2021() {
716749
// In Rust 2021, this is a hard error.
717750
let sugg = if prefix == "rb" {
718751
Some(errors::UnknownPrefixSugg::UseBr(prefix_span))

compiler/rustc_parse/src/parser/expr.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2050,7 +2050,7 @@ impl<'a> Parser<'a> {
20502050
};
20512051
// On an error path, eagerly consider a lifetime to be an unclosed character lit, if that
20522052
// makes sense.
2053-
if let Some(ident) = self.token.lifetime()
2053+
if let Some((ident, IdentIsRaw::No)) = self.token.lifetime()
20542054
&& could_be_unclosed_char_literal(ident)
20552055
{
20562056
let lt = self.expect_lifetime();
@@ -2925,9 +2925,9 @@ impl<'a> Parser<'a> {
29252925
}
29262926

29272927
pub(crate) fn eat_label(&mut self) -> Option<Label> {
2928-
if let Some(ident) = self.token.lifetime() {
2928+
if let Some((ident, is_raw)) = self.token.lifetime() {
29292929
// Disallow `'fn`, but with a better error message than `expect_lifetime`.
2930-
if ident.without_first_quote().is_reserved() {
2930+
if matches!(is_raw, IdentIsRaw::No) && ident.without_first_quote().is_reserved() {
29312931
self.dcx().emit_err(errors::InvalidLabel { span: ident.span, name: ident.name });
29322932
}
29332933

compiler/rustc_parse/src/parser/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1666,7 +1666,7 @@ enum FlatToken {
16661666
pub enum ParseNtResult {
16671667
Tt(TokenTree),
16681668
Ident(Ident, IdentIsRaw),
1669-
Lifetime(Ident),
1669+
Lifetime(Ident, IdentIsRaw),
16701670

16711671
/// This case will eventually be removed, along with `Token::Interpolate`.
16721672
Nt(Lrc<Nonterminal>),

compiler/rustc_parse/src/parser/nonterminal.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ impl<'a> Parser<'a> {
8888
},
8989
NonterminalKind::Pat(pat_kind) => token.can_begin_pattern(pat_kind),
9090
NonterminalKind::Lifetime => match &token.kind {
91-
token::Lifetime(_) | token::NtLifetime(..) => true,
91+
token::Lifetime(..) | token::NtLifetime(..) => true,
9292
_ => false,
9393
},
9494
NonterminalKind::TT | NonterminalKind::Item | NonterminalKind::Stmt => {
@@ -171,9 +171,9 @@ impl<'a> Parser<'a> {
171171
NonterminalKind::Lifetime => {
172172
// We want to keep `'keyword` parsing, just like `keyword` is still
173173
// an ident for nonterminal purposes.
174-
return if let Some(ident) = self.token.lifetime() {
174+
return if let Some((ident, is_raw)) = self.token.lifetime() {
175175
self.bump();
176-
Ok(ParseNtResult::Lifetime(ident))
176+
Ok(ParseNtResult::Lifetime(ident, is_raw))
177177
} else {
178178
Err(self.dcx().create_err(UnexpectedNonterminal::Lifetime {
179179
span: self.token.span,

compiler/rustc_parse/src/parser/pat.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use rustc_ast::mut_visit::{walk_pat, MutVisitor};
22
use rustc_ast::ptr::P;
3-
use rustc_ast::token::{self, BinOpToken, Delimiter, Token};
3+
use rustc_ast::token::{self, BinOpToken, Delimiter, IdentIsRaw, Token};
44
use rustc_ast::{
55
self as ast, AttrVec, BindingMode, ByRef, Expr, ExprKind, MacCall, Mutability, Pat, PatField,
66
PatFieldsRest, PatKind, Path, QSelf, RangeEnd, RangeSyntax,
@@ -548,7 +548,7 @@ impl<'a> Parser<'a> {
548548
None => PatKind::Path(qself, path),
549549
}
550550
}
551-
} else if let Some(lt) = self.token.lifetime()
551+
} else if let Some((lt, IdentIsRaw::No)) = self.token.lifetime()
552552
// In pattern position, we're totally fine with using "next token isn't colon"
553553
// as a heuristic. We could probably just always try to recover if it's a lifetime,
554554
// because we never have `'a: label {}` in a pattern position anyways, but it does
@@ -689,7 +689,7 @@ impl<'a> Parser<'a> {
689689
/// Parse `&pat` / `&mut pat`.
690690
fn parse_pat_deref(&mut self, expected: Option<Expected>) -> PResult<'a, PatKind> {
691691
self.expect_and()?;
692-
if let Some(lifetime) = self.token.lifetime() {
692+
if let Some((lifetime, _)) = self.token.lifetime() {
693693
self.bump(); // `'a`
694694

695695
self.dcx().emit_err(UnexpectedLifetimeInPattern {

compiler/rustc_parse/src/parser/ty.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use rustc_ast::ptr::P;
2-
use rustc_ast::token::{self, BinOpToken, Delimiter, Token, TokenKind};
2+
use rustc_ast::token::{self, BinOpToken, Delimiter, IdentIsRaw, Token, TokenKind};
33
use rustc_ast::util::case::Case;
44
use rustc_ast::{
55
self as ast, BareFnTy, BoundAsyncness, BoundConstness, BoundPolarity, FnRetTy, GenericBound,
@@ -1285,8 +1285,9 @@ impl<'a> Parser<'a> {
12851285

12861286
/// Parses a single lifetime `'a` or panics.
12871287
pub(super) fn expect_lifetime(&mut self) -> Lifetime {
1288-
if let Some(ident) = self.token.lifetime() {
1289-
if ident.without_first_quote().is_reserved()
1288+
if let Some((ident, is_raw)) = self.token.lifetime() {
1289+
if matches!(is_raw, IdentIsRaw::No)
1290+
&& ident.without_first_quote().is_reserved()
12901291
&& ![kw::UnderscoreLifetime, kw::StaticLifetime].contains(&ident.name)
12911292
{
12921293
self.dcx().emit_err(errors::KeywordLifetime { span: ident.span });

0 commit comments

Comments
 (0)