Skip to content

Commit 5c2601a

Browse files
authored
Do not propose to elide lifetimes if this causes an ambiguity (rust-lang#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 rust-lang#13923 changelog: [`needless_lifetimes`]: remove false positives by checking that lifetimes can indeed be elided
2 parents 197d58d + c686ffd commit 5c2601a

File tree

4 files changed

+261
-1
lines changed

4 files changed

+261
-1
lines changed

clippy_lints/src/lifetimes.rs

+39
Original file line numberDiff line numberDiff line change
@@ -488,11 +488,13 @@ fn has_where_lifetimes<'tcx>(cx: &LateContext<'tcx>, generics: &'tcx Generics<'_
488488
false
489489
}
490490

491+
#[allow(clippy::struct_excessive_bools)]
491492
struct Usage {
492493
lifetime: Lifetime,
493494
in_where_predicate: bool,
494495
in_bounded_ty: bool,
495496
in_generics_arg: bool,
497+
lifetime_elision_impossible: bool,
496498
}
497499

498500
struct LifetimeChecker<'cx, 'tcx, F> {
@@ -501,6 +503,7 @@ struct LifetimeChecker<'cx, 'tcx, F> {
501503
where_predicate_depth: usize,
502504
bounded_ty_depth: usize,
503505
generic_args_depth: usize,
506+
lifetime_elision_impossible: bool,
504507
phantom: std::marker::PhantomData<F>,
505508
}
506509

@@ -525,6 +528,7 @@ where
525528
where_predicate_depth: 0,
526529
bounded_ty_depth: 0,
527530
generic_args_depth: 0,
531+
lifetime_elision_impossible: false,
528532
phantom: std::marker::PhantomData,
529533
}
530534
}
@@ -566,6 +570,7 @@ where
566570
in_where_predicate: self.where_predicate_depth != 0,
567571
in_bounded_ty: self.bounded_ty_depth != 0,
568572
in_generics_arg: self.generic_args_depth != 0,
573+
lifetime_elision_impossible: self.lifetime_elision_impossible,
569574
});
570575
}
571576
}
@@ -592,11 +597,44 @@ where
592597
self.generic_args_depth -= 1;
593598
}
594599

600+
fn visit_fn_decl(&mut self, fd: &'tcx FnDecl<'tcx>) -> Self::Result {
601+
self.lifetime_elision_impossible = !is_candidate_for_elision(fd);
602+
walk_fn_decl(self, fd);
603+
self.lifetime_elision_impossible = false;
604+
}
605+
595606
fn nested_visit_map(&mut self) -> Self::Map {
596607
self.cx.tcx.hir()
597608
}
598609
}
599610

611+
/// Check if `fd` supports function elision with an anonymous (or elided) lifetime,
612+
/// and has a lifetime somewhere in its output type.
613+
fn is_candidate_for_elision(fd: &FnDecl<'_>) -> bool {
614+
struct V;
615+
616+
impl Visitor<'_> for V {
617+
type Result = ControlFlow<bool>;
618+
619+
fn visit_lifetime(&mut self, lifetime: &Lifetime) -> Self::Result {
620+
ControlFlow::Break(lifetime.is_elided() || lifetime.is_anonymous())
621+
}
622+
}
623+
624+
if fd.lifetime_elision_allowed
625+
&& let Return(ret_ty) = fd.output
626+
&& walk_ty(&mut V, ret_ty).is_break()
627+
{
628+
// The first encountered input lifetime will either be one on `self`, or will be the only lifetime.
629+
fd.inputs
630+
.iter()
631+
.find_map(|ty| walk_ty(&mut V, ty).break_value())
632+
.unwrap()
633+
} else {
634+
false
635+
}
636+
}
637+
600638
fn report_extra_lifetimes<'tcx>(cx: &LateContext<'tcx>, func: &'tcx FnDecl<'_>, generics: &'tcx Generics<'_>) {
601639
let mut checker = LifetimeChecker::<hir_nested_filter::None>::new(cx, generics);
602640

@@ -662,6 +700,7 @@ fn report_elidable_impl_lifetimes<'tcx>(
662700
Usage {
663701
lifetime,
664702
in_where_predicate: false,
703+
lifetime_elision_impossible: false,
665704
..
666705
},
667706
] = usages.as_slice()

tests/ui/needless_lifetimes.fixed

+81
Original file line numberDiff line numberDiff line change
@@ -576,4 +576,85 @@ mod issue13749bis {
576576
impl<'a, T: 'a> Generic<T> {}
577577
}
578578

579+
mod issue13923 {
580+
struct Py<'py> {
581+
data: &'py str,
582+
}
583+
584+
enum Content<'t, 'py> {
585+
Py(Py<'py>),
586+
T1(&'t str),
587+
T2(&'t str),
588+
}
589+
590+
enum ContentString<'t> {
591+
T1(&'t str),
592+
T2(&'t str),
593+
}
594+
595+
impl<'t, 'py> ContentString<'t> {
596+
// `'py` cannot be elided
597+
fn map_content1(self, f: impl FnOnce(&'t str) -> &'t str) -> Content<'t, 'py> {
598+
match self {
599+
Self::T1(content) => Content::T1(f(content)),
600+
Self::T2(content) => Content::T2(f(content)),
601+
}
602+
}
603+
}
604+
605+
impl<'t> ContentString<'t> {
606+
// `'py` can be elided because of `&self`
607+
fn map_content2(&self, f: impl FnOnce(&'t str) -> &'t str) -> Content<'t, '_> {
608+
match self {
609+
Self::T1(content) => Content::T1(f(content)),
610+
Self::T2(content) => Content::T2(f(content)),
611+
}
612+
}
613+
}
614+
615+
impl<'t> ContentString<'t> {
616+
// `'py` can be elided because of `&'_ self`
617+
fn map_content3(&'_ self, f: impl FnOnce(&'t str) -> &'t str) -> Content<'t, '_> {
618+
match self {
619+
Self::T1(content) => Content::T1(f(content)),
620+
Self::T2(content) => Content::T2(f(content)),
621+
}
622+
}
623+
}
624+
625+
impl<'t, 'py> ContentString<'t> {
626+
// `'py` should not be elided as the default lifetime, even if working, could be named as `'t`
627+
fn map_content4(self, f: impl FnOnce(&'t str) -> &'t str, o: &'t str) -> Content<'t, 'py> {
628+
match self {
629+
Self::T1(content) => Content::T1(f(content)),
630+
Self::T2(_) => Content::T2(o),
631+
}
632+
}
633+
}
634+
635+
impl<'t> ContentString<'t> {
636+
// `'py` can be elided because of `&Self`
637+
fn map_content5(
638+
self: std::pin::Pin<&Self>,
639+
f: impl FnOnce(&'t str) -> &'t str,
640+
o: &'t str,
641+
) -> Content<'t, '_> {
642+
match *self {
643+
Self::T1(content) => Content::T1(f(content)),
644+
Self::T2(_) => Content::T2(o),
645+
}
646+
}
647+
}
648+
649+
struct Cx<'a, 'b> {
650+
a: &'a u32,
651+
b: &'b u32,
652+
}
653+
654+
// `'c` cannot be elided because we have several input lifetimes
655+
fn one_explicit<'b>(x: Cx<'_, 'b>) -> &'b u32 {
656+
x.b
657+
}
658+
}
659+
579660
fn main() {}

tests/ui/needless_lifetimes.rs

+81
Original file line numberDiff line numberDiff line change
@@ -576,4 +576,85 @@ mod issue13749bis {
576576
impl<'a, T: 'a> Generic<T> {}
577577
}
578578

579+
mod issue13923 {
580+
struct Py<'py> {
581+
data: &'py str,
582+
}
583+
584+
enum Content<'t, 'py> {
585+
Py(Py<'py>),
586+
T1(&'t str),
587+
T2(&'t str),
588+
}
589+
590+
enum ContentString<'t> {
591+
T1(&'t str),
592+
T2(&'t str),
593+
}
594+
595+
impl<'t, 'py> ContentString<'t> {
596+
// `'py` cannot be elided
597+
fn map_content1(self, f: impl FnOnce(&'t str) -> &'t str) -> Content<'t, 'py> {
598+
match self {
599+
Self::T1(content) => Content::T1(f(content)),
600+
Self::T2(content) => Content::T2(f(content)),
601+
}
602+
}
603+
}
604+
605+
impl<'t, 'py> ContentString<'t> {
606+
// `'py` can be elided because of `&self`
607+
fn map_content2(&self, f: impl FnOnce(&'t str) -> &'t str) -> Content<'t, 'py> {
608+
match self {
609+
Self::T1(content) => Content::T1(f(content)),
610+
Self::T2(content) => Content::T2(f(content)),
611+
}
612+
}
613+
}
614+
615+
impl<'t, 'py> ContentString<'t> {
616+
// `'py` can be elided because of `&'_ self`
617+
fn map_content3(&'_ self, f: impl FnOnce(&'t str) -> &'t str) -> Content<'t, 'py> {
618+
match self {
619+
Self::T1(content) => Content::T1(f(content)),
620+
Self::T2(content) => Content::T2(f(content)),
621+
}
622+
}
623+
}
624+
625+
impl<'t, 'py> ContentString<'t> {
626+
// `'py` should not be elided as the default lifetime, even if working, could be named as `'t`
627+
fn map_content4(self, f: impl FnOnce(&'t str) -> &'t str, o: &'t str) -> Content<'t, 'py> {
628+
match self {
629+
Self::T1(content) => Content::T1(f(content)),
630+
Self::T2(_) => Content::T2(o),
631+
}
632+
}
633+
}
634+
635+
impl<'t, 'py> ContentString<'t> {
636+
// `'py` can be elided because of `&Self`
637+
fn map_content5(
638+
self: std::pin::Pin<&Self>,
639+
f: impl FnOnce(&'t str) -> &'t str,
640+
o: &'t str,
641+
) -> Content<'t, 'py> {
642+
match *self {
643+
Self::T1(content) => Content::T1(f(content)),
644+
Self::T2(_) => Content::T2(o),
645+
}
646+
}
647+
}
648+
649+
struct Cx<'a, 'b> {
650+
a: &'a u32,
651+
b: &'b u32,
652+
}
653+
654+
// `'c` cannot be elided because we have several input lifetimes
655+
fn one_explicit<'b>(x: Cx<'_, 'b>) -> &'b u32 {
656+
&x.b
657+
}
658+
}
659+
579660
fn main() {}

tests/ui/needless_lifetimes.stderr

+60-1
Original file line numberDiff line numberDiff line change
@@ -576,5 +576,64 @@ LL - fn one_input<'a>(x: &'a u8) -> &'a u8 {
576576
LL + fn one_input(x: &u8) -> &u8 {
577577
|
578578

579-
error: aborting due to 48 previous errors
579+
error: the following explicit lifetimes could be elided: 'py
580+
--> tests/ui/needless_lifetimes.rs:605:14
581+
|
582+
LL | impl<'t, 'py> ContentString<'t> {
583+
| ^^^
584+
LL | // `'py` can be elided because of `&self`
585+
LL | fn map_content2(&self, f: impl FnOnce(&'t str) -> &'t str) -> Content<'t, 'py> {
586+
| ^^^
587+
|
588+
help: elide the lifetimes
589+
|
590+
LL ~ impl<'t> ContentString<'t> {
591+
LL | // `'py` can be elided because of `&self`
592+
LL ~ fn map_content2(&self, f: impl FnOnce(&'t str) -> &'t str) -> Content<'t, '_> {
593+
|
594+
595+
error: the following explicit lifetimes could be elided: 'py
596+
--> tests/ui/needless_lifetimes.rs:615:14
597+
|
598+
LL | impl<'t, 'py> ContentString<'t> {
599+
| ^^^
600+
LL | // `'py` can be elided because of `&'_ self`
601+
LL | fn map_content3(&'_ self, f: impl FnOnce(&'t str) -> &'t str) -> Content<'t, 'py> {
602+
| ^^^
603+
|
604+
help: elide the lifetimes
605+
|
606+
LL ~ impl<'t> ContentString<'t> {
607+
LL | // `'py` can be elided because of `&'_ self`
608+
LL ~ fn map_content3(&'_ self, f: impl FnOnce(&'t str) -> &'t str) -> Content<'t, '_> {
609+
|
610+
611+
error: the following explicit lifetimes could be elided: 'py
612+
--> tests/ui/needless_lifetimes.rs:635:14
613+
|
614+
LL | impl<'t, 'py> ContentString<'t> {
615+
| ^^^
616+
...
617+
LL | ) -> Content<'t, 'py> {
618+
| ^^^
619+
|
620+
help: elide the lifetimes
621+
|
622+
LL ~ impl<'t> ContentString<'t> {
623+
LL | // `'py` can be elided because of `&Self`
624+
...
625+
LL | o: &'t str,
626+
LL ~ ) -> Content<'t, '_> {
627+
|
628+
629+
error: this expression creates a reference which is immediately dereferenced by the compiler
630+
--> tests/ui/needless_lifetimes.rs:656:9
631+
|
632+
LL | &x.b
633+
| ^^^^ help: change this to: `x.b`
634+
|
635+
= note: `-D clippy::needless-borrow` implied by `-D warnings`
636+
= help: to override `-D warnings` add `#[allow(clippy::needless_borrow)]`
637+
638+
error: aborting due to 52 previous errors
580639

0 commit comments

Comments
 (0)