From 4b9e4606cb5c184a142ed34acf6f5baefd2b27f5 Mon Sep 17 00:00:00 2001 From: Nilstrieb <48135649+Nilstrieb@users.noreply.github.com> Date: Tue, 2 Nov 2021 21:22:41 +0100 Subject: [PATCH] Add beginner friendly lifetime elision hint to E0623 Suggest adding a new lifetime parameter when two elided lifetimes should match up but don't Issue #90170 This also changes the tests introduced by the previous commits because of another rustc issue (#90258) --- .../nice_region_error/different_lifetimes.rs | 94 +++++++++++++++++-- .../issue-90170-elision-mismatch.fixed | 9 ++ .../issue-90170-elision-mismatch.nll.stderr | 29 ++++++ .../lifetimes/issue-90170-elision-mismatch.rs | 9 ++ .../issue-90170-elision-mismatch.stderr | 45 +++++++++ .../ex3-both-anon-regions-2.stderr | 6 ++ .../ex3-both-anon-regions-3.stderr | 12 +++ ...x3-both-anon-regions-using-fn-items.stderr | 6 ++ ...th-anon-regions-using-trait-objects.stderr | 6 ++ .../ex3-both-anon-regions.stderr | 6 ++ ...underscore-lifetime-elison-mismatch.stderr | 6 ++ 11 files changed, 220 insertions(+), 8 deletions(-) create mode 100644 src/test/ui/lifetimes/issue-90170-elision-mismatch.fixed create mode 100644 src/test/ui/lifetimes/issue-90170-elision-mismatch.nll.stderr create mode 100644 src/test/ui/lifetimes/issue-90170-elision-mismatch.rs create mode 100644 src/test/ui/lifetimes/issue-90170-elision-mismatch.stderr diff --git a/compiler/rustc_infer/src/infer/error_reporting/nice_region_error/different_lifetimes.rs b/compiler/rustc_infer/src/infer/error_reporting/nice_region_error/different_lifetimes.rs index 1b35c4032f4..ac57796763f 100644 --- a/compiler/rustc_infer/src/infer/error_reporting/nice_region_error/different_lifetimes.rs +++ b/compiler/rustc_infer/src/infer/error_reporting/nice_region_error/different_lifetimes.rs @@ -7,7 +7,10 @@ use crate::infer::lexical_region_resolve::RegionResolutionError; use crate::infer::SubregionOrigin; -use rustc_errors::{struct_span_err, ErrorReported}; +use rustc_errors::{struct_span_err, Applicability, DiagnosticBuilder, ErrorReported}; +use rustc_hir as hir; +use rustc_hir::{GenericParamKind, Ty}; +use rustc_middle::ty::Region; impl<'a, 'tcx> NiceRegionError<'a, 'tcx> { /// Print the error message for lifetime errors when both the concerned regions are anonymous. @@ -160,11 +163,13 @@ pub(super) fn try_report_anon_anon_conflict(&self) -> Option { } }; - let mut e = struct_span_err!(self.tcx().sess, span, E0623, "lifetime mismatch"); + let mut err = struct_span_err!(self.tcx().sess, span, E0623, "lifetime mismatch"); - e.span_label(span_1, main_label); - e.span_label(span_2, String::new()); - e.span_label(span, span_label); + err.span_label(span_1, main_label); + err.span_label(span_2, String::new()); + err.span_label(span, span_label); + + self.suggest_adding_lifetime_params(sub, ty_sup, ty_sub, &mut err); if let Some(t) = future_return_type { let snip = self @@ -178,14 +183,87 @@ pub(super) fn try_report_anon_anon_conflict(&self) -> Option { (_, "") => None, _ => Some(s), }) - .unwrap_or("{unnamed_type}".to_string()); + .unwrap_or_else(|| "{unnamed_type}".to_string()); - e.span_label( + err.span_label( t.span, &format!("this `async fn` implicitly returns an `impl Future`", snip), ); } - e.emit(); + err.emit(); Some(ErrorReported) } + + fn suggest_adding_lifetime_params( + &self, + sub: Region<'tcx>, + ty_sup: &Ty<'_>, + ty_sub: &Ty<'_>, + err: &mut DiagnosticBuilder<'_>, + ) { + if let ( + hir::Ty { kind: hir::TyKind::Rptr(lifetime_sub, _), .. }, + hir::Ty { kind: hir::TyKind::Rptr(lifetime_sup, _), .. }, + ) = (ty_sub, ty_sup) + { + if lifetime_sub.name.is_elided() && lifetime_sup.name.is_elided() { + if let Some(anon_reg) = self.tcx().is_suitable_region(sub) { + let hir_id = self.tcx().hir().local_def_id_to_hir_id(anon_reg.def_id); + if let hir::Node::Item(&hir::Item { + kind: hir::ItemKind::Fn(_, ref generics, ..), + .. + }) = self.tcx().hir().get(hir_id) + { + let (suggestion_param_name, introduce_new) = generics + .params + .iter() + .find(|p| matches!(p.kind, GenericParamKind::Lifetime { .. })) + .and_then(|p| self.tcx().sess.source_map().span_to_snippet(p.span).ok()) + .map(|name| (name, false)) + .unwrap_or_else(|| ("'a".to_string(), true)); + + let mut suggestions = vec![ + if let hir::LifetimeName::Underscore = lifetime_sub.name { + (lifetime_sub.span, suggestion_param_name.clone()) + } else { + ( + lifetime_sub.span.shrink_to_hi(), + suggestion_param_name.clone() + " ", + ) + }, + if let hir::LifetimeName::Underscore = lifetime_sup.name { + (lifetime_sup.span, suggestion_param_name.clone()) + } else { + ( + lifetime_sup.span.shrink_to_hi(), + suggestion_param_name.clone() + " ", + ) + }, + ]; + + if introduce_new { + let new_param_suggestion = match &generics.params { + [] => (generics.span, format!("<{}>", suggestion_param_name)), + [first, ..] => ( + first.span.shrink_to_lo(), + format!("{}, ", suggestion_param_name), + ), + }; + + suggestions.push(new_param_suggestion); + } + + err.multipart_suggestion( + "consider introducing a named lifetime parameter", + suggestions, + Applicability::MaybeIncorrect, + ); + err.note( + "each elided lifetime in input position becomes a distinct lifetime", + ); + } + } + } + } + } } diff --git a/src/test/ui/lifetimes/issue-90170-elision-mismatch.fixed b/src/test/ui/lifetimes/issue-90170-elision-mismatch.fixed new file mode 100644 index 00000000000..0bc889ee721 --- /dev/null +++ b/src/test/ui/lifetimes/issue-90170-elision-mismatch.fixed @@ -0,0 +1,9 @@ +// run-rustfix + +pub fn foo<'a>(x: &mut Vec<&'a u8>, y: &'a u8) { x.push(y); } //~ ERROR lifetime mismatch + +pub fn foo2<'a>(x: &mut Vec<&'a u8>, y: &'a u8) { x.push(y); } //~ ERROR lifetime mismatch + +pub fn foo3<'a>(_other: &'a [u8], x: &mut Vec<&'a u8>, y: &'a u8) { x.push(y); } //~ ERROR lifetime mismatch + +fn main() {} diff --git a/src/test/ui/lifetimes/issue-90170-elision-mismatch.nll.stderr b/src/test/ui/lifetimes/issue-90170-elision-mismatch.nll.stderr new file mode 100644 index 00000000000..a5bc7450bbf --- /dev/null +++ b/src/test/ui/lifetimes/issue-90170-elision-mismatch.nll.stderr @@ -0,0 +1,29 @@ +error: lifetime may not live long enough + --> $DIR/issue-90170-elision-mismatch.rs:3:40 + | +LL | pub fn foo(x: &mut Vec<&u8>, y: &u8) { x.push(y); } + | - - ^^^^^^^^^ argument requires that `'1` must outlive `'2` + | | | + | | let's call the lifetime of this reference `'1` + | let's call the lifetime of this reference `'2` + +error: lifetime may not live long enough + --> $DIR/issue-90170-elision-mismatch.rs:5:44 + | +LL | pub fn foo2(x: &mut Vec<&'_ u8>, y: &u8) { x.push(y); } + | - - ^^^^^^^^^ argument requires that `'1` must outlive `'2` + | | | + | | let's call the lifetime of this reference `'1` + | let's call the lifetime of this reference `'2` + +error: lifetime may not live long enough + --> $DIR/issue-90170-elision-mismatch.rs:7:63 + | +LL | pub fn foo3<'a>(_other: &'a [u8], x: &mut Vec<&u8>, y: &u8) { x.push(y); } + | - - ^^^^^^^^^ argument requires that `'1` must outlive `'2` + | | | + | | let's call the lifetime of this reference `'1` + | let's call the lifetime of this reference `'2` + +error: aborting due to 3 previous errors + diff --git a/src/test/ui/lifetimes/issue-90170-elision-mismatch.rs b/src/test/ui/lifetimes/issue-90170-elision-mismatch.rs new file mode 100644 index 00000000000..1d6573c00ad --- /dev/null +++ b/src/test/ui/lifetimes/issue-90170-elision-mismatch.rs @@ -0,0 +1,9 @@ +// run-rustfix + +pub fn foo(x: &mut Vec<&u8>, y: &u8) { x.push(y); } //~ ERROR lifetime mismatch + +pub fn foo2(x: &mut Vec<&'_ u8>, y: &u8) { x.push(y); } //~ ERROR lifetime mismatch + +pub fn foo3<'a>(_other: &'a [u8], x: &mut Vec<&u8>, y: &u8) { x.push(y); } //~ ERROR lifetime mismatch + +fn main() {} diff --git a/src/test/ui/lifetimes/issue-90170-elision-mismatch.stderr b/src/test/ui/lifetimes/issue-90170-elision-mismatch.stderr new file mode 100644 index 00000000000..7fa092ca220 --- /dev/null +++ b/src/test/ui/lifetimes/issue-90170-elision-mismatch.stderr @@ -0,0 +1,45 @@ +error[E0623]: lifetime mismatch + --> $DIR/issue-90170-elision-mismatch.rs:3:47 + | +LL | pub fn foo(x: &mut Vec<&u8>, y: &u8) { x.push(y); } + | --- --- ^ ...but data from `y` flows into `x` here + | | + | these two types are declared with different lifetimes... + | + = note: each elided lifetime in input position becomes a distinct lifetime +help: consider introducing a named lifetime parameter + | +LL | pub fn foo<'a>(x: &mut Vec<&'a u8>, y: &'a u8) { x.push(y); } + | ++++ ++ ++ + +error[E0623]: lifetime mismatch + --> $DIR/issue-90170-elision-mismatch.rs:5:51 + | +LL | pub fn foo2(x: &mut Vec<&'_ u8>, y: &u8) { x.push(y); } + | ------ --- ^ ...but data from `y` flows into `x` here + | | + | these two types are declared with different lifetimes... + | + = note: each elided lifetime in input position becomes a distinct lifetime +help: consider introducing a named lifetime parameter + | +LL | pub fn foo2<'a>(x: &mut Vec<&'a u8>, y: &'a u8) { x.push(y); } + | ++++ ~~ ++ + +error[E0623]: lifetime mismatch + --> $DIR/issue-90170-elision-mismatch.rs:7:70 + | +LL | pub fn foo3<'a>(_other: &'a [u8], x: &mut Vec<&u8>, y: &u8) { x.push(y); } + | --- --- ^ ...but data from `y` flows into `x` here + | | + | these two types are declared with different lifetimes... + | + = note: each elided lifetime in input position becomes a distinct lifetime +help: consider introducing a named lifetime parameter + | +LL | pub fn foo3<'a>(_other: &'a [u8], x: &mut Vec<&'a u8>, y: &'a u8) { x.push(y); } + | ++ ++ + +error: aborting due to 3 previous errors + +For more information about this error, try `rustc --explain E0623`. diff --git a/src/test/ui/lifetimes/lifetime-errors/ex3-both-anon-regions-2.stderr b/src/test/ui/lifetimes/lifetime-errors/ex3-both-anon-regions-2.stderr index 0aff80c6fbd..5c793636778 100644 --- a/src/test/ui/lifetimes/lifetime-errors/ex3-both-anon-regions-2.stderr +++ b/src/test/ui/lifetimes/lifetime-errors/ex3-both-anon-regions-2.stderr @@ -5,6 +5,12 @@ LL | fn foo(&mut (ref mut v, w): &mut (&u8, &u8), x: &u8) { | --- --- these two types are declared with different lifetimes... LL | *v = x; | ^ ...but data from `x` flows here + | + = note: each elided lifetime in input position becomes a distinct lifetime +help: consider introducing a named lifetime parameter + | +LL | fn foo<'a>(&mut (ref mut v, w): &mut (&'a u8, &u8), x: &'a u8) { + | ++++ ++ ++ error: aborting due to previous error diff --git a/src/test/ui/lifetimes/lifetime-errors/ex3-both-anon-regions-3.stderr b/src/test/ui/lifetimes/lifetime-errors/ex3-both-anon-regions-3.stderr index 2e5ff6782d3..1a7b4fca1ba 100644 --- a/src/test/ui/lifetimes/lifetime-errors/ex3-both-anon-regions-3.stderr +++ b/src/test/ui/lifetimes/lifetime-errors/ex3-both-anon-regions-3.stderr @@ -5,6 +5,12 @@ LL | fn foo(z: &mut Vec<(&u8,&u8)>, (x, y): (&u8, &u8)) { | --- --- these two types are declared with different lifetimes... LL | z.push((x,y)); | ^ ...but data flows into `z` here + | + = note: each elided lifetime in input position becomes a distinct lifetime +help: consider introducing a named lifetime parameter + | +LL | fn foo<'a>(z: &mut Vec<(&'a u8,&u8)>, (x, y): (&'a u8, &u8)) { + | ++++ ++ ++ error[E0623]: lifetime mismatch --> $DIR/ex3-both-anon-regions-3.rs:2:15 @@ -13,6 +19,12 @@ LL | fn foo(z: &mut Vec<(&u8,&u8)>, (x, y): (&u8, &u8)) { | --- --- these two types are declared with different lifetimes... LL | z.push((x,y)); | ^ ...but data flows into `z` here + | + = note: each elided lifetime in input position becomes a distinct lifetime +help: consider introducing a named lifetime parameter + | +LL | fn foo<'a>(z: &mut Vec<(&u8,&'a u8)>, (x, y): (&u8, &'a u8)) { + | ++++ ++ ++ error: aborting due to 2 previous errors diff --git a/src/test/ui/lifetimes/lifetime-errors/ex3-both-anon-regions-using-fn-items.stderr b/src/test/ui/lifetimes/lifetime-errors/ex3-both-anon-regions-using-fn-items.stderr index d2cc3dba6a4..6ed3528bb9f 100644 --- a/src/test/ui/lifetimes/lifetime-errors/ex3-both-anon-regions-using-fn-items.stderr +++ b/src/test/ui/lifetimes/lifetime-errors/ex3-both-anon-regions-using-fn-items.stderr @@ -5,6 +5,12 @@ LL | fn foo(x:fn(&u8, &u8), y: Vec<&u8>, z: &u8) { | --- --- these two types are declared with different lifetimes... LL | y.push(z); | ^ ...but data from `z` flows into `y` here + | + = note: each elided lifetime in input position becomes a distinct lifetime +help: consider introducing a named lifetime parameter + | +LL | fn foo<'a>(x:fn(&u8, &u8), y: Vec<&'a u8>, z: &'a u8) { + | ++++ ++ ++ error: aborting due to previous error diff --git a/src/test/ui/lifetimes/lifetime-errors/ex3-both-anon-regions-using-trait-objects.stderr b/src/test/ui/lifetimes/lifetime-errors/ex3-both-anon-regions-using-trait-objects.stderr index 2acc4eaf60f..ede1631db27 100644 --- a/src/test/ui/lifetimes/lifetime-errors/ex3-both-anon-regions-using-trait-objects.stderr +++ b/src/test/ui/lifetimes/lifetime-errors/ex3-both-anon-regions-using-trait-objects.stderr @@ -5,6 +5,12 @@ LL | fn foo(x:Box , y: Vec<&u8>, z: &u8) { | --- --- these two types are declared with different lifetimes... LL | y.push(z); | ^ ...but data from `z` flows into `y` here + | + = note: each elided lifetime in input position becomes a distinct lifetime +help: consider introducing a named lifetime parameter + | +LL | fn foo<'a>(x:Box , y: Vec<&u8>, z: &u8) { + | ++++ ++ ++ error: aborting due to previous error diff --git a/src/test/ui/lifetimes/lifetime-errors/ex3-both-anon-regions.stderr b/src/test/ui/lifetimes/lifetime-errors/ex3-both-anon-regions.stderr index b2784827672..cf405c0de3f 100644 --- a/src/test/ui/lifetimes/lifetime-errors/ex3-both-anon-regions.stderr +++ b/src/test/ui/lifetimes/lifetime-errors/ex3-both-anon-regions.stderr @@ -5,6 +5,12 @@ LL | fn foo(x: &mut Vec<&u8>, y: &u8) { | --- --- these two types are declared with different lifetimes... LL | x.push(y); | ^ ...but data from `y` flows into `x` here + | + = note: each elided lifetime in input position becomes a distinct lifetime +help: consider introducing a named lifetime parameter + | +LL | fn foo<'a>(x: &mut Vec<&'a u8>, y: &'a u8) { + | ++++ ++ ++ error: aborting due to previous error diff --git a/src/test/ui/underscore-lifetime/underscore-lifetime-elison-mismatch.stderr b/src/test/ui/underscore-lifetime/underscore-lifetime-elison-mismatch.stderr index 76c14ccc14b..8976da01e73 100644 --- a/src/test/ui/underscore-lifetime/underscore-lifetime-elison-mismatch.stderr +++ b/src/test/ui/underscore-lifetime/underscore-lifetime-elison-mismatch.stderr @@ -5,6 +5,12 @@ LL | fn foo(x: &mut Vec<&'_ u8>, y: &'_ u8) { x.push(y); } | ------ ------ ^ ...but data from `y` flows into `x` here | | | these two types are declared with different lifetimes... + | + = note: each elided lifetime in input position becomes a distinct lifetime +help: consider introducing a named lifetime parameter + | +LL | fn foo<'a>(x: &mut Vec<&'a u8>, y: &'a u8) { x.push(y); } + | ++++ ~~ ~~ error: aborting due to previous error