Skip to content

Commit d9cbfeb

Browse files
committed
Auto merge of #126452 - compiler-errors:raw-lifetimes, r=<try>
Implement raw lifetimes and labels (`'r#ident`) This PR implements a syntax for raw lifetimes and labels as requested in #126335. This PR also moves keyword lifetime validation (i.e. checking that we don't have something like `'async`) from ast validation to the parser, similar to how we handle raw identifiers, since we don't distinguish raw identifiers after parsing. This PR additionally extends the `keyword_idents_2024` lint to also check identifiers. cc `@traviscross` r? parser
2 parents 0ef0dd2 + 0129478 commit d9cbfeb

37 files changed

+274
-107
lines changed

compiler/rustc_ast/src/mut_visit.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -776,7 +776,7 @@ fn visit_lazy_tts<T: MutVisitor>(lazy_tts: &mut Option<LazyAttrTokenStream>, vis
776776
pub fn visit_token<T: MutVisitor>(t: &mut Token, vis: &mut T) {
777777
let Token { kind, span } = t;
778778
match kind {
779-
token::Ident(name, _) | token::Lifetime(name) => {
779+
token::Ident(name, _) | token::Lifetime(name, _) => {
780780
let mut ident = Ident::new(*name, *span);
781781
vis.visit_ident(&mut ident);
782782
*name = ident.name;
@@ -786,7 +786,7 @@ pub fn visit_token<T: MutVisitor>(t: &mut Token, vis: &mut T) {
786786
token::NtIdent(ident, _is_raw) => {
787787
vis.visit_ident(ident);
788788
}
789-
token::NtLifetime(ident) => {
789+
token::NtLifetime(ident, _is_raw) => {
790790
vis.visit_ident(ident);
791791
}
792792
token::Interpolated(nt) => {

compiler/rustc_ast/src/token.rs

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

337337
/// An embedded AST node, as produced by a macro. This only exists for
338338
/// historical reasons. We'd like to get rid of it, for multiple reasons.
@@ -455,7 +455,7 @@ impl Token {
455455
/// if they keep spans or perform edition checks.
456456
pub fn uninterpolated_span(&self) -> Span {
457457
match self.kind {
458-
NtIdent(ident, _) | NtLifetime(ident) => ident.span,
458+
NtIdent(ident, _) | NtLifetime(ident, _) => ident.span,
459459
Interpolated(ref nt) => nt.use_span(),
460460
_ => self.span,
461461
}
@@ -627,7 +627,9 @@ impl Token {
627627
pub fn uninterpolate(&self) -> Cow<'_, Token> {
628628
match self.kind {
629629
NtIdent(ident, is_raw) => Cow::Owned(Token::new(Ident(ident.name, is_raw), ident.span)),
630-
NtLifetime(ident) => Cow::Owned(Token::new(Lifetime(ident.name), ident.span)),
630+
NtLifetime(ident, is_raw) => {
631+
Cow::Owned(Token::new(Lifetime(ident.name, is_raw), ident.span))
632+
}
631633
_ => Cow::Borrowed(self),
632634
}
633635
}
@@ -645,11 +647,11 @@ impl Token {
645647

646648
/// Returns a lifetime identifier if this token is a lifetime.
647649
#[inline]
648-
pub fn lifetime(&self) -> Option<Ident> {
650+
pub fn lifetime(&self) -> Option<(Ident, IdentIsRaw)> {
649651
// We avoid using `Token::uninterpolate` here because it's slow.
650652
match self.kind {
651-
Lifetime(name) => Some(Ident::new(name, self.span)),
652-
NtLifetime(ident) => Some(ident),
653+
Lifetime(name, is_raw) => Some((Ident::new(name, self.span), is_raw)),
654+
NtLifetime(ident, is_raw) => Some((ident, is_raw)),
653655
_ => None,
654656
}
655657
}
@@ -660,6 +662,10 @@ impl Token {
660662
}
661663

662664
/// Returns `true` if the token is a lifetime.
665+
///
666+
/// This does not check that the lifetime is a *valid* lifetime -- for that,
667+
/// you need to go through `expect_lifetime`, since we need to check that the
668+
/// lifetime name is not reserved.
663669
pub fn is_lifetime(&self) -> bool {
664670
self.lifetime().is_some()
665671
}
@@ -832,7 +838,7 @@ impl Token {
832838
_ => return None,
833839
},
834840
SingleQuote => match joint.kind {
835-
Ident(name, IdentIsRaw::No) => Lifetime(Symbol::intern(&format!("'{name}"))),
841+
Ident(name, is_raw) => Lifetime(Symbol::intern(&format!("'{name}")), is_raw),
836842
_ => return None,
837843
},
838844

compiler/rustc_ast/src/tokenstream.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -487,11 +487,11 @@ impl TokenStream {
487487
token::NtIdent(ident, is_raw) => {
488488
TokenTree::Token(Token::new(token::Ident(ident.name, is_raw), ident.span), spacing)
489489
}
490-
token::NtLifetime(ident) => TokenTree::Delimited(
490+
token::NtLifetime(ident, is_raw) => TokenTree::Delimited(
491491
DelimSpan::from_single(token.span),
492492
DelimSpacing::new(Spacing::JointHidden, spacing),
493493
Delimiter::Invisible,
494-
TokenStream::token_alone(token::Lifetime(ident.name), ident.span),
494+
TokenStream::token_alone(token::Lifetime(ident.name, is_raw), ident.span),
495495
),
496496
token::Interpolated(ref nt) => TokenTree::Delimited(
497497
DelimSpan::from_single(token.span),

compiler/rustc_ast_passes/messages.ftl

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -156,9 +156,6 @@ ast_passes_inherent_cannot_be = inherent impls cannot be {$annotation}
156156
.type = inherent impl for this type
157157
.only_trait = only trait implementations may be annotated with {$annotation}
158158
159-
ast_passes_invalid_label =
160-
invalid label name `{$name}`
161-
162159
ast_passes_invalid_unnamed_field =
163160
unnamed fields are not allowed outside of structs or unions
164161
.label = unnamed field declared here
@@ -170,9 +167,6 @@ ast_passes_invalid_unnamed_field_ty =
170167
ast_passes_item_underscore = `{$kind}` items in this context need a name
171168
.label = `_` is not a valid name for this `{$kind}` item
172169
173-
ast_passes_keyword_lifetime =
174-
lifetimes cannot use keyword names
175-
176170
ast_passes_match_arm_with_no_body =
177171
`match` arm with no body
178172
.suggestion = add a body after the pattern

compiler/rustc_ast_passes/src/ast_validation.rs

Lines changed: 0 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -257,19 +257,6 @@ impl<'a> AstValidator<'a> {
257257
self.session.dcx()
258258
}
259259

260-
fn check_lifetime(&self, ident: Ident) {
261-
let valid_names = [kw::UnderscoreLifetime, kw::StaticLifetime, kw::Empty];
262-
if !valid_names.contains(&ident.name) && ident.without_first_quote().is_reserved() {
263-
self.dcx().emit_err(errors::KeywordLifetime { span: ident.span });
264-
}
265-
}
266-
267-
fn check_label(&self, ident: Ident) {
268-
if ident.without_first_quote().is_reserved() {
269-
self.dcx().emit_err(errors::InvalidLabel { span: ident.span, name: ident.name });
270-
}
271-
}
272-
273260
fn visibility_not_permitted(&self, vis: &Visibility, note: errors::VisibilityNotPermittedNote) {
274261
if let VisibilityKind::Inherited = vis.kind {
275262
return;
@@ -879,16 +866,6 @@ impl<'a> Visitor<'a> for AstValidator<'a> {
879866
self.walk_ty(ty)
880867
}
881868

882-
fn visit_label(&mut self, label: &'a Label) {
883-
self.check_label(label.ident);
884-
visit::walk_label(self, label);
885-
}
886-
887-
fn visit_lifetime(&mut self, lifetime: &'a Lifetime, _: visit::LifetimeCtxt) {
888-
self.check_lifetime(lifetime.ident);
889-
visit::walk_lifetime(self, lifetime);
890-
}
891-
892869
fn visit_field_def(&mut self, field: &'a FieldDef) {
893870
self.deny_unnamed_field(field);
894871
visit::walk_field_def(self, field)
@@ -1315,9 +1292,6 @@ impl<'a> Visitor<'a> for AstValidator<'a> {
13151292
}
13161293

13171294
fn visit_generic_param(&mut self, param: &'a GenericParam) {
1318-
if let GenericParamKind::Lifetime { .. } = param.kind {
1319-
self.check_lifetime(param.ident);
1320-
}
13211295
visit::walk_generic_param(self, param);
13221296
}
13231297

compiler/rustc_ast_passes/src/errors.rs

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,21 +9,6 @@ use rustc_span::{symbol::Ident, Span, Symbol};
99

1010
use crate::fluent_generated as fluent;
1111

12-
#[derive(Diagnostic)]
13-
#[diag(ast_passes_keyword_lifetime)]
14-
pub struct KeywordLifetime {
15-
#[primary_span]
16-
pub span: Span,
17-
}
18-
19-
#[derive(Diagnostic)]
20-
#[diag(ast_passes_invalid_label)]
21-
pub struct InvalidLabel {
22-
#[primary_span]
23-
pub span: Span,
24-
pub name: Symbol,
25-
}
26-
2712
#[derive(Diagnostic)]
2813
#[diag(ast_passes_visibility_not_permitted, code = E0449)]
2914
pub struct VisibilityNotPermitted {

compiler/rustc_ast_pretty/src/pprust/state.rs

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

952959
/* Other */
953960
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
@@ -278,9 +278,9 @@ pub(super) fn transcribe<'a>(
278278
let kind = token::NtIdent(*ident, *is_raw);
279279
TokenTree::token_alone(kind, sp)
280280
}
281-
MatchedSingle(ParseNtResult::Lifetime(ident)) => {
281+
MatchedSingle(ParseNtResult::Lifetime(ident, is_raw)) => {
282282
marker.visit_span(&mut sp);
283-
let kind = token::NtLifetime(*ident);
283+
let kind = token::NtLifetime(*ident, *is_raw);
284284
TokenTree::token_alone(kind, sp)
285285
}
286286
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
@@ -227,15 +227,16 @@ impl FromInternal<(TokenStream, &mut Rustc<'_, '_>)> for Vec<TokenTree<TokenStre
227227
span: ident.span,
228228
})),
229229

230-
Lifetime(name) => {
230+
Lifetime(name, is_raw) => {
231231
let ident = symbol::Ident::new(name, span).without_first_quote();
232232
trees.extend([
233233
TokenTree::Punct(Punct { ch: b'\'', joint: true, span }),
234-
TokenTree::Ident(Ident { sym: ident.name, is_raw: false, span }),
234+
TokenTree::Ident(Ident { sym: ident.name, is_raw: is_raw.into(), span }),
235235
]);
236236
}
237-
NtLifetime(ident) => {
238-
let stream = TokenStream::token_alone(token::Lifetime(ident.name), ident.span);
237+
NtLifetime(ident, is_raw) => {
238+
let stream =
239+
TokenStream::token_alone(token::Lifetime(ident.name, is_raw), ident.span);
239240
trees.push(TokenTree::Group(Group {
240241
delimiter: pm::Delimiter::None,
241242
stream: Some(stream),

compiler/rustc_lexer/src/lib.rs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,9 @@ pub enum TokenKind {
104104
/// "'a"
105105
Lifetime { starts_with_number: bool },
106106

107+
/// `'r#async`
108+
RawLifetime,
109+
107110
// One-char tokens:
108111
/// ";"
109112
Semi,
@@ -676,9 +679,18 @@ impl Cursor<'_> {
676679
return Literal { kind, suffix_start };
677680
}
678681

682+
if self.first() == 'r' && self.second() == '#' && is_id_start(self.third()) {
683+
// Eat "r" character.
684+
self.bump();
685+
// Eat "#" symbol.
686+
self.bump();
687+
// Eat the identifier part of RawIdent.
688+
self.eat_identifier();
689+
return RawLifetime;
690+
}
691+
679692
// Either a lifetime or a character literal with
680693
// length greater than 1.
681-
682694
let starts_with_number = self.first().is_ascii_digit();
683695

684696
// Skip the literal contents.

compiler/rustc_lint/src/builtin.rs

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1888,8 +1888,16 @@ impl KeywordIdents {
18881888
TokenTree::Token(token, _) => {
18891889
if let Some((ident, token::IdentIsRaw::No)) = token.ident() {
18901890
if !prev_dollar {
1891-
self.check_ident_token(cx, UnderMacro(true), ident);
1891+
self.check_ident_token(cx, UnderMacro(true), ident, "");
18921892
}
1893+
}
1894+
if let Some((ident, token::IdentIsRaw::No)) = token.lifetime() {
1895+
self.check_ident_token(
1896+
cx,
1897+
UnderMacro(true),
1898+
ident.without_first_quote(),
1899+
"'",
1900+
);
18931901
} else if token.kind == TokenKind::Dollar {
18941902
prev_dollar = true;
18951903
continue;
@@ -1906,6 +1914,7 @@ impl KeywordIdents {
19061914
cx: &EarlyContext<'_>,
19071915
UnderMacro(under_macro): UnderMacro,
19081916
ident: Ident,
1917+
prefix: &'static str,
19091918
) {
19101919
let (lint, edition) = match ident.name {
19111920
kw::Async | kw::Await | kw::Try => (KEYWORD_IDENTS_2018, Edition::Edition2018),
@@ -1939,7 +1948,7 @@ impl KeywordIdents {
19391948
cx.emit_span_lint(
19401949
lint,
19411950
ident.span,
1942-
BuiltinKeywordIdents { kw: ident, next: edition, suggestion: ident.span },
1951+
BuiltinKeywordIdents { kw: ident, next: edition, suggestion: ident.span, prefix },
19431952
);
19441953
}
19451954
}
@@ -1952,7 +1961,11 @@ impl EarlyLintPass for KeywordIdents {
19521961
self.check_tokens(cx, &mac.args.tokens);
19531962
}
19541963
fn check_ident(&mut self, cx: &EarlyContext<'_>, ident: Ident) {
1955-
self.check_ident_token(cx, UnderMacro(false), ident);
1964+
if ident.name.as_str().starts_with('\'') {
1965+
self.check_ident_token(cx, UnderMacro(false), ident.without_first_quote(), "'");
1966+
} else {
1967+
self.check_ident_token(cx, UnderMacro(false), ident, "");
1968+
}
19561969
}
19571970
}
19581971

compiler/rustc_lint/src/lints.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -368,8 +368,9 @@ pub enum BuiltinEllipsisInclusiveRangePatternsLint {
368368
pub struct BuiltinKeywordIdents {
369369
pub kw: Ident,
370370
pub next: Edition,
371-
#[suggestion(code = "r#{kw}", applicability = "machine-applicable")]
371+
#[suggestion(code = "{prefix}r#{kw}", applicability = "machine-applicable")]
372372
pub suggestion: Span,
373+
pub prefix: &'static str,
373374
}
374375

375376
#[derive(LintDiagnostic)]

compiler/rustc_parse/messages.ftl

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -390,6 +390,9 @@ parse_invalid_dyn_keyword = invalid `dyn` keyword
390390
parse_invalid_expression_in_let_else = a `{$operator}` expression cannot be directly assigned in `let...else`
391391
parse_invalid_identifier_with_leading_number = identifiers cannot start with a number
392392
393+
parse_invalid_label =
394+
invalid label name `{$name}`
395+
393396
parse_invalid_literal_suffix_on_tuple_index = suffixes on a tuple index are invalid
394397
.label = invalid suffix `{$suffix}`
395398
.tuple_exception_line_1 = `{$suffix}` is *temporarily* accepted on tuple index fields as it was incorrectly accepted on stable for a few releases
@@ -416,6 +419,9 @@ parse_invalid_unicode_escape = invalid unicode character escape
416419
parse_invalid_variable_declaration =
417420
invalid variable declaration
418421
422+
parse_keyword_lifetime =
423+
lifetimes cannot use keyword names
424+
419425
parse_kw_bad_case = keyword `{$kw}` is written in the wrong case
420426
.suggestion = write it in the correct case
421427

compiler/rustc_parse/src/errors.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1907,6 +1907,21 @@ pub struct CannotBeRawIdent {
19071907
pub ident: Symbol,
19081908
}
19091909

1910+
#[derive(Diagnostic)]
1911+
#[diag(parse_keyword_lifetime)]
1912+
pub struct KeywordLifetime {
1913+
#[primary_span]
1914+
pub span: Span,
1915+
}
1916+
1917+
#[derive(Diagnostic)]
1918+
#[diag(parse_invalid_label)]
1919+
pub struct InvalidLabel {
1920+
#[primary_span]
1921+
pub span: Span,
1922+
pub name: Symbol,
1923+
}
1924+
19101925
#[derive(Diagnostic)]
19111926
#[diag(parse_cr_doc_comment)]
19121927
pub struct CrDocComment {

0 commit comments

Comments
 (0)