Skip to content

Commit

Permalink
Rollup merge of #124682 - estebank:issue-40990, r=pnkfelix
Browse files Browse the repository at this point in the history
Suggest setting lifetime in borrowck error involving types with elided lifetimes

```
error: lifetime may not live long enough
  --> $DIR/ex3-both-anon-regions-both-are-structs-2.rs:7:5
   |
LL | fn foo(mut x: Ref, y: Ref) {
   |        -----       - has type `Ref<'_, '1>`
   |        |
   |        has type `Ref<'_, '2>`
LL |     x.b = y.b;
   |     ^^^^^^^^^ assignment requires that `'1` must outlive `'2`
   |
help: consider introducing a named lifetime parameter
   |
LL | fn foo<'a>(mut x: Ref<'a, 'a>, y: Ref<'a, 'a>) {
   |       ++++           ++++++++        ++++++++
```

As can be seen above, it currently doesn't try to compare the `ty::Ty` lifetimes that diverged vs the `hir::Ty` to correctly suggest the following

```
help: consider introducing a named lifetime parameter
   |
LL | fn foo<'a>(mut x: Ref<'_, 'a>, y: Ref<'_, 'a>) {
   |       ++++           ++++++++        ++++++++
```

but I believe this to still be an improvement over the status quo.

Fix #40990.
  • Loading branch information
matthiaskrgr authored May 20, 2024
2 parents 75cb5c5 + cf5702e commit 29c603c
Show file tree
Hide file tree
Showing 42 changed files with 698 additions and 216 deletions.
5 changes: 4 additions & 1 deletion compiler/rustc_infer/messages.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,10 @@ infer_label_bad = {$bad_kind ->
infer_lf_bound_not_satisfied = lifetime bound not satisfied
infer_lifetime_mismatch = lifetime mismatch
infer_lifetime_param_suggestion = consider introducing a named lifetime parameter{$is_impl ->
infer_lifetime_param_suggestion = consider {$is_reuse ->
[true] reusing
*[false] introducing
} a named lifetime parameter{$is_impl ->
[true] {" "}and update trait if needed
*[false] {""}
}
Expand Down
161 changes: 127 additions & 34 deletions compiler/rustc_infer/src/errors/mod.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
use hir::GenericParamKind;
use rustc_data_structures::fx::FxHashSet;
use rustc_errors::{
codes::*, Applicability, Diag, DiagMessage, DiagStyledString, EmissionGuarantee, IntoDiagArg,
MultiSpan, SubdiagMessageOp, Subdiagnostic,
};
use rustc_hir as hir;
use rustc_hir::intravisit::{walk_ty, Visitor};
use rustc_hir::FnRetTy;
use rustc_macros::{Diagnostic, Subdiagnostic};
use rustc_middle::ty::print::TraitRefPrintOnlyTraitPath;
Expand Down Expand Up @@ -355,31 +357,33 @@ impl Subdiagnostic for AddLifetimeParamsSuggestion<'_> {
_f: &F,
) {
let mut mk_suggestion = || {
let (
hir::Ty { kind: hir::TyKind::Ref(lifetime_sub, _), .. },
hir::Ty { kind: hir::TyKind::Ref(lifetime_sup, _), .. },
) = (self.ty_sub, self.ty_sup)
else {
return false;
};

if !lifetime_sub.is_anonymous() || !lifetime_sup.is_anonymous() {
return false;
};

let Some(anon_reg) = self.tcx.is_suitable_region(self.sub) else {
return false;
};

let node = self.tcx.hir_node_by_def_id(anon_reg.def_id);
let is_impl = matches!(&node, hir::Node::ImplItem(_));
let generics = match node {
let (generics, parent_generics) = match node {
hir::Node::Item(&hir::Item {
kind: hir::ItemKind::Fn(_, ref generics, ..),
..
})
| hir::Node::TraitItem(&hir::TraitItem { ref generics, .. })
| hir::Node::ImplItem(&hir::ImplItem { ref generics, .. }) => generics,
| hir::Node::ImplItem(&hir::ImplItem { ref generics, .. }) => (
generics,
match self.tcx.parent_hir_node(self.tcx.local_def_id_to_hir_id(anon_reg.def_id))
{
hir::Node::Item(hir::Item {
kind: hir::ItemKind::Trait(_, _, ref generics, ..),
..
})
| hir::Node::Item(hir::Item {
kind: hir::ItemKind::Impl(hir::Impl { ref generics, .. }),
..
}) => Some(generics),
_ => None,
},
),
_ => return false,
};

Expand All @@ -390,24 +394,112 @@ impl Subdiagnostic for AddLifetimeParamsSuggestion<'_> {
.map(|p| p.name.ident().name)
.find(|i| *i != kw::UnderscoreLifetime);
let introduce_new = suggestion_param_name.is_none();

let mut default = "'a".to_string();
if let Some(parent_generics) = parent_generics {
let used: FxHashSet<_> = parent_generics
.params
.iter()
.filter(|p| matches!(p.kind, GenericParamKind::Lifetime { .. }))
.map(|p| p.name.ident().name)
.filter(|i| *i != kw::UnderscoreLifetime)
.map(|l| l.to_string())
.collect();
if let Some(lt) =
('a'..='z').map(|it| format!("'{it}")).find(|it| !used.contains(it))
{
// We want a lifetime that *isn't* present in the `trait` or `impl` that assoc
// `fn` belongs to. We could suggest reusing one of their lifetimes, but it is
// likely to be an over-constraining lifetime requirement, so we always add a
// lifetime to the `fn`.
default = lt;
}
}
let suggestion_param_name =
suggestion_param_name.map(|n| n.to_string()).unwrap_or_else(|| "'a".to_owned());

debug!(?lifetime_sup.ident.span);
debug!(?lifetime_sub.ident.span);
let make_suggestion = |ident: Ident| {
let sugg = if ident.name == kw::Empty {
format!("{suggestion_param_name}, ")
} else if ident.name == kw::UnderscoreLifetime && ident.span.is_empty() {
format!("{suggestion_param_name} ")
} else {
suggestion_param_name.clone()
};
(ident.span, sugg)
};
let mut suggestions =
vec![make_suggestion(lifetime_sub.ident), make_suggestion(lifetime_sup.ident)];
suggestion_param_name.map(|n| n.to_string()).unwrap_or_else(|| default);

struct ImplicitLifetimeFinder {
suggestions: Vec<(Span, String)>,
suggestion_param_name: String,
}

impl<'v> Visitor<'v> for ImplicitLifetimeFinder {
fn visit_ty(&mut self, ty: &'v hir::Ty<'v>) {
let make_suggestion = |ident: Ident| {
if ident.name == kw::Empty && ident.span.is_empty() {
format!("{}, ", self.suggestion_param_name)
} else if ident.name == kw::UnderscoreLifetime && ident.span.is_empty() {
format!("{} ", self.suggestion_param_name)
} else {
self.suggestion_param_name.clone()
}
};
match ty.kind {
hir::TyKind::Path(hir::QPath::Resolved(_, path)) => {
for segment in path.segments {
if let Some(args) = segment.args {
if args.args.iter().all(|arg| {
matches!(
arg,
hir::GenericArg::Lifetime(lifetime)
if lifetime.ident.name == kw::Empty
)
}) {
self.suggestions.push((
segment.ident.span.shrink_to_hi(),
format!(
"<{}>",
args.args
.iter()
.map(|_| self.suggestion_param_name.clone())
.collect::<Vec<_>>()
.join(", ")
),
));
} else {
for arg in args.args {
if let hir::GenericArg::Lifetime(lifetime) = arg
&& lifetime.is_anonymous()
{
self.suggestions.push((
lifetime.ident.span,
make_suggestion(lifetime.ident),
));
}
}
}
}
}
}
hir::TyKind::Ref(lifetime, ..) if lifetime.is_anonymous() => {
self.suggestions
.push((lifetime.ident.span, make_suggestion(lifetime.ident)));
}
_ => {}
}
walk_ty(self, ty);
}
}
let mut visitor = ImplicitLifetimeFinder {
suggestions: vec![],
suggestion_param_name: suggestion_param_name.clone(),
};
if let Some(fn_decl) = node.fn_decl()
&& let hir::FnRetTy::Return(ty) = fn_decl.output
{
visitor.visit_ty(ty);
}
if visitor.suggestions.is_empty() {
// Do not suggest constraining the `&self` param, but rather the return type.
// If that is wrong (because it is not sufficient), a follow up error will tell the
// user to fix it. This way we lower the chances of *over* constraining, but still
// get the cake of "correctly" contrained in two steps.
visitor.visit_ty(self.ty_sup);
}
visitor.visit_ty(self.ty_sub);
if visitor.suggestions.is_empty() {
return false;
}
if introduce_new {
let new_param_suggestion = if let Some(first) =
generics.params.iter().find(|p| !p.name.ident().span.is_empty())
Expand All @@ -417,15 +509,16 @@ impl Subdiagnostic for AddLifetimeParamsSuggestion<'_> {
(generics.span, format!("<{suggestion_param_name}>"))
};

suggestions.push(new_param_suggestion);
visitor.suggestions.push(new_param_suggestion);
}

diag.multipart_suggestion(
diag.multipart_suggestion_verbose(
fluent::infer_lifetime_param_suggestion,
suggestions,
visitor.suggestions,
Applicability::MaybeIncorrect,
);
diag.arg("is_impl", is_impl);
diag.arg("is_reuse", !introduce_new);

true
};
if mk_suggestion() && self.add_note {
Expand Down
4 changes: 2 additions & 2 deletions tests/ui/lifetimes/issue-17728.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ LL | Some(entry) => Ok(entry),
|
help: consider introducing a named lifetime parameter
|
LL | fn attemptTraverse<'a>(&'a self, room: &'a Room, directionStr: &str) -> Result<&Room, &str> {
| ++++ ++ ++
LL | fn attemptTraverse<'a>(&self, room: &'a Room, directionStr: &str) -> Result<&'a Room, &'a str> {
| ++++ ++ ++ ++

error: aborting due to 2 previous errors

Expand Down
2 changes: 1 addition & 1 deletion tests/ui/lifetimes/issue-90170-elision-mismatch.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ LL | pub fn foo3<'a>(_other: &'a [u8], x: &mut Vec<&u8>, y: &u8) { x.push(y); }
| | let's call the lifetime of this reference `'1`
| let's call the lifetime of this reference `'2`
|
help: consider introducing a named lifetime parameter
help: consider reusing a named lifetime parameter
|
LL | pub fn foo3<'a>(_other: &'a [u8], x: &mut Vec<&'a u8>, y: &'a u8) { x.push(y); }
| ++ ++
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ LL | fn foo<'a>(x: &i32, y: &'a i32) -> &'a i32 {
LL |
LL | if x > y { x } else { y }
| ^ associated function was supposed to return data with lifetime `'a` but it is returning data with lifetime `'1`
|
help: consider reusing a named lifetime parameter and update trait if needed
|
LL | fn foo<'a>(x: &'a i32, y: &'a i32) -> &'a i32 {
| ++

error: aborting due to 1 previous error

Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//@ run-rustfix
#![allow(dead_code)]
struct Foo {
field: i32,
}

impl Foo {
fn foo<'a>(&self, x: &'a i32) -> &'a i32 {
x
//~^ ERROR lifetime may not live long enough
}
}

fn main() {}
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
//@ run-rustfix
#![allow(dead_code)]
struct Foo {
field: i32
field: i32,
}

impl Foo {
fn foo<'a>(&self, x: &'a i32) -> &i32 {

x
//~^ ERROR lifetime may not live long enough

}

fn foo<'a>(&self, x: &'a i32) -> &i32 {
x
//~^ ERROR lifetime may not live long enough
}
}

fn main() { }
fn main() {}
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
error: lifetime may not live long enough
--> $DIR/ex1-return-one-existing-name-return-type-is-anon.rs:8:5
--> $DIR/ex1-return-one-existing-name-return-type-is-anon.rs:9:9
|
LL | fn foo<'a>(&self, x: &'a i32) -> &i32 {
| -- - let's call the lifetime of this reference `'1`
| |
| lifetime `'a` defined here
LL |
LL | x
| ^ method was supposed to return data with lifetime `'1` but it is returning data with lifetime `'a`
LL | fn foo<'a>(&self, x: &'a i32) -> &i32 {
| -- - let's call the lifetime of this reference `'1`
| |
| lifetime `'a` defined here
LL | x
| ^ method was supposed to return data with lifetime `'1` but it is returning data with lifetime `'a`
|
help: consider reusing a named lifetime parameter and update trait if needed
|
LL | fn foo<'a>(&self, x: &'a i32) -> &'a i32 {
| ++

error: aborting due to 1 previous error

Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ LL | fn foo<'a>(&self, x: &'a Foo) -> &'a Foo {
LL |
LL | if true { x } else { self }
| ^^^^ method was supposed to return data with lifetime `'a` but it is returning data with lifetime `'1`
|
help: consider reusing a named lifetime parameter and update trait if needed
|
LL | fn foo<'a>(&'a self, x: &'a Foo) -> &'a Foo {
| ++

error: aborting due to 1 previous error

Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ LL | fn foo(x: &mut Vec<Ref<i32>>, y: Ref<i32>) {
| has type `&mut Vec<Ref<'2, i32>>`
LL | x.push(y);
| ^^^^^^^^^ argument requires that `'1` must outlive `'2`
|
help: consider introducing a named lifetime parameter
|
LL | fn foo<'a>(x: &mut Vec<Ref<'a, i32>>, y: Ref<'a, i32>) {
| ++++ +++ +++

error: aborting due to 1 previous error

Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ LL | fn foo(mut x: Ref, y: Ref) {
| has type `Ref<'_, '2>`
LL | x.b = y.b;
| ^^^^^^^^^ assignment requires that `'1` must outlive `'2`
|
help: consider introducing a named lifetime parameter
|
LL | fn foo<'a>(mut x: Ref<'a, 'a>, y: Ref<'a, 'a>) {
| ++++ ++++++++ ++++++++

error: aborting due to 1 previous error

Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ LL | fn foo(mut x: Ref) {
| has type `Ref<'2, '_>`
LL | x.a = x.b;
| ^^^^^^^^^ assignment requires that `'1` must outlive `'2`
|
help: consider introducing a named lifetime parameter
|
LL | fn foo<'a>(mut x: Ref<'a, 'a>) {
| ++++ ++++++++

error: aborting due to 1 previous error

Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ LL | fn foo(mut x: Vec<Ref>, y: Ref) {
| has type `Vec<Ref<'2>>`
LL | x.push(y);
| ^^^^^^^^^ argument requires that `'1` must outlive `'2`
|
help: consider introducing a named lifetime parameter
|
LL | fn foo<'a>(mut x: Vec<Ref<'a>>, y: Ref<'a>) {
| ++++ ++++ ++++

error: aborting due to 1 previous error

Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ LL | fn foo(mut x: Ref, y: &u32) {
| has type `Ref<'_, '1>`
LL | y = x.b;
| ^^^^^^^ assignment requires that `'1` must outlive `'2`
|
help: consider introducing a named lifetime parameter
|
LL | fn foo<'a>(mut x: Ref<'a, 'a>, y: &'a u32) {
| ++++ ++++++++ ++

error[E0384]: cannot assign to immutable argument `y`
--> $DIR/ex3-both-anon-regions-one-is-struct-2.rs:4:5
Expand Down
Loading

0 comments on commit 29c603c

Please sign in to comment.