Rollup merge of #152637 - JohnTitor:issue-65866, r=estebank

Add a note about elided lifetime

Fixes rust-lang/rust#65866
r? @estebank
This commit is contained in:
Stuart Cook 2026-02-18 17:29:42 +11:00 committed by GitHub
commit 9e38745532
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 206 additions and 3 deletions

View file

@ -11,7 +11,7 @@ use rustc_middle::traits::ObligationCauseCode;
use rustc_middle::ty::error::ExpectedFound;
use rustc_middle::ty::print::RegionHighlightMode;
use rustc_middle::ty::{self, TyCtxt, TypeVisitable};
use rustc_span::Span;
use rustc_span::{Ident, Span};
use tracing::debug;
use crate::error_reporting::infer::nice_region_error::NiceRegionError;
@ -99,7 +99,8 @@ impl<'a, 'tcx> NiceRegionError<'a, 'tcx> {
// Get the span of all the used type parameters in the method.
let assoc_item = self.tcx().associated_item(trait_item_def_id);
let mut visitor = TypeParamSpanVisitor { tcx: self.tcx(), types: vec![] };
let mut visitor =
TypeParamSpanVisitor { tcx: self.tcx(), types: vec![], elided_lifetime_paths: vec![] };
match assoc_item.kind {
ty::AssocKind::Fn { .. } => {
if let Some(hir_id) =
@ -122,13 +123,49 @@ impl<'a, 'tcx> NiceRegionError<'a, 'tcx> {
found,
};
self.tcx().dcx().emit_err(diag)
let mut diag = self.tcx().dcx().create_err(diag);
// A limit not to make diag verbose.
const ELIDED_LIFETIME_NOTE_LIMIT: usize = 5;
let elided_lifetime_paths = visitor.elided_lifetime_paths;
let total_elided_lifetime_paths = elided_lifetime_paths.len();
let shown_elided_lifetime_paths = if tcx.sess.opts.verbose {
total_elided_lifetime_paths
} else {
ELIDED_LIFETIME_NOTE_LIMIT
};
for elided in elided_lifetime_paths.into_iter().take(shown_elided_lifetime_paths) {
diag.span_note(
elided.span,
format!("`{}` here is elided as `{}`", elided.ident, elided.shorthand),
);
}
if total_elided_lifetime_paths > shown_elided_lifetime_paths {
diag.note(format!(
"and {} more elided lifetime{} in type paths",
total_elided_lifetime_paths - shown_elided_lifetime_paths,
if total_elided_lifetime_paths - shown_elided_lifetime_paths == 1 {
""
} else {
"s"
},
));
}
diag.emit()
}
}
#[derive(Clone)]
struct ElidedLifetimeInPath {
span: Span,
ident: Ident,
shorthand: String,
}
struct TypeParamSpanVisitor<'tcx> {
tcx: TyCtxt<'tcx>,
types: Vec<Span>,
elided_lifetime_paths: Vec<ElidedLifetimeInPath>,
}
impl<'tcx> Visitor<'tcx> for TypeParamSpanVisitor<'tcx> {
@ -138,6 +175,83 @@ impl<'tcx> Visitor<'tcx> for TypeParamSpanVisitor<'tcx> {
self.tcx
}
fn visit_qpath(&mut self, qpath: &'tcx hir::QPath<'tcx>, id: hir::HirId, _span: Span) {
fn record_elided_lifetimes(
tcx: TyCtxt<'_>,
elided_lifetime_paths: &mut Vec<ElidedLifetimeInPath>,
segment: &hir::PathSegment<'_>,
) {
let Some(args) = segment.args else { return };
if args.parenthesized != hir::GenericArgsParentheses::No {
// Our diagnostic rendering below uses `<...>` syntax; skip cases like `Fn(..) -> ..`.
return;
}
let elided_count = args
.args
.iter()
.filter(|arg| {
let hir::GenericArg::Lifetime(l) = arg else { return false };
l.syntax == hir::LifetimeSyntax::Implicit
&& matches!(l.source, hir::LifetimeSource::Path { .. })
})
.count();
if elided_count == 0
|| elided_lifetime_paths.iter().any(|p| p.span == segment.ident.span)
{
return;
}
let sm = tcx.sess.source_map();
let mut parts = args
.args
.iter()
.map(|arg| match arg {
hir::GenericArg::Lifetime(l) => {
if l.syntax == hir::LifetimeSyntax::Implicit
&& matches!(l.source, hir::LifetimeSource::Path { .. })
{
"'_".to_string()
} else {
sm.span_to_snippet(l.ident.span)
.unwrap_or_else(|_| format!("'{}", l.ident.name))
}
}
hir::GenericArg::Type(ty) => {
sm.span_to_snippet(ty.span).unwrap_or_else(|_| "..".to_string())
}
hir::GenericArg::Const(ct) => {
sm.span_to_snippet(ct.span).unwrap_or_else(|_| "..".to_string())
}
hir::GenericArg::Infer(_) => "_".to_string(),
})
.collect::<Vec<_>>();
parts.extend(args.constraints.iter().map(|constraint| {
sm.span_to_snippet(constraint.span)
.unwrap_or_else(|_| format!("{} = ..", constraint.ident))
}));
let shorthand = format!("{}<{}>", segment.ident, parts.join(", "));
elided_lifetime_paths.push(ElidedLifetimeInPath {
span: segment.ident.span,
ident: segment.ident,
shorthand,
});
}
match qpath {
hir::QPath::Resolved(_, path) => {
for segment in path.segments {
record_elided_lifetimes(self.tcx, &mut self.elided_lifetime_paths, segment);
}
}
hir::QPath::TypeRelative(_, segment) => {
record_elided_lifetimes(self.tcx, &mut self.elided_lifetime_paths, segment);
}
}
hir::intravisit::walk_qpath(self, qpath, id);
}
fn visit_ty(&mut self, arg: &'tcx hir::Ty<'tcx, AmbigArg>) {
match arg.kind {
hir::TyKind::Ref(_, ref mut_ty) => {

View file

@ -0,0 +1,49 @@
// Regression test for https://github.com/rust-lang/rust/issues/65866.
mod plain {
struct Foo;
struct Re<'a> {
_data: &'a u16,
}
trait Bar {
fn bar(&self, r: &mut Re);
//~^ NOTE expected
//~| NOTE `Re` here is elided as `Re<'_>`
}
impl Bar for Foo {
fn bar<'a, 'b>(&'a self, _r: &'b mut Re<'a>) {}
//~^ ERROR `impl` item signature doesn't match `trait` item signature
//~| NOTE expected signature
//~| NOTE found
//~| HELP the lifetime requirements
//~| HELP verify the lifetime relationships
}
}
mod with_type_args {
struct Foo;
struct Re<'a, T> {
_data: (&'a u16, T),
}
trait Bar {
fn bar(&self, r: &mut Re<u8>);
//~^ NOTE expected
//~| NOTE `Re` here is elided as `Re<'_, u8>`
}
impl Bar for Foo {
fn bar<'a, 'b>(&'a self, _r: &'b mut Re<'a, u8>) {}
//~^ ERROR `impl` item signature doesn't match `trait` item signature
//~| NOTE expected signature
//~| NOTE found
//~| HELP the lifetime requirements
//~| HELP verify the lifetime relationships
}
}
fn main() {}

View file

@ -0,0 +1,40 @@
error: `impl` item signature doesn't match `trait` item signature
--> $DIR/trait-impl-mismatch-elided-lifetime-issue-65866.rs:17:9
|
LL | fn bar(&self, r: &mut Re);
| -------------------------- expected `fn(&'1 plain::Foo, &'2 mut plain::Re<'3>)`
...
LL | fn bar<'a, 'b>(&'a self, _r: &'b mut Re<'a>) {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ found `fn(&'1 plain::Foo, &'2 mut plain::Re<'1>)`
|
= note: expected signature `fn(&'1 plain::Foo, &'2 mut plain::Re<'3>)`
found signature `fn(&'1 plain::Foo, &'2 mut plain::Re<'1>)`
= help: the lifetime requirements from the `impl` do not correspond to the requirements in the `trait`
= help: verify the lifetime relationships in the `trait` and `impl` between the `self` argument, the other inputs and its output
note: `Re` here is elided as `Re<'_>`
--> $DIR/trait-impl-mismatch-elided-lifetime-issue-65866.rs:11:31
|
LL | fn bar(&self, r: &mut Re);
| ^^
error: `impl` item signature doesn't match `trait` item signature
--> $DIR/trait-impl-mismatch-elided-lifetime-issue-65866.rs:40:9
|
LL | fn bar(&self, r: &mut Re<u8>);
| ------------------------------ expected `fn(&'1 with_type_args::Foo, &'2 mut with_type_args::Re<'3, u8>)`
...
LL | fn bar<'a, 'b>(&'a self, _r: &'b mut Re<'a, u8>) {}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ found `fn(&'1 with_type_args::Foo, &'2 mut with_type_args::Re<'1, u8>)`
|
= note: expected signature `fn(&'1 with_type_args::Foo, &'2 mut with_type_args::Re<'3, u8>)`
found signature `fn(&'1 with_type_args::Foo, &'2 mut with_type_args::Re<'1, u8>)`
= help: the lifetime requirements from the `impl` do not correspond to the requirements in the `trait`
= help: verify the lifetime relationships in the `trait` and `impl` between the `self` argument, the other inputs and its output
note: `Re` here is elided as `Re<'_, u8>`
--> $DIR/trait-impl-mismatch-elided-lifetime-issue-65866.rs:34:31
|
LL | fn bar(&self, r: &mut Re<u8>);
| ^^
error: aborting due to 2 previous errors