print raw lifetime idents with r#

This commit is contained in:
Deadbeef 2025-08-04 09:41:55 +00:00
parent 8e3710ef31
commit 1e5b5ba1e7
7 changed files with 166 additions and 48 deletions

View File

@ -7,6 +7,7 @@ pub use NtPatKind::*;
pub use TokenKind::*;
use rustc_macros::{Decodable, Encodable, HashStable_Generic};
use rustc_span::edition::Edition;
use rustc_span::symbol::IdentPrintMode;
use rustc_span::{DUMMY_SP, ErrorGuaranteed, Span, kw, sym};
#[allow(clippy::useless_attribute)] // FIXME: following use of `hidden_glob_reexports` incorrectly triggers `useless_attribute` lint.
#[allow(hidden_glob_reexports)]
@ -344,15 +345,24 @@ pub enum IdentIsRaw {
Yes,
}
impl From<bool> for IdentIsRaw {
fn from(b: bool) -> Self {
if b { Self::Yes } else { Self::No }
impl IdentIsRaw {
pub fn to_print_mode_ident(self) -> IdentPrintMode {
match self {
IdentIsRaw::No => IdentPrintMode::Normal,
IdentIsRaw::Yes => IdentPrintMode::RawIdent,
}
}
pub fn to_print_mode_lifetime(self) -> IdentPrintMode {
match self {
IdentIsRaw::No => IdentPrintMode::Normal,
IdentIsRaw::Yes => IdentPrintMode::RawLifetime,
}
}
}
impl From<IdentIsRaw> for bool {
fn from(is_raw: IdentIsRaw) -> bool {
matches!(is_raw, IdentIsRaw::Yes)
impl From<bool> for IdentIsRaw {
fn from(b: bool) -> Self {
if b { Self::Yes } else { Self::No }
}
}

View File

@ -10,7 +10,7 @@ use std::borrow::Cow;
use std::sync::Arc;
use rustc_ast::attr::AttrIdGenerator;
use rustc_ast::token::{self, CommentKind, Delimiter, IdentIsRaw, Token, TokenKind};
use rustc_ast::token::{self, CommentKind, Delimiter, Token, TokenKind};
use rustc_ast::tokenstream::{Spacing, TokenStream, TokenTree};
use rustc_ast::util::classify;
use rustc_ast::util::comments::{Comment, CommentStyle};
@ -441,7 +441,7 @@ pub trait PrintState<'a>: std::ops::Deref<Target = pp::Printer> + std::ops::Dere
fn print_generic_args(&mut self, args: &ast::GenericArgs, colons_before_params: bool);
fn print_ident(&mut self, ident: Ident) {
self.word(IdentPrinter::for_ast_ident(ident, ident.is_raw_guess()).to_string());
self.word(IdentPrinter::for_ast_ident(ident, ident.guess_print_mode()).to_string());
self.ann_post(ident)
}
@ -1015,17 +1015,16 @@ pub trait PrintState<'a>: std::ops::Deref<Target = pp::Printer> + std::ops::Dere
/* Name components */
token::Ident(name, is_raw) => {
IdentPrinter::new(name, is_raw.into(), convert_dollar_crate).to_string().into()
IdentPrinter::new(name, is_raw.to_print_mode_ident(), convert_dollar_crate)
.to_string()
.into()
}
token::NtIdent(ident, is_raw) => {
IdentPrinter::for_ast_ident(ident, is_raw.into()).to_string().into()
IdentPrinter::for_ast_ident(ident, is_raw.to_print_mode_ident()).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()
token::Lifetime(name, is_raw) | token::NtLifetime(Ident { name, .. }, is_raw) => {
IdentPrinter::new(name, is_raw.to_print_mode_lifetime(), None).to_string().into()
}
/* Other */

View File

@ -250,12 +250,14 @@ impl FromInternal<(TokenStream, &mut Rustc<'_, '_>)> for Vec<TokenTree<TokenStre
Question => op("?"),
SingleQuote => op("'"),
Ident(sym, is_raw) => {
trees.push(TokenTree::Ident(Ident { sym, is_raw: is_raw.into(), span }))
}
Ident(sym, is_raw) => trees.push(TokenTree::Ident(Ident {
sym,
is_raw: matches!(is_raw, IdentIsRaw::Yes),
span,
})),
NtIdent(ident, is_raw) => trees.push(TokenTree::Ident(Ident {
sym: ident.name,
is_raw: is_raw.into(),
is_raw: matches!(is_raw, IdentIsRaw::Yes),
span: ident.span,
})),
@ -263,7 +265,11 @@ impl FromInternal<(TokenStream, &mut Rustc<'_, '_>)> for Vec<TokenTree<TokenStre
let ident = rustc_span::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: is_raw.into(), span }),
TokenTree::Ident(Ident {
sym: ident.name,
is_raw: matches!(is_raw, IdentIsRaw::Yes),
span,
}),
]);
}
NtLifetime(ident, is_raw) => {

View File

@ -3094,7 +3094,7 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> {
} else {
self.suggest_introducing_lifetime(
&mut err,
Some(lifetime_ref.ident.name.as_str()),
Some(lifetime_ref.ident),
|err, _, span, message, suggestion, span_suggs| {
err.multipart_suggestion_verbose(
message,
@ -3112,7 +3112,7 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> {
fn suggest_introducing_lifetime(
&self,
err: &mut Diag<'_>,
name: Option<&str>,
name: Option<Ident>,
suggest: impl Fn(
&mut Diag<'_>,
bool,
@ -3159,7 +3159,7 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> {
let mut rm_inner_binders: FxIndexSet<Span> = Default::default();
let (span, sugg) = if span.is_empty() {
let mut binder_idents: FxIndexSet<Ident> = Default::default();
binder_idents.insert(Ident::from_str(name.unwrap_or("'a")));
binder_idents.insert(name.unwrap_or(Ident::from_str("'a")));
// We need to special case binders in the following situation:
// Change `T: for<'a> Trait<T> + 'b` to `for<'a, 'b> T: Trait<T> + 'b`
@ -3189,16 +3189,11 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> {
}
}
let binders_sugg = binder_idents.into_iter().enumerate().fold(
"".to_string(),
|mut binders, (i, x)| {
if i != 0 {
binders += ", ";
}
binders += x.as_str();
binders
},
);
let binders_sugg: String = binder_idents
.into_iter()
.map(|ident| ident.to_string())
.intersperse(", ".to_owned())
.collect();
let sugg = format!(
"{}<{}>{}",
if higher_ranked { "for" } else { "" },
@ -3214,7 +3209,8 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> {
.source_map()
.span_through_char(span, '<')
.shrink_to_hi();
let sugg = format!("{}, ", name.unwrap_or("'a"));
let sugg =
format!("{}, ", name.map(|i| i.to_string()).as_deref().unwrap_or("'a"));
(span, sugg)
};
@ -3222,7 +3218,7 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> {
let message = Cow::from(format!(
"consider making the {} lifetime-generic with a new `{}` lifetime",
kind.descr(),
name.unwrap_or("'a"),
name.map(|i| i.to_string()).as_deref().unwrap_or("'a"),
));
should_continue = suggest(
err,

View File

@ -2533,10 +2533,16 @@ impl fmt::Debug for Ident {
/// except that AST identifiers don't keep the rawness flag, so we have to guess it.
impl fmt::Display for Ident {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&IdentPrinter::new(self.name, self.is_raw_guess(), None), f)
fmt::Display::fmt(&IdentPrinter::new(self.name, self.guess_print_mode(), None), f)
}
}
pub enum IdentPrintMode {
Normal,
RawIdent,
RawLifetime,
}
/// The most general type to print identifiers.
///
/// AST pretty-printer is used as a fallback for turning AST structures into token streams for
@ -2552,7 +2558,7 @@ impl fmt::Display for Ident {
/// done for a token stream or a single token.
pub struct IdentPrinter {
symbol: Symbol,
is_raw: bool,
mode: IdentPrintMode,
/// Span used for retrieving the crate name to which `$crate` refers to,
/// if this field is `None` then the `$crate` conversion doesn't happen.
convert_dollar_crate: Option<Span>,
@ -2560,32 +2566,51 @@ pub struct IdentPrinter {
impl IdentPrinter {
/// The most general `IdentPrinter` constructor. Do not use this.
pub fn new(symbol: Symbol, is_raw: bool, convert_dollar_crate: Option<Span>) -> IdentPrinter {
IdentPrinter { symbol, is_raw, convert_dollar_crate }
pub fn new(
symbol: Symbol,
mode: IdentPrintMode,
convert_dollar_crate: Option<Span>,
) -> IdentPrinter {
IdentPrinter { symbol, mode, convert_dollar_crate }
}
/// This implementation is supposed to be used when printing identifiers
/// as a part of pretty-printing for larger AST pieces.
/// Do not use this either.
pub fn for_ast_ident(ident: Ident, is_raw: bool) -> IdentPrinter {
IdentPrinter::new(ident.name, is_raw, Some(ident.span))
pub fn for_ast_ident(ident: Ident, mode: IdentPrintMode) -> IdentPrinter {
IdentPrinter::new(ident.name, mode, Some(ident.span))
}
}
impl fmt::Display for IdentPrinter {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.is_raw {
f.write_str("r#")?;
} else if self.symbol == kw::DollarCrate {
if let Some(span) = self.convert_dollar_crate {
let s = match self.mode {
IdentPrintMode::Normal
if self.symbol == kw::DollarCrate
&& let Some(span) = self.convert_dollar_crate =>
{
let converted = span.ctxt().dollar_crate_name();
if !converted.is_path_segment_keyword() {
f.write_str("::")?;
}
return fmt::Display::fmt(&converted, f);
converted
}
}
fmt::Display::fmt(&self.symbol, f)
IdentPrintMode::Normal => self.symbol,
IdentPrintMode::RawIdent => {
f.write_str("r#")?;
self.symbol
}
IdentPrintMode::RawLifetime => {
f.write_str("'r#")?;
let s = self
.symbol
.as_str()
.strip_prefix("'")
.expect("only lifetime idents should be passed with RawLifetime mode");
Symbol::intern(s)
}
};
s.fmt(f)
}
}
@ -3020,6 +3045,25 @@ impl Ident {
self.name.can_be_raw() && self.is_reserved()
}
pub fn is_raw_lifetime_guess(self) -> bool {
// this should be kept consistent with `Parser::expect_lifetime` found under
// compiler/rustc_parse/src/parser/ty.rs
let name_without_apostrophe = self.without_first_quote();
name_without_apostrophe.name != self.name
&& ![kw::UnderscoreLifetime, kw::StaticLifetime].contains(&self.name)
&& name_without_apostrophe.is_raw_guess()
}
pub fn guess_print_mode(self) -> IdentPrintMode {
if self.is_raw_lifetime_guess() {
IdentPrintMode::RawLifetime
} else if self.is_raw_guess() {
IdentPrintMode::RawIdent
} else {
IdentPrintMode::Normal
}
}
/// Whether this would be the identifier for a tuple field like `self.0`, as
/// opposed to a named field like `self.thing`.
pub fn is_numeric(self) -> bool {

View File

@ -0,0 +1,21 @@
// Check that we properly suggest `r#fn` if we use it undeclared.
// https://github.com/rust-lang/rust/issues/143150
//
//@ edition: 2021
fn a(_: dyn Trait + 'r#fn) {
//~^ ERROR use of undeclared lifetime name `'r#fn` [E0261]
}
trait Trait {}
struct Test {
a: &'r#fn str,
//~^ ERROR use of undeclared lifetime name `'r#fn` [E0261]
}
trait Trait1<T>
where T: for<'a> Trait1<T> + 'r#fn { }
//~^ ERROR use of undeclared lifetime name `'r#fn` [E0261]
fn main() {}

View File

@ -0,0 +1,42 @@
error[E0261]: use of undeclared lifetime name `'r#fn`
--> $DIR/use-of-undeclared-raw-lifetimes.rs:6:21
|
LL | fn a(_: dyn Trait + 'r#fn) {
| ^^^^^ undeclared lifetime
|
help: consider introducing lifetime `'r#fn` here
|
LL | fn a<'r#fn>(_: dyn Trait + 'r#fn) {
| +++++++
error[E0261]: use of undeclared lifetime name `'r#fn`
--> $DIR/use-of-undeclared-raw-lifetimes.rs:13:9
|
LL | a: &'r#fn str,
| ^^^^^ undeclared lifetime
|
help: consider introducing lifetime `'r#fn` here
|
LL | struct Test<'r#fn> {
| +++++++
error[E0261]: use of undeclared lifetime name `'r#fn`
--> $DIR/use-of-undeclared-raw-lifetimes.rs:18:32
|
LL | where T: for<'a> Trait1<T> + 'r#fn { }
| ^^^^^ undeclared lifetime
|
= note: for more information on higher-ranked polymorphism, visit https://doc.rust-lang.org/nomicon/hrtb.html
help: consider making the bound lifetime-generic with a new `'r#fn` lifetime
|
LL - where T: for<'a> Trait1<T> + 'r#fn { }
LL + where for<'r#fn, 'a> T: Trait1<T> + 'r#fn { }
|
help: consider introducing lifetime `'r#fn` here
|
LL | trait Trait1<'r#fn, T>
| ++++++
error: aborting due to 3 previous errors
For more information about this error, try `rustc --explain E0261`.