Skip to content

Commit ce361fb

Browse files
committed
Auto merge of #68267 - estebank:lt-sugg, r=petrochenkov
Tweak lifetime definition errors Taking inspiration from the narrative in @fasterthanlime's https://fasterthanli.me/blog/2019/declarative-memory-management/, add suggestions to some lifetime definition errors.
2 parents 8c73fa7 + 03d7fed commit ce361fb

33 files changed

+485
-80
lines changed

src/librustc_resolve/diagnostics.rs

+73-1
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@ use rustc::bug;
55
use rustc::session::Session;
66
use rustc::ty::{self, DefIdTree};
77
use rustc_data_structures::fx::FxHashSet;
8-
use rustc_errors::{struct_span_err, Applicability, DiagnosticBuilder};
8+
use rustc_errors::{pluralize, struct_span_err, Applicability, DiagnosticBuilder};
99
use rustc_feature::BUILTIN_ATTRIBUTES;
10+
use rustc_hir as hir;
1011
use rustc_hir::def::Namespace::{self, *};
1112
use rustc_hir::def::{self, CtorKind, CtorOf, DefKind, NonMacroAttrKind};
1213
use rustc_hir::def_id::{DefId, CRATE_DEF_INDEX, LOCAL_CRATE};
@@ -1445,3 +1446,74 @@ crate fn show_candidates(
14451446
}
14461447
}
14471448
}
1449+
1450+
crate fn report_missing_lifetime_specifiers(
1451+
sess: &Session,
1452+
span: Span,
1453+
count: usize,
1454+
) -> DiagnosticBuilder<'_> {
1455+
struct_span_err!(sess, span, E0106, "missing lifetime specifier{}", pluralize!(count))
1456+
}
1457+
1458+
crate fn add_missing_lifetime_specifiers_label(
1459+
err: &mut DiagnosticBuilder<'_>,
1460+
span: Span,
1461+
count: usize,
1462+
lifetime_names: &FxHashSet<ast::Ident>,
1463+
snippet: Option<&str>,
1464+
missing_named_lifetime_spots: &[&hir::Generics<'_>],
1465+
) {
1466+
if count > 1 {
1467+
err.span_label(span, format!("expected {} lifetime parameters", count));
1468+
} else {
1469+
let suggest_existing = |err: &mut DiagnosticBuilder<'_>, sugg| {
1470+
err.span_suggestion(
1471+
span,
1472+
"consider using the named lifetime",
1473+
sugg,
1474+
Applicability::MaybeIncorrect,
1475+
);
1476+
};
1477+
let suggest_new = |err: &mut DiagnosticBuilder<'_>, sugg| {
1478+
err.span_label(span, "expected named lifetime parameter");
1479+
1480+
if let Some(generics) = missing_named_lifetime_spots.iter().last() {
1481+
let mut introduce_suggestion = vec![];
1482+
introduce_suggestion.push(match &generics.params {
1483+
[] => (generics.span, "<'lifetime>".to_string()),
1484+
[param, ..] => (param.span.shrink_to_lo(), "'lifetime, ".to_string()),
1485+
});
1486+
introduce_suggestion.push((span, sugg));
1487+
err.multipart_suggestion(
1488+
"consider introducing a named lifetime parameter",
1489+
introduce_suggestion,
1490+
Applicability::MaybeIncorrect,
1491+
);
1492+
}
1493+
};
1494+
1495+
match (lifetime_names.len(), lifetime_names.iter().next(), snippet) {
1496+
(1, Some(name), Some("&")) => {
1497+
suggest_existing(err, format!("&{} ", name));
1498+
}
1499+
(1, Some(name), Some("'_")) => {
1500+
suggest_existing(err, name.to_string());
1501+
}
1502+
(1, Some(name), Some(snippet)) if !snippet.ends_with(">") => {
1503+
suggest_existing(err, format!("{}<{}>", snippet, name));
1504+
}
1505+
(0, _, Some("&")) => {
1506+
suggest_new(err, "&'lifetime ".to_string());
1507+
}
1508+
(0, _, Some("'_")) => {
1509+
suggest_new(err, "'lifetime".to_string());
1510+
}
1511+
(0, _, Some(snippet)) if !snippet.ends_with(">") => {
1512+
suggest_new(err, format!("{}<'lifetime>", snippet));
1513+
}
1514+
_ => {
1515+
err.span_label(span, "expected lifetime parameter");
1516+
}
1517+
}
1518+
}
1519+
}

src/librustc_resolve/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#![feature(crate_visibility_modifier)]
1212
#![feature(label_break_value)]
1313
#![feature(nll)]
14+
#![cfg_attr(bootstrap, feature(slice_patterns))]
1415
#![recursion_limit = "256"]
1516

1617
pub use rustc_hir::def::{Namespace, PerNS};

src/librustc_resolve/lifetimes.rs

+41-38
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,16 @@
55
//! used between functions, and they operate in a purely top-down
66
//! way. Therefore, we break lifetime name resolution into a separate pass.
77
8+
use crate::diagnostics::{
9+
add_missing_lifetime_specifiers_label, report_missing_lifetime_specifiers,
10+
};
811
use rustc::hir::map::Map;
912
use rustc::lint;
1013
use rustc::middle::resolve_lifetime::*;
11-
use rustc::session::Session;
1214
use rustc::ty::{self, DefIdTree, GenericParamDefKind, TyCtxt};
1315
use rustc::{bug, span_bug};
1416
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
15-
use rustc_errors::{pluralize, struct_span_err, Applicability, DiagnosticBuilder};
17+
use rustc_errors::{struct_span_err, Applicability, DiagnosticBuilder};
1618
use rustc_hir as hir;
1719
use rustc_hir::def::{DefKind, Res};
1820
use rustc_hir::def_id::{CrateNum, DefId, DefIdMap, LocalDefId, LOCAL_CRATE};
@@ -181,6 +183,10 @@ struct LifetimeContext<'a, 'tcx> {
181183
xcrate_object_lifetime_defaults: DefIdMap<Vec<ObjectLifetimeDefault>>,
182184

183185
lifetime_uses: &'a mut DefIdMap<LifetimeUseSet<'tcx>>,
186+
187+
/// When encountering an undefined named lifetime, we will suggest introducing it in these
188+
/// places.
189+
missing_named_lifetime_spots: Vec<&'tcx hir::Generics<'tcx>>,
184190
}
185191

186192
#[derive(Debug)]
@@ -340,6 +346,7 @@ fn krate(tcx: TyCtxt<'_>) -> NamedRegionMap {
340346
labels_in_fn: vec![],
341347
xcrate_object_lifetime_defaults: Default::default(),
342348
lifetime_uses: &mut Default::default(),
349+
missing_named_lifetime_spots: vec![],
343350
};
344351
for (_, item) in &krate.items {
345352
visitor.visit_item(item);
@@ -382,9 +389,11 @@ impl<'a, 'tcx> Visitor<'tcx> for LifetimeContext<'a, 'tcx> {
382389
fn visit_item(&mut self, item: &'tcx hir::Item<'tcx>) {
383390
match item.kind {
384391
hir::ItemKind::Fn(ref sig, ref generics, _) => {
392+
self.missing_named_lifetime_spots.push(generics);
385393
self.visit_early_late(None, &sig.decl, generics, |this| {
386394
intravisit::walk_item(this, item);
387395
});
396+
self.missing_named_lifetime_spots.pop();
388397
}
389398

390399
hir::ItemKind::ExternCrate(_)
@@ -415,6 +424,8 @@ impl<'a, 'tcx> Visitor<'tcx> for LifetimeContext<'a, 'tcx> {
415424
| hir::ItemKind::Trait(_, _, ref generics, ..)
416425
| hir::ItemKind::TraitAlias(ref generics, ..)
417426
| hir::ItemKind::Impl { ref generics, .. } => {
427+
self.missing_named_lifetime_spots.push(generics);
428+
418429
// Impls permit `'_` to be used and it is equivalent to "some fresh lifetime name".
419430
// This is not true for other kinds of items.x
420431
let track_lifetime_uses = match item.kind {
@@ -452,6 +463,7 @@ impl<'a, 'tcx> Visitor<'tcx> for LifetimeContext<'a, 'tcx> {
452463
this.check_lifetime_params(old_scope, &generics.params);
453464
intravisit::walk_item(this, item);
454465
});
466+
self.missing_named_lifetime_spots.pop();
455467
}
456468
}
457469
}
@@ -684,6 +696,7 @@ impl<'a, 'tcx> Visitor<'tcx> for LifetimeContext<'a, 'tcx> {
684696

685697
fn visit_trait_item(&mut self, trait_item: &'tcx hir::TraitItem<'tcx>) {
686698
use self::hir::TraitItemKind::*;
699+
self.missing_named_lifetime_spots.push(&trait_item.generics);
687700
match trait_item.kind {
688701
Method(ref sig, _) => {
689702
let tcx = self.tcx;
@@ -735,10 +748,12 @@ impl<'a, 'tcx> Visitor<'tcx> for LifetimeContext<'a, 'tcx> {
735748
intravisit::walk_trait_item(self, trait_item);
736749
}
737750
}
751+
self.missing_named_lifetime_spots.pop();
738752
}
739753

740754
fn visit_impl_item(&mut self, impl_item: &'tcx hir::ImplItem<'tcx>) {
741755
use self::hir::ImplItemKind::*;
756+
self.missing_named_lifetime_spots.push(&impl_item.generics);
742757
match impl_item.kind {
743758
Method(ref sig, _) => {
744759
let tcx = self.tcx;
@@ -822,6 +837,7 @@ impl<'a, 'tcx> Visitor<'tcx> for LifetimeContext<'a, 'tcx> {
822837
intravisit::walk_impl_item(self, impl_item);
823838
}
824839
}
840+
self.missing_named_lifetime_spots.pop();
825841
}
826842

827843
fn visit_lifetime(&mut self, lifetime_ref: &'tcx hir::Lifetime) {
@@ -1307,6 +1323,7 @@ impl<'a, 'tcx> LifetimeContext<'a, 'tcx> {
13071323
let LifetimeContext { tcx, map, lifetime_uses, .. } = self;
13081324
let labels_in_fn = take(&mut self.labels_in_fn);
13091325
let xcrate_object_lifetime_defaults = take(&mut self.xcrate_object_lifetime_defaults);
1326+
let missing_named_lifetime_spots = take(&mut self.missing_named_lifetime_spots);
13101327
let mut this = LifetimeContext {
13111328
tcx: *tcx,
13121329
map: map,
@@ -1315,14 +1332,16 @@ impl<'a, 'tcx> LifetimeContext<'a, 'tcx> {
13151332
is_in_fn_syntax: self.is_in_fn_syntax,
13161333
labels_in_fn,
13171334
xcrate_object_lifetime_defaults,
1318-
lifetime_uses: lifetime_uses,
1335+
lifetime_uses,
1336+
missing_named_lifetime_spots,
13191337
};
13201338
debug!("entering scope {:?}", this.scope);
13211339
f(self.scope, &mut this);
13221340
this.check_uses_for_lifetimes_defined_by_scope();
13231341
debug!("exiting scope {:?}", this.scope);
13241342
self.labels_in_fn = this.labels_in_fn;
13251343
self.xcrate_object_lifetime_defaults = this.xcrate_object_lifetime_defaults;
1344+
self.missing_named_lifetime_spots = this.missing_named_lifetime_spots;
13261345
}
13271346

13281347
/// helper method to determine the span to remove when suggesting the
@@ -1805,15 +1824,29 @@ impl<'a, 'tcx> LifetimeContext<'a, 'tcx> {
18051824

18061825
self.insert_lifetime(lifetime_ref, def);
18071826
} else {
1808-
struct_span_err!(
1827+
let mut err = struct_span_err!(
18091828
self.tcx.sess,
18101829
lifetime_ref.span,
18111830
E0261,
18121831
"use of undeclared lifetime name `{}`",
18131832
lifetime_ref
1814-
)
1815-
.span_label(lifetime_ref.span, "undeclared lifetime")
1816-
.emit();
1833+
);
1834+
err.span_label(lifetime_ref.span, "undeclared lifetime");
1835+
if !self.is_in_fn_syntax {
1836+
for generics in &self.missing_named_lifetime_spots {
1837+
let (span, sugg) = match &generics.params {
1838+
[] => (generics.span, format!("<{}>", lifetime_ref)),
1839+
[param, ..] => (param.span.shrink_to_lo(), format!("{}, ", lifetime_ref)),
1840+
};
1841+
err.span_suggestion(
1842+
span,
1843+
&format!("consider introducing lifetime `{}` here", lifetime_ref),
1844+
sugg,
1845+
Applicability::MaybeIncorrect,
1846+
);
1847+
}
1848+
}
1849+
err.emit();
18171850
}
18181851
}
18191852

@@ -2367,6 +2400,7 @@ impl<'a, 'tcx> LifetimeContext<'a, 'tcx> {
23672400
lifetime_refs.len(),
23682401
&lifetime_names,
23692402
self.tcx.sess.source_map().span_to_snippet(span).ok().as_ref().map(|s| s.as_str()),
2403+
&self.missing_named_lifetime_spots,
23702404
);
23712405
}
23722406

@@ -2862,34 +2896,3 @@ fn insert_late_bound_lifetimes(
28622896
}
28632897
}
28642898
}
2865-
2866-
fn report_missing_lifetime_specifiers(
2867-
sess: &Session,
2868-
span: Span,
2869-
count: usize,
2870-
) -> DiagnosticBuilder<'_> {
2871-
struct_span_err!(sess, span, E0106, "missing lifetime specifier{}", pluralize!(count))
2872-
}
2873-
2874-
fn add_missing_lifetime_specifiers_label(
2875-
err: &mut DiagnosticBuilder<'_>,
2876-
span: Span,
2877-
count: usize,
2878-
lifetime_names: &FxHashSet<ast::Ident>,
2879-
snippet: Option<&str>,
2880-
) {
2881-
if count > 1 {
2882-
err.span_label(span, format!("expected {} lifetime parameters", count));
2883-
} else if let (1, Some(name), Some("&")) =
2884-
(lifetime_names.len(), lifetime_names.iter().next(), snippet)
2885-
{
2886-
err.span_suggestion(
2887-
span,
2888-
"consider using the named lifetime",
2889-
format!("&{} ", name),
2890-
Applicability::MaybeIncorrect,
2891-
);
2892-
} else {
2893-
err.span_label(span, "expected lifetime parameter");
2894-
}
2895-
}

src/test/ui/error-codes/E0106.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ struct Buzz<'a, 'b>(&'a str, &'b str);
1616
struct Quux {
1717
baz: Baz,
1818
//~^ ERROR E0106
19-
//~| expected lifetime parameter
19+
//~| expected named lifetime parameter
2020
buzz: Buzz,
2121
//~^ ERROR E0106
2222
//~| expected 2 lifetime parameters

src/test/ui/error-codes/E0106.stderr

+28-4
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,49 @@ error[E0106]: missing lifetime specifier
22
--> $DIR/E0106.rs:2:8
33
|
44
LL | x: &bool,
5-
| ^ expected lifetime parameter
5+
| ^ expected named lifetime parameter
6+
|
7+
help: consider introducing a named lifetime parameter
8+
|
9+
LL | struct Foo<'lifetime> {
10+
LL | x: &'lifetime bool,
11+
|
612

713
error[E0106]: missing lifetime specifier
814
--> $DIR/E0106.rs:7:7
915
|
1016
LL | B(&bool),
11-
| ^ expected lifetime parameter
17+
| ^ expected named lifetime parameter
18+
|
19+
help: consider introducing a named lifetime parameter
20+
|
21+
LL | enum Bar<'lifetime> {
22+
LL | A(u8),
23+
LL | B(&'lifetime bool),
24+
|
1225

1326
error[E0106]: missing lifetime specifier
1427
--> $DIR/E0106.rs:10:14
1528
|
1629
LL | type MyStr = &str;
17-
| ^ expected lifetime parameter
30+
| ^ expected named lifetime parameter
31+
|
32+
help: consider introducing a named lifetime parameter
33+
|
34+
LL | type MyStr<'lifetime> = &'lifetime str;
35+
| ^^^^^^^^^^^ ^^^^^^^^^^
1836

1937
error[E0106]: missing lifetime specifier
2038
--> $DIR/E0106.rs:17:10
2139
|
2240
LL | baz: Baz,
23-
| ^^^ expected lifetime parameter
41+
| ^^^ expected named lifetime parameter
42+
|
43+
help: consider introducing a named lifetime parameter
44+
|
45+
LL | struct Quux<'lifetime> {
46+
LL | baz: Baz<'lifetime>,
47+
|
2448

2549
error[E0106]: missing lifetime specifiers
2650
--> $DIR/E0106.rs:20:11

src/test/ui/error-codes/E0261.stderr

+5-1
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,15 @@ error[E0261]: use of undeclared lifetime name `'a`
22
--> $DIR/E0261.rs:1:12
33
|
44
LL | fn foo(x: &'a str) { }
5-
| ^^ undeclared lifetime
5+
| - ^^ undeclared lifetime
6+
| |
7+
| help: consider introducing lifetime `'a` here: `<'a>`
68

79
error[E0261]: use of undeclared lifetime name `'a`
810
--> $DIR/E0261.rs:5:9
911
|
12+
LL | struct Foo {
13+
| - help: consider introducing lifetime `'a` here: `<'a>`
1014
LL | x: &'a str,
1115
| ^^ undeclared lifetime
1216

0 commit comments

Comments
 (0)