Skip to content

Implement raw lifetimes and labels ('r#ident) #126452

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 6 commits into from
Sep 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions compiler/rustc_ast/src/mut_visit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
let Token { kind, span } = t;
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);
vis.visit_ident(&mut ident);
*name = ident.name;
Expand All @@ -762,7 +762,7 @@ pub fn visit_token<T: MutVisitor>(vis: &mut T, t: &mut Token) {
token::NtIdent(ident, _is_raw) => {
vis.visit_ident(ident);
}
token::NtLifetime(ident) => {
token::NtLifetime(ident, _is_raw) => {
vis.visit_ident(ident);
}
token::Interpolated(nt) => {
Expand Down
18 changes: 10 additions & 8 deletions compiler/rustc_ast/src/token.rs
Original file line number Diff line number Diff line change
Expand Up @@ -331,11 +331,11 @@ pub enum TokenKind {
/// Do not forget about `NtLifetime` when you want to match on lifetime identifiers.
/// It's recommended to use `Token::(lifetime,uninterpolate,uninterpolated_span)` to
/// 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
/// declarative macro. The span in the surrounding `Token` is the span of
/// 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
/// historical reasons. We'd like to get rid of it, for multiple reasons.
Expand Down Expand Up @@ -458,7 +458,7 @@ impl Token {
/// if they keep spans or perform edition checks.
pub fn uninterpolated_span(&self) -> Span {
match self.kind {
NtIdent(ident, _) | NtLifetime(ident) => ident.span,
NtIdent(ident, _) | NtLifetime(ident, _) => ident.span,
Interpolated(ref nt) => nt.use_span(),
_ => self.span,
}
Expand Down Expand Up @@ -661,7 +661,9 @@ impl Token {
pub fn uninterpolate(&self) -> Cow<'_, Token> {
match self.kind {
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),
}
}
Expand All @@ -679,11 +681,11 @@ impl Token {

/// Returns a lifetime identifier if this token is a lifetime.
#[inline]
pub fn lifetime(&self) -> Option<Ident> {
pub fn lifetime(&self) -> Option<(Ident, IdentIsRaw)> {
// We avoid using `Token::uninterpolate` here because it's slow.
match self.kind {
Lifetime(name) => Some(Ident::new(name, self.span)),
NtLifetime(ident) => Some(ident),
Lifetime(name, is_raw) => Some((Ident::new(name, self.span), is_raw)),
NtLifetime(ident, is_raw) => Some((ident, is_raw)),
_ => None,
}
}
Expand Down Expand Up @@ -865,7 +867,7 @@ impl Token {
_ => return None,
},
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,
},

Expand Down
4 changes: 2 additions & 2 deletions compiler/rustc_ast/src/tokenstream.rs
Original file line number Diff line number Diff line change
Expand Up @@ -482,11 +482,11 @@ impl TokenStream {
token::NtIdent(ident, is_raw) => {
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),
DelimSpacing::new(Spacing::JointHidden, spacing),
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(
DelimSpan::from_single(token.span),
Expand Down
13 changes: 10 additions & 3 deletions compiler/rustc_ast_pretty/src/pprust/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ use std::borrow::Cow;
use ast::TraitBoundModifiers;
use rustc_ast::attr::AttrIdGenerator;
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::util::classify;
use rustc_ast::util::comments::{Comment, CommentStyle};
Expand Down Expand Up @@ -947,8 +949,13 @@ pub trait PrintState<'a>: std::ops::Deref<Target = pp::Printer> + std::ops::Dere
token::NtIdent(ident, is_raw) => {
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 */
token::DocComment(comment_kind, attr_style, data) => {
Expand Down
6 changes: 4 additions & 2 deletions compiler/rustc_expand/src/mbe/macro_parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -398,8 +398,10 @@ pub(crate) enum NamedMatch {
fn token_name_eq(t1: &Token, t2: &Token) -> bool {
if let (Some((ident1, is_raw1)), Some((ident2, is_raw2))) = (t1.ident(), t2.ident()) {
ident1.name == ident2.name && is_raw1 == is_raw2
} else if let (Some(ident1), Some(ident2)) = (t1.lifetime(), t2.lifetime()) {
ident1.name == ident2.name
} else if let (Some((ident1, is_raw1)), Some((ident2, is_raw2))) =
(t1.lifetime(), t2.lifetime())
{
ident1.name == ident2.name && is_raw1 == is_raw2
} else {
t1.kind == t2.kind
}
Expand Down
4 changes: 2 additions & 2 deletions compiler/rustc_expand/src/mbe/transcribe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -283,9 +283,9 @@ pub(super) fn transcribe<'a>(
let kind = token::NtIdent(*ident, *is_raw);
TokenTree::token_alone(kind, sp)
}
MatchedSingle(ParseNtResult::Lifetime(ident)) => {
MatchedSingle(ParseNtResult::Lifetime(ident, is_raw)) => {
marker.visit_span(&mut sp);
let kind = token::NtLifetime(*ident);
let kind = token::NtLifetime(*ident, *is_raw);
TokenTree::token_alone(kind, sp)
}
MatchedSingle(ParseNtResult::Nt(nt)) => {
Expand Down
9 changes: 5 additions & 4 deletions compiler/rustc_expand/src/proc_macro_server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -229,15 +229,16 @@ impl FromInternal<(TokenStream, &mut Rustc<'_, '_>)> for Vec<TokenTree<TokenStre
span: ident.span,
})),

Lifetime(name) => {
Lifetime(name, is_raw) => {
let ident = symbol::Ident::new(name, span).without_first_quote();
trees.extend([
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) => {
let stream = TokenStream::token_alone(token::Lifetime(ident.name), ident.span);
NtLifetime(ident, is_raw) => {
let stream =
TokenStream::token_alone(token::Lifetime(ident.name, is_raw), ident.span);
trees.push(TokenTree::Group(Group {
delimiter: pm::Delimiter::None,
stream: Some(stream),
Expand Down
39 changes: 29 additions & 10 deletions compiler/rustc_lexer/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,15 @@ pub enum TokenKind {
/// tokens.
UnknownPrefix,

/// An unknown prefix in a lifetime, like `'foo#`.
///
/// Note that like above, only the `'` and prefix are included in the token
/// and not the separator.
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
/// for emoji identifier recovery, as those are not meant to be ever accepted.
InvalidPrefix,
Expand Down Expand Up @@ -677,9 +686,17 @@ impl Cursor<'_> {
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
// length greater than 1.

let starts_with_number = self.first().is_ascii_digit();

// Skip the literal contents.
Expand All @@ -688,15 +705,17 @@ impl Cursor<'_> {
self.bump();
self.eat_while(is_id_continue);

// Check if after skipping literal contents we've met a closing
// single quote (which means that user attempted to create a
// string with single quotes).
if self.first() == '\'' {
self.bump();
let kind = Char { terminated: true };
Literal { kind, suffix_start: self.pos_within_token() }
} else {
Lifetime { starts_with_number }
match self.first() {
// Check if after skipping literal contents we've met a closing
// single quote (which means that user attempted to create a
// string with single quotes).
'\'' => {
self.bump();
let kind = Char { terminated: true };
Literal { kind, suffix_start: self.pos_within_token() }
}
'#' if !starts_with_number => UnknownPrefixLifetime,
_ => Lifetime { starts_with_number },
}
}

Expand Down
4 changes: 4 additions & 0 deletions compiler/rustc_lint/messages.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -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_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_come_last = reason in lint attribute must come last
Expand Down
20 changes: 16 additions & 4 deletions compiler/rustc_lint/src/builtin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1851,9 +1851,16 @@ impl KeywordIdents {
TokenTree::Token(token, _) => {
if let Some((ident, token::IdentIsRaw::No)) = token.ident() {
if !prev_dollar {
self.check_ident_token(cx, UnderMacro(true), ident);
self.check_ident_token(cx, UnderMacro(true), ident, "");
}
} else if *token == TokenKind::Dollar {
} else if let Some((ident, token::IdentIsRaw::No)) = token.lifetime() {
self.check_ident_token(
cx,
UnderMacro(true),
ident.without_first_quote(),
"'",
);
} else if token.kind == TokenKind::Dollar {
prev_dollar = true;
continue;
}
Expand All @@ -1869,6 +1876,7 @@ impl KeywordIdents {
cx: &EarlyContext<'_>,
UnderMacro(under_macro): UnderMacro,
ident: Ident,
prefix: &'static str,
) {
let (lint, edition) = match ident.name {
kw::Async | kw::Await | kw::Try => (KEYWORD_IDENTS_2018, Edition::Edition2018),
Expand Down Expand Up @@ -1902,7 +1910,7 @@ impl KeywordIdents {
cx.emit_span_lint(
lint,
ident.span,
BuiltinKeywordIdents { kw: ident, next: edition, suggestion: ident.span },
BuiltinKeywordIdents { kw: ident, next: edition, suggestion: ident.span, prefix },
);
}
}
Expand All @@ -1915,7 +1923,11 @@ impl EarlyLintPass for KeywordIdents {
self.check_tokens(cx, &mac.args.tokens);
}
fn check_ident(&mut self, cx: &EarlyContext<'_>, ident: Ident) {
self.check_ident_token(cx, UnderMacro(false), ident);
if ident.name.as_str().starts_with('\'') {
self.check_ident_token(cx, UnderMacro(false), ident.without_first_quote(), "'");
} else {
self.check_ident_token(cx, UnderMacro(false), ident, "");
}
}
}

Expand Down
4 changes: 4 additions & 0 deletions compiler/rustc_lint/src/context/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,10 @@ pub(super) fn decorate_lint(sess: &Session, diagnostic: BuiltinLintDiag, 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 } => {
lints::UnusedBuiltinAttribute { invoc_span, attr_name, macro_name }.decorate_lint(diag);
}
Expand Down
12 changes: 11 additions & 1 deletion compiler/rustc_lint/src/lints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -363,8 +363,9 @@ pub(crate) enum BuiltinEllipsisInclusiveRangePatternsLint {
pub(crate) struct BuiltinKeywordIdents {
pub kw: Ident,
pub next: Edition,
#[suggestion(code = "r#{kw}", applicability = "machine-applicable")]
#[suggestion(code = "{prefix}r#{kw}", applicability = "machine-applicable")]
pub suggestion: Span,
pub prefix: &'static str,
}

#[derive(LintDiagnostic)]
Expand Down Expand Up @@ -2772,6 +2773,15 @@ pub(crate) struct ReservedPrefix {
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)]
#[diag(lint_unused_builtin_attribute)]
pub(crate) struct UnusedBuiltinAttribute {
Expand Down
2 changes: 2 additions & 0 deletions compiler/rustc_lint_defs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -609,6 +609,8 @@ pub enum BuiltinLintDiag {
LegacyDeriveHelpers(Span),
OrPatternsBackCompat(Span, String),
ReservedPrefix(Span, String),
/// `'r#` in edition < 2021.
RawPrefix(Span),
TrailingMacro(bool, Ident),
BreakWithLabelAndLoop(Span),
UnicodeTextFlow(Span, String),
Expand Down
Loading
Loading