Do not propose to elide lifetimes if this causes an ambiguity (#13929)

Some lifetimes in function return types are not bound to concrete
content and can be set arbitrarily. Clippy should not propose to replace
them by the default `'_` lifetime if such a lifetime cannot be
determined unambigously.

I added a field to the `LifetimeChecker` and `Usage` to flag lifetimes
that cannot be replaced by default ones, but it feels a bit hacky.

Fix #13923

changelog: [`needless_lifetimes`]: remove false positives by checking
that lifetimes can indeed be elided
This commit is contained in:
Fridtjof Stoldt 2025-01-10 11:58:29 +00:00 committed by GitHub
commit 5c2601af15
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 261 additions and 1 deletions

View file

@ -488,11 +488,13 @@ fn has_where_lifetimes<'tcx>(cx: &LateContext<'tcx>, generics: &'tcx Generics<'_
false
}
#[allow(clippy::struct_excessive_bools)]
struct Usage {
lifetime: Lifetime,
in_where_predicate: bool,
in_bounded_ty: bool,
in_generics_arg: bool,
lifetime_elision_impossible: bool,
}
struct LifetimeChecker<'cx, 'tcx, F> {
@ -501,6 +503,7 @@ struct LifetimeChecker<'cx, 'tcx, F> {
where_predicate_depth: usize,
bounded_ty_depth: usize,
generic_args_depth: usize,
lifetime_elision_impossible: bool,
phantom: std::marker::PhantomData<F>,
}
@ -525,6 +528,7 @@ where
where_predicate_depth: 0,
bounded_ty_depth: 0,
generic_args_depth: 0,
lifetime_elision_impossible: false,
phantom: std::marker::PhantomData,
}
}
@ -566,6 +570,7 @@ where
in_where_predicate: self.where_predicate_depth != 0,
in_bounded_ty: self.bounded_ty_depth != 0,
in_generics_arg: self.generic_args_depth != 0,
lifetime_elision_impossible: self.lifetime_elision_impossible,
});
}
}
@ -592,11 +597,44 @@ where
self.generic_args_depth -= 1;
}
fn visit_fn_decl(&mut self, fd: &'tcx FnDecl<'tcx>) -> Self::Result {
self.lifetime_elision_impossible = !is_candidate_for_elision(fd);
walk_fn_decl(self, fd);
self.lifetime_elision_impossible = false;
}
fn nested_visit_map(&mut self) -> Self::Map {
self.cx.tcx.hir()
}
}
/// Check if `fd` supports function elision with an anonymous (or elided) lifetime,
/// and has a lifetime somewhere in its output type.
fn is_candidate_for_elision(fd: &FnDecl<'_>) -> bool {
struct V;
impl Visitor<'_> for V {
type Result = ControlFlow<bool>;
fn visit_lifetime(&mut self, lifetime: &Lifetime) -> Self::Result {
ControlFlow::Break(lifetime.is_elided() || lifetime.is_anonymous())
}
}
if fd.lifetime_elision_allowed
&& let Return(ret_ty) = fd.output
&& walk_ty(&mut V, ret_ty).is_break()
{
// The first encountered input lifetime will either be one on `self`, or will be the only lifetime.
fd.inputs
.iter()
.find_map(|ty| walk_ty(&mut V, ty).break_value())
.unwrap()
} else {
false
}
}
fn report_extra_lifetimes<'tcx>(cx: &LateContext<'tcx>, func: &'tcx FnDecl<'_>, generics: &'tcx Generics<'_>) {
let mut checker = LifetimeChecker::<hir_nested_filter::None>::new(cx, generics);
@ -662,6 +700,7 @@ fn report_elidable_impl_lifetimes<'tcx>(
Usage {
lifetime,
in_where_predicate: false,
lifetime_elision_impossible: false,
..
},
] = usages.as_slice()