Skip to content

Commit b9585fd

Browse files
committed
When suggesting for lts, consider existing lifetime names
Fix #72404.
1 parent 7956b1c commit b9585fd

File tree

6 files changed

+172
-20
lines changed

6 files changed

+172
-20
lines changed

src/librustc_resolve/late/diagnostics.rs

+62-13
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use rustc_hir::def_id::{DefId, CRATE_DEF_INDEX, LOCAL_CRATE};
1616
use rustc_hir::PrimTy;
1717
use rustc_session::config::nightly_options;
1818
use rustc_span::hygiene::MacroKind;
19-
use rustc_span::symbol::{kw, sym, Ident};
19+
use rustc_span::symbol::{kw, sym, Ident, Symbol};
2020
use rustc_span::{BytePos, Span, DUMMY_SP};
2121

2222
use log::debug;
@@ -1244,7 +1244,8 @@ impl<'tcx> LifetimeContext<'_, 'tcx> {
12441244
err: &mut DiagnosticBuilder<'_>,
12451245
span: Span,
12461246
count: usize,
1247-
lifetime_names: &FxHashSet<Ident>,
1247+
lifetime_names: &FxHashSet<Symbol>,
1248+
lifetime_spans: Vec<Span>,
12481249
params: &[ElisionFailureInfo],
12491250
) {
12501251
let snippet = self.tcx.sess.source_map().span_to_snippet(span).ok();
@@ -1258,11 +1259,60 @@ impl<'tcx> LifetimeContext<'_, 'tcx> {
12581259
),
12591260
);
12601261

1261-
let suggest_existing = |err: &mut DiagnosticBuilder<'_>, sugg| {
1262+
let suggest_existing = |err: &mut DiagnosticBuilder<'_>,
1263+
name: &str,
1264+
formatter: &dyn Fn(&str) -> String| {
1265+
if let Some(MissingLifetimeSpot::HigherRanked { span: for_span, span_type }) =
1266+
self.missing_named_lifetime_spots.iter().rev().next()
1267+
{
1268+
// When we have `struct S<'a>(&'a dyn Fn(&X) -> &X);` we want to not only suggest
1269+
// using `'a`, but also introduce the concept of HRLTs by suggesting
1270+
// `struct S<'a>(&'a dyn for<'b> Fn(&X) -> &'b X);`. (#72404)
1271+
let mut introduce_suggestion = vec![];
1272+
1273+
let a_to_z_repeat_n = |n| {
1274+
(b'a'..=b'z').map(move |c| {
1275+
let mut s = '\''.to_string();
1276+
s.extend(std::iter::repeat(char::from(c)).take(n));
1277+
s
1278+
})
1279+
};
1280+
1281+
// If all single char lifetime names are present, we wrap around and double the chars.
1282+
let lt_name = (1..)
1283+
.flat_map(a_to_z_repeat_n)
1284+
.find(|lt| !lifetime_names.contains(&Symbol::intern(&lt)))
1285+
.unwrap();
1286+
let msg = format!(
1287+
"consider making the {} lifetime-generic with a new `{}` lifetime",
1288+
span_type.descr(),
1289+
lt_name,
1290+
);
1291+
err.note(
1292+
"for more information on higher-ranked polymorphism, visit \
1293+
https://doc.rust-lang.org/nomicon/hrtb.html",
1294+
);
1295+
let for_sugg = span_type.suggestion(&lt_name);
1296+
for param in params {
1297+
if let Ok(snippet) = self.tcx.sess.source_map().span_to_snippet(param.span) {
1298+
if snippet.starts_with('&') && !snippet.starts_with("&'") {
1299+
introduce_suggestion
1300+
.push((param.span, format!("&{} {}", lt_name, &snippet[1..])));
1301+
} else if snippet.starts_with("&'_ ") {
1302+
introduce_suggestion
1303+
.push((param.span, format!("&{} {}", lt_name, &snippet[4..])));
1304+
}
1305+
}
1306+
}
1307+
introduce_suggestion.push((*for_span, for_sugg.to_string()));
1308+
introduce_suggestion.push((span, formatter(&lt_name)));
1309+
err.multipart_suggestion(&msg, introduce_suggestion, Applicability::MaybeIncorrect);
1310+
}
1311+
12621312
err.span_suggestion_verbose(
12631313
span,
12641314
&format!("consider using the `{}` lifetime", lifetime_names.iter().next().unwrap()),
1265-
sugg,
1315+
formatter(name),
12661316
Applicability::MaybeIncorrect,
12671317
);
12681318
};
@@ -1330,26 +1380,26 @@ impl<'tcx> LifetimeContext<'_, 'tcx> {
13301380

13311381
match (lifetime_names.len(), lifetime_names.iter().next(), snippet.as_deref()) {
13321382
(1, Some(name), Some("&")) => {
1333-
suggest_existing(err, format!("&{} ", name));
1383+
suggest_existing(err, &name.as_str()[..], &|name| format!("&{} ", name));
13341384
}
13351385
(1, Some(name), Some("'_")) => {
1336-
suggest_existing(err, name.to_string());
1386+
suggest_existing(err, &name.as_str()[..], &|n| n.to_string());
13371387
}
13381388
(1, Some(name), Some("")) => {
1339-
suggest_existing(err, format!("{}, ", name).repeat(count));
1389+
suggest_existing(err, &name.as_str()[..], &|n| format!("{}, ", n).repeat(count));
13401390
}
13411391
(1, Some(name), Some(snippet)) if !snippet.ends_with('>') => {
1342-
suggest_existing(
1343-
err,
1392+
let f = |name: &str| {
13441393
format!(
13451394
"{}<{}>",
13461395
snippet,
13471396
std::iter::repeat(name.to_string())
13481397
.take(count)
13491398
.collect::<Vec<_>>()
13501399
.join(", ")
1351-
),
1352-
);
1400+
)
1401+
};
1402+
suggest_existing(err, &name.as_str()[..], &f);
13531403
}
13541404
(0, _, Some("&")) if count == 1 => {
13551405
suggest_new(err, "&'a ");
@@ -1367,8 +1417,7 @@ impl<'tcx> LifetimeContext<'_, 'tcx> {
13671417
}
13681418
}
13691419
(n, ..) if n > 1 => {
1370-
let spans: Vec<Span> = lifetime_names.iter().map(|lt| lt.span).collect();
1371-
err.span_note(spans, "these named lifetimes are available to use");
1420+
err.span_note(lifetime_spans, "these named lifetimes are available to use");
13721421
if Some("") == snippet.as_deref() {
13731422
// This happens when we have `Foo<T>` where we point at the space before `T`,
13741423
// but this can be confusing so we give a suggestion with placeholders.

src/librustc_resolve/late/lifetimes.rs

+22-7
Original file line numberDiff line numberDiff line change
@@ -2317,6 +2317,7 @@ impl<'a, 'tcx> LifetimeContext<'a, 'tcx> {
23172317
let mut late_depth = 0;
23182318
let mut scope = self.scope;
23192319
let mut lifetime_names = FxHashSet::default();
2320+
let mut lifetime_spans = vec![];
23202321
let error = loop {
23212322
match *scope {
23222323
// Do not assign any resolution, it will be inferred.
@@ -2328,7 +2329,8 @@ impl<'a, 'tcx> LifetimeContext<'a, 'tcx> {
23282329
// collect named lifetimes for suggestions
23292330
for name in lifetimes.keys() {
23302331
if let hir::ParamName::Plain(name) = name {
2331-
lifetime_names.insert(*name);
2332+
lifetime_names.insert(name.name);
2333+
lifetime_spans.push(name.span);
23322334
}
23332335
}
23342336
late_depth += 1;
@@ -2346,12 +2348,24 @@ impl<'a, 'tcx> LifetimeContext<'a, 'tcx> {
23462348
}
23472349
Elide::Exact(l) => l.shifted(late_depth),
23482350
Elide::Error(ref e) => {
2349-
if let Scope::Binder { ref lifetimes, .. } = s {
2350-
// collect named lifetimes for suggestions
2351-
for name in lifetimes.keys() {
2352-
if let hir::ParamName::Plain(name) = name {
2353-
lifetime_names.insert(*name);
2351+
let mut scope = s;
2352+
loop {
2353+
match scope {
2354+
Scope::Binder { ref lifetimes, s, .. } => {
2355+
// Collect named lifetimes for suggestions.
2356+
for name in lifetimes.keys() {
2357+
if let hir::ParamName::Plain(name) = name {
2358+
lifetime_names.insert(name.name);
2359+
lifetime_spans.push(name.span);
2360+
}
2361+
}
2362+
scope = s;
2363+
}
2364+
Scope::ObjectLifetimeDefault { ref s, .. }
2365+
| Scope::Elision { ref s, .. } => {
2366+
scope = s;
23542367
}
2368+
_ => break,
23552369
}
23562370
}
23572371
break Some(e);
@@ -2375,14 +2389,15 @@ impl<'a, 'tcx> LifetimeContext<'a, 'tcx> {
23752389
if let Some(params) = error {
23762390
// If there's no lifetime available, suggest `'static`.
23772391
if self.report_elision_failure(&mut err, params) && lifetime_names.is_empty() {
2378-
lifetime_names.insert(Ident::with_dummy_span(kw::StaticLifetime));
2392+
lifetime_names.insert(kw::StaticLifetime);
23792393
}
23802394
}
23812395
self.add_missing_lifetime_specifiers_label(
23822396
&mut err,
23832397
span,
23842398
lifetime_refs.len(),
23852399
&lifetime_names,
2400+
lifetime_spans,
23862401
error.map(|p| &p[..]).unwrap_or(&[]),
23872402
);
23882403
err.emit();

src/test/ui/associated-types/bound-lifetime-in-binding-only.elision.stderr

+5
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@ LL | fn elision<T: Fn() -> &i32>() {
55
| ^ expected named lifetime parameter
66
|
77
= help: this function's return type contains a borrowed value, but there is no value for it to be borrowed from
8+
= note: for more information on higher-ranked polymorphism, visit https://doc.rust-lang.org/nomicon/hrtb.html
9+
help: consider making the bound lifetime-generic with a new `'a` lifetime
10+
|
11+
LL | fn elision<T: for<'a> Fn() -> &'a i32>() {
12+
| ^^^^^^^ ^^^
813
help: consider using the `'static` lifetime
914
|
1015
LL | fn elision<T: Fn() -> &'static i32>() {

src/test/ui/associated-types/bound-lifetime-in-return-only.elision.stderr

+5
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@ LL | fn elision(_: fn() -> &i32) {
55
| ^ expected named lifetime parameter
66
|
77
= help: this function's return type contains a borrowed value, but there is no value for it to be borrowed from
8+
= note: for more information on higher-ranked polymorphism, visit https://doc.rust-lang.org/nomicon/hrtb.html
9+
help: consider making the type lifetime-generic with a new `'a` lifetime
10+
|
11+
LL | fn elision(_: for<'a> fn() -> &'a i32) {
12+
| ^^^^^^^ ^^^
813
help: consider using the `'static` lifetime
914
|
1015
LL | fn elision(_: fn() -> &'static i32) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
struct X<'a>(&'a ());
2+
struct S<'a>(&'a dyn Fn(&X) -> &X);
3+
//~^ ERROR missing lifetime specifier
4+
//~| ERROR missing lifetime specifier
5+
struct V<'a>(&'a dyn for<'b> Fn(&X) -> &X);
6+
//~^ ERROR missing lifetime specifier
7+
//~| ERROR missing lifetime specifier
8+
9+
fn main() {
10+
let x = S(&|x| {
11+
println!("hi");
12+
x
13+
});
14+
x.0(&X(&()));
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
error[E0106]: missing lifetime specifier
2+
--> $DIR/missing-lt-for-hrtb.rs:2:32
3+
|
4+
LL | struct S<'a>(&'a dyn Fn(&X) -> &X);
5+
| -- ^ expected named lifetime parameter
6+
|
7+
= help: this function's return type contains a borrowed value, but the signature does not say which one of argument 1's 2 lifetimes it is borrowed from
8+
= note: for more information on higher-ranked polymorphism, visit https://doc.rust-lang.org/nomicon/hrtb.html
9+
help: consider making the bound lifetime-generic with a new `'b` lifetime
10+
|
11+
LL | struct S<'a>(&'a dyn for<'b> Fn(&'b X) -> &'b X);
12+
| ^^^^^^^ ^^^^^ ^^^
13+
help: consider using the `'a` lifetime
14+
|
15+
LL | struct S<'a>(&'a dyn Fn(&X) -> &'a X);
16+
| ^^^
17+
18+
error[E0106]: missing lifetime specifier
19+
--> $DIR/missing-lt-for-hrtb.rs:2:33
20+
|
21+
LL | struct S<'a>(&'a dyn Fn(&X) -> &X);
22+
| -- ^ expected named lifetime parameter
23+
|
24+
= help: this function's return type contains a borrowed value, but the signature does not say which one of argument 1's 2 lifetimes it is borrowed from
25+
= note: for more information on higher-ranked polymorphism, visit https://doc.rust-lang.org/nomicon/hrtb.html
26+
help: consider making the bound lifetime-generic with a new `'b` lifetime
27+
|
28+
LL | struct S<'a>(&'a dyn for<'b> Fn(&'b X) -> &X<'b>);
29+
| ^^^^^^^ ^^^^^ ^^^^^
30+
help: consider using the `'a` lifetime
31+
|
32+
LL | struct S<'a>(&'a dyn Fn(&X) -> &X<'a>);
33+
| ^^^^^
34+
35+
error[E0106]: missing lifetime specifier
36+
--> $DIR/missing-lt-for-hrtb.rs:5:40
37+
|
38+
LL | struct V<'a>(&'a dyn for<'b> Fn(&X) -> &X);
39+
| -- ^ expected named lifetime parameter
40+
|
41+
= help: this function's return type contains a borrowed value, but the signature does not say which one of argument 1's 2 lifetimes it is borrowed from
42+
note: these named lifetimes are available to use
43+
--> $DIR/missing-lt-for-hrtb.rs:5:10
44+
|
45+
LL | struct V<'a>(&'a dyn for<'b> Fn(&X) -> &X);
46+
| ^^ ^^
47+
48+
error[E0106]: missing lifetime specifier
49+
--> $DIR/missing-lt-for-hrtb.rs:5:41
50+
|
51+
LL | struct V<'a>(&'a dyn for<'b> Fn(&X) -> &X);
52+
| -- ^ expected named lifetime parameter
53+
|
54+
= help: this function's return type contains a borrowed value, but the signature does not say which one of argument 1's 2 lifetimes it is borrowed from
55+
note: these named lifetimes are available to use
56+
--> $DIR/missing-lt-for-hrtb.rs:5:10
57+
|
58+
LL | struct V<'a>(&'a dyn for<'b> Fn(&X) -> &X);
59+
| ^^ ^^
60+
61+
error: aborting due to 4 previous errors
62+
63+
For more information about this error, try `rustc --explain E0106`.

0 commit comments

Comments
 (0)