Skip to content

Commit c686ffd

Browse files
committed
Do not propose to elide lifetimes if this causes an ambiguity
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.
1 parent 034f3d2 commit c686ffd

File tree

4 files changed

+261
-1
lines changed

4 files changed

+261
-1
lines changed

clippy_lints/src/lifetimes.rs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -482,11 +482,13 @@ fn has_where_lifetimes<'tcx>(cx: &LateContext<'tcx>, generics: &'tcx Generics<'_
482482
false
483483
}
484484

485+
#[allow(clippy::struct_excessive_bools)]
485486
struct Usage {
486487
lifetime: Lifetime,
487488
in_where_predicate: bool,
488489
in_bounded_ty: bool,
489490
in_generics_arg: bool,
491+
lifetime_elision_impossible: bool,
490492
}
491493

492494
struct LifetimeChecker<'cx, 'tcx, F> {
@@ -495,6 +497,7 @@ struct LifetimeChecker<'cx, 'tcx, F> {
495497
where_predicate_depth: usize,
496498
bounded_ty_depth: usize,
497499
generic_args_depth: usize,
500+
lifetime_elision_impossible: bool,
498501
phantom: std::marker::PhantomData<F>,
499502
}
500503

@@ -519,6 +522,7 @@ where
519522
where_predicate_depth: 0,
520523
bounded_ty_depth: 0,
521524
generic_args_depth: 0,
525+
lifetime_elision_impossible: false,
522526
phantom: std::marker::PhantomData,
523527
}
524528
}
@@ -560,6 +564,7 @@ where
560564
in_where_predicate: self.where_predicate_depth != 0,
561565
in_bounded_ty: self.bounded_ty_depth != 0,
562566
in_generics_arg: self.generic_args_depth != 0,
567+
lifetime_elision_impossible: self.lifetime_elision_impossible,
563568
});
564569
}
565570
}
@@ -586,11 +591,44 @@ where
586591
self.generic_args_depth -= 1;
587592
}
588593

594+
fn visit_fn_decl(&mut self, fd: &'tcx FnDecl<'tcx>) -> Self::Result {
595+
self.lifetime_elision_impossible = !is_candidate_for_elision(fd);
596+
walk_fn_decl(self, fd);
597+
self.lifetime_elision_impossible = false;
598+
}
599+
589600
fn nested_visit_map(&mut self) -> Self::Map {
590601
self.cx.tcx.hir()
591602
}
592603
}
593604

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

@@ -656,6 +694,7 @@ fn report_elidable_impl_lifetimes<'tcx>(
656694
Usage {
657695
lifetime,
658696
in_where_predicate: false,
697+
lifetime_elision_impossible: false,
659698
..
660699
},
661700
] = usages.as_slice()

tests/ui/needless_lifetimes.fixed

Lines changed: 81 additions & 0 deletions
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

Lines changed: 81 additions & 0 deletions
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

Lines changed: 60 additions & 1 deletion
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)