Skip to content

Add lifetime-aware support for Display impl of Ident #143185

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

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion compiler/rustc_parse/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2176,7 +2176,7 @@ pub(crate) struct KeywordLifetime {
pub(crate) struct InvalidLabel {
#[primary_span]
pub span: Span,
pub name: Symbol,
pub name: String,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The label needs to be converted to String, otherwise the error message will call to_ident_string, which will print 'break as 'r#break in the following example.

fn main() {
    'break: loop { //~ ERROR invalid label name `'break`
    }
}

I haven't checked to see if there are any other label-related errors yet though, there may be similar issues, but it's generally a more borderline case.

Copy link
Member

@fmease fmease Jun 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Oh interesting. 'break is actually an invalid label but 'r#break is only a valid label in Rust >=2021) (ignore, not relevant)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, you should be able to use Symbol instead of String here (doesn't make much of a difference here tho) (we could actually suggest escaping via r# (if we have Rust >= 2021) here but that's for another PR)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found the implementation of Symbol as diagnostic arguments.

impl IntoDiagArg for Symbol {
fn into_diag_arg(self, path: &mut Option<std::path::PathBuf>) -> DiagArgValue {
self.to_ident_string().into_diag_arg(path)
}
}

It will be transformed to Ident, so if I use Symbol here, It prints invalid label name 'r#break. I may have to use String.

}

#[derive(Diagnostic)]
Expand Down
5 changes: 4 additions & 1 deletion compiler/rustc_parse/src/parser/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3077,7 +3077,10 @@ impl<'a> Parser<'a> {
if let Some((ident, is_raw)) = self.token.lifetime() {
// Disallow `'fn`, but with a better error message than `expect_lifetime`.
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.to_string(),
});
}

self.bump();
Expand Down
12 changes: 6 additions & 6 deletions compiler/rustc_resolve/src/late/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3120,7 +3120,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,
Expand All @@ -3138,7 +3138,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,
Expand Down Expand Up @@ -3185,7 +3185,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`
Expand Down Expand Up @@ -3221,7 +3221,7 @@ impl<'ast, 'ra, 'tcx> LateResolutionVisitor<'_, 'ast, 'ra, 'tcx> {
if i != 0 {
binders += ", ";
}
binders += x.as_str();
binders += &x.to_string();
binders
},
);
Expand All @@ -3240,15 +3240,15 @@ 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.unwrap_or(Ident::from_str("'a")));
(span, sugg)
};

if higher_ranked {
let message = Cow::from(format!(
"consider making the {} lifetime-generic with a new `{}` lifetime",
kind.descr(),
name.unwrap_or("'a"),
name.unwrap_or(Ident::from_str("'a")),
));
should_continue = suggest(
err,
Expand Down
25 changes: 24 additions & 1 deletion compiler/rustc_span/src/symbol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2473,6 +2473,10 @@ impl Ident {
pub fn as_str(&self) -> &str {
self.name.as_str()
}

pub fn as_lifetime(&self) -> Option<Ident> {
self.name.as_lifetime().map(|sym| Ident::with_dummy_span(sym))
}
}

impl PartialEq for Ident {
Expand Down Expand Up @@ -2542,6 +2546,14 @@ impl IdentPrinter {

impl fmt::Display for IdentPrinter {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some(lifetime) = self.symbol.as_lifetime() {
f.write_str("'")?;
if self.is_raw {
f.write_str("r#")?;
}
return fmt::Display::fmt(&lifetime, f);
}

if self.is_raw {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When Ident starts with ', the contents are printed recursively.

f.write_str("r#")?;
} else if self.symbol == kw::DollarCrate {
Expand Down Expand Up @@ -2633,6 +2645,11 @@ impl Symbol {
self == sym::empty
}

pub fn as_lifetime(self) -> Option<Symbol> {
let name = self.as_str();
name.strip_prefix("'").map(Symbol::intern)
}

/// This method is supposed to be used in error messages, so it's expected to be
/// identical to printing the original identifier token written in source code
/// (`token_to_string`, `Ident::to_string`), except that symbols don't keep the rawness flag
Expand Down Expand Up @@ -2863,7 +2880,13 @@ impl Ident {
/// We see this identifier in a normal identifier position, like variable name or a type.
/// How was it written originally? Did it use the raw form? Let's try to guess.
pub fn is_raw_guess(self) -> bool {
self.name.can_be_raw() && self.is_reserved()
if self.name == kw::StaticLifetime || self.name == kw::UnderscoreLifetime {
false
} else if let Some(lifetime) = self.as_lifetime() {
lifetime.is_raw_guess()
} else {
self.name.can_be_raw() && self.is_reserved()
}
Comment on lines +2883 to +2889
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are two keywords that need to be excluded here.

}

/// Whether this would be the identifier for a tuple field like `self.0`, as
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
//@ edition: 2021
fn a(_: dyn Trait + 'r#fn) { //~ ERROR use of undeclared lifetime name `'r#fn` [E0261]

}

trait Trait {}

#[derive(Eq, PartialEq)]
struct Test {
a: &'r#fn str,
//~^ ERROR use of undeclared lifetime name `'r#fn` [E0261]
//~| 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() {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
error[E0261]: use of undeclared lifetime name `'r#fn`
--> $DIR/error-lifetime-name-issue-143150.rs:2: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/error-lifetime-name-issue-143150.rs:10: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/error-lifetime-name-issue-143150.rs:10:9
|
LL | #[derive(Eq, PartialEq)]
| -- lifetime `'r#fn` is missing in item created through this procedural macro
LL | struct Test {
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/error-lifetime-name-issue-143150.rs:16: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 4 previous errors

For more information about this error, try `rustc --explain E0261`.
Loading