fix(elidable_lifetime_names): avoid overlapping spans in suggestions

This commit is contained in:
Ada Alakbarova 2025-09-12 18:56:52 +02:00
parent 382c964f3e
commit 25881bfc34
No known key found for this signature in database
7 changed files with 410 additions and 21 deletions

View file

@ -856,36 +856,89 @@ fn elision_suggestions(
.filter(|param| !param.is_elided_lifetime() && !param.is_impl_trait())
.collect::<Vec<_>>();
let mut suggestions = if elidable_lts.len() == explicit_params.len() {
if !elidable_lts
.iter()
.all(|lt| explicit_params.iter().any(|param| param.def_id == *lt))
{
return None;
}
let mut suggestions = if elidable_lts.is_empty() {
vec![]
} else if elidable_lts.len() == explicit_params.len() {
// if all the params are elided remove the whole generic block
//
// fn x<'a>() {}
// ^^^^
vec![(generics.span, String::new())]
} else {
elidable_lts
.iter()
.map(|&id| {
let pos = explicit_params.iter().position(|param| param.def_id == id)?;
let param = explicit_params.get(pos)?;
let span = if let Some(next) = explicit_params.get(pos + 1) {
// fn x<'prev, 'a, 'next>() {}
// ^^^^
param.span.until(next.span)
match &explicit_params[..] {
// no params, nothing to elide
[] => unreachable!("handled by `elidable_lts.is_empty()`"),
[param] => {
if elidable_lts.contains(&param.def_id) {
unreachable!("handled by `elidable_lts.len() == explicit_params.len()`")
} else {
// `pos` should be at least 1 here, because the param in position 0 would either have a `next`
// param or would have taken the `elidable_lts.len() == explicit_params.len()` branch.
let prev = explicit_params.get(pos - 1)?;
unreachable!("handled by `elidable_lts.is_empty()`")
}
},
[_, _, ..] => {
// Given a list like `<'a, 'b, 'c, 'd, ..>`,
//
// If there is a cluster of elidable lifetimes at the beginning, say `'a` and `'b`, we should
// suggest removing them _and_ the trailing comma. The span for that is `a.span.until(c.span)`:
// <'a, 'b, 'c, 'd, ..> => <'a, 'b, 'c, 'd, ..>
// ^^ ^^ ^^^^^^^^
//
// And since we know that `'c` isn't elidable--otherwise it would've been in the cluster--we can go
// over all the lifetimes after it, and for each elidable one, add a suggestion spanning the
// lifetime itself and the comma before, because each individual suggestion is guaranteed to leave
// the list valid:
// <.., 'c, 'd, 'e, 'f, 'g, ..> => <.., 'c, 'd, 'e, 'f, 'g, ..>
// ^^ ^^ ^^ ^^^^ ^^^^^^^^
//
// In case there is no such starting cluster, we only need to do the second part of the algorithm:
// <'a, 'b, 'c, 'd, 'e, 'f, 'g, ..> => <'a, 'b , 'c, 'd, 'e, 'f, 'g, ..>
// ^^ ^^ ^^ ^^ ^^^^^^^^^ ^^^^^^^^
// fn x<'prev, 'a>() {}
// ^^^^
param.span.with_lo(prev.span.hi())
// Split off the starting cluster
// TODO: use `slice::split_once` once stabilized (github.com/rust-lang/rust/issues/112811):
// ```
// let Some(split) = explicit_params.split_once(|param| !elidable_lts.contains(&param.def_id)) else {
// // there were no lifetime param that couldn't be elided
// unreachable!("handled by `elidable_lts.len() == explicit_params.len()`")
// };
// match split { /* .. */ }
// ```
let Some(split_pos) = explicit_params
.iter()
.position(|param| !elidable_lts.contains(&param.def_id))
else {
// there were no lifetime param that couldn't be elided
unreachable!("handled by `elidable_lts.len() == explicit_params.len()`")
};
let split = explicit_params
.split_at_checked(split_pos)
.expect("got `split_pos` from `position` on the same Vec");
Some((span, String::new()))
})
.collect::<Option<Vec<_>>>()?
match split {
([..], []) => unreachable!("handled by `elidable_lts.len() == explicit_params.len()`"),
([], [_]) => unreachable!("handled by `explicit_params.len() == 1`"),
(cluster, rest @ [rest_first, ..]) => {
// the span for the cluster
(cluster.first().map(|fw| fw.span.until(rest_first.span)).into_iter())
// the span for the remaining lifetimes (calculations independent of the cluster)
.chain(
rest.array_windows()
.filter(|[_, curr]| elidable_lts.contains(&curr.def_id))
.map(|[prev, curr]| curr.span.with_lo(prev.span.hi())),
)
.map(|sp| (sp, String::new()))
.collect()
},
}
},
}
};
suggestions.extend(usages.iter().map(|&usage| {

View file

@ -0,0 +1,6 @@
#![warn(clippy::elidable_lifetime_names)]
struct UnitVariantAccess<'a, 'b, 's>(&'a &'b &'s ());
trait Trait<'de> {}
impl<'de> Trait<'de> for UnitVariantAccess<'_, 'de, '_> {}
//~^ elidable_lifetime_names

View file

@ -0,0 +1,6 @@
#![warn(clippy::elidable_lifetime_names)]
struct UnitVariantAccess<'a, 'b, 's>(&'a &'b &'s ());
trait Trait<'de> {}
impl<'de, 'a, 's> Trait<'de> for UnitVariantAccess<'a, 'de, 's> {}
//~^ elidable_lifetime_names

View file

@ -0,0 +1,16 @@
error: the following explicit lifetimes could be elided: 'a, 's
--> tests/ui/crashes/ice-15666.rs:5:11
|
LL | impl<'de, 'a, 's> Trait<'de> for UnitVariantAccess<'a, 'de, 's> {}
| ^^ ^^ ^^ ^^
|
= note: `-D clippy::elidable-lifetime-names` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::elidable_lifetime_names)]`
help: elide the lifetimes
|
LL - impl<'de, 'a, 's> Trait<'de> for UnitVariantAccess<'a, 'de, 's> {}
LL + impl<'de> Trait<'de> for UnitVariantAccess<'_, 'de, '_> {}
|
error: aborting due to 1 previous error

View file

@ -192,3 +192,85 @@ mod issue13923 {
x.b
}
}
fn issue15666_original() {
struct UnitVariantAccess<'a, 'b, 's>(&'a &'b &'s ());
trait Trait<'de> {}
//~v elidable_lifetime_names
impl<'de> Trait<'de> for UnitVariantAccess<'_, 'de, '_> {}
// ^^ ^^ ^^ ^^
}
#[allow(clippy::upper_case_acronyms)]
fn issue15666() {
struct S1<'a>(&'a ());
struct S2<'a, 'b>(&'a &'b ());
struct S3<'a, 'b, 'c>(&'a &'b &'c ());
trait T {}
trait TA<'a> {}
trait TB<'b> {}
trait TC<'c> {}
trait TAB<'a, 'b> {}
trait TAC<'a, 'c> {}
trait TBC<'b, 'c> {}
trait TABC<'a, 'b, 'c> {}
// 1 lifetime
impl<'a> TA<'a> for S1<'a> {}
//~v elidable_lifetime_names
impl T for S1<'_> {}
// ^^
// 2 lifetimes
impl<'a, 'b> TAB<'a, 'b> for S2<'a, 'b> {}
//~v elidable_lifetime_names
impl<'a> TA<'a> for S2<'a, '_> {}
// ^^
//~v elidable_lifetime_names
impl<'b> TB<'b> for S2<'_, 'b> {}
// ^^
//~v elidable_lifetime_names
impl T for S2<'_, '_> {}
// ^^ ^^
// 3 lifetimes
impl<'a, 'b, 'c> TABC<'a, 'b, 'c> for S3<'a, 'b, 'c> {}
//~v elidable_lifetime_names
impl<'a, 'b> TAB<'a, 'b> for S3<'a, 'b, '_> {}
// ^^
//~v elidable_lifetime_names
impl<'a, 'c> TAC<'a, 'c> for S3<'a, '_, 'c> {}
// ^^
//~v elidable_lifetime_names
impl<'a> TA<'a> for S3<'a, '_, '_> {}
// ^^ ^^
//~v elidable_lifetime_names
impl<'b, 'c> TBC<'b, 'c> for S3<'_, 'b, 'c> {}
// ^^
//~v elidable_lifetime_names
impl<'b> TB<'b> for S3<'_, 'b, '_> {}
// ^^ ^^
//~v elidable_lifetime_names
impl<'c> TC<'c> for S3<'_, '_, 'c> {}
// ^^ ^^
//~v elidable_lifetime_names
impl T for S3<'_, '_, '_> {}
// ^^ ^^ ^^
}

View file

@ -192,3 +192,85 @@ mod issue13923 {
x.b
}
}
fn issue15666_original() {
struct UnitVariantAccess<'a, 'b, 's>(&'a &'b &'s ());
trait Trait<'de> {}
//~v elidable_lifetime_names
impl<'de, 'a, 's> Trait<'de> for UnitVariantAccess<'a, 'de, 's> {}
// ^^ ^^ ^^ ^^
}
#[allow(clippy::upper_case_acronyms)]
fn issue15666() {
struct S1<'a>(&'a ());
struct S2<'a, 'b>(&'a &'b ());
struct S3<'a, 'b, 'c>(&'a &'b &'c ());
trait T {}
trait TA<'a> {}
trait TB<'b> {}
trait TC<'c> {}
trait TAB<'a, 'b> {}
trait TAC<'a, 'c> {}
trait TBC<'b, 'c> {}
trait TABC<'a, 'b, 'c> {}
// 1 lifetime
impl<'a> TA<'a> for S1<'a> {}
//~v elidable_lifetime_names
impl<'a> T for S1<'a> {}
// ^^
// 2 lifetimes
impl<'a, 'b> TAB<'a, 'b> for S2<'a, 'b> {}
//~v elidable_lifetime_names
impl<'a, 'b> TA<'a> for S2<'a, 'b> {}
// ^^
//~v elidable_lifetime_names
impl<'a, 'b> TB<'b> for S2<'a, 'b> {}
// ^^
//~v elidable_lifetime_names
impl<'a, 'b> T for S2<'a, 'b> {}
// ^^ ^^
// 3 lifetimes
impl<'a, 'b, 'c> TABC<'a, 'b, 'c> for S3<'a, 'b, 'c> {}
//~v elidable_lifetime_names
impl<'a, 'b, 'c> TAB<'a, 'b> for S3<'a, 'b, 'c> {}
// ^^
//~v elidable_lifetime_names
impl<'a, 'b, 'c> TAC<'a, 'c> for S3<'a, 'b, 'c> {}
// ^^
//~v elidable_lifetime_names
impl<'a, 'b, 'c> TA<'a> for S3<'a, 'b, 'c> {}
// ^^ ^^
//~v elidable_lifetime_names
impl<'a, 'b, 'c> TBC<'b, 'c> for S3<'a, 'b, 'c> {}
// ^^
//~v elidable_lifetime_names
impl<'a, 'b, 'c> TB<'b> for S3<'a, 'b, 'c> {}
// ^^ ^^
//~v elidable_lifetime_names
impl<'a, 'b, 'c> TC<'c> for S3<'a, 'b, 'c> {}
// ^^ ^^
//~v elidable_lifetime_names
impl<'a, 'b, 'c> T for S3<'a, 'b, 'c> {}
// ^^ ^^ ^^
}

View file

@ -158,5 +158,149 @@ LL | o: &'t str,
LL ~ ) -> Content<'t, '_> {
|
error: aborting due to 12 previous errors
error: the following explicit lifetimes could be elided: 'a, 's
--> tests/ui/elidable_lifetime_names.rs:202:15
|
LL | impl<'de, 'a, 's> Trait<'de> for UnitVariantAccess<'a, 'de, 's> {}
| ^^ ^^ ^^ ^^
|
help: elide the lifetimes
|
LL - impl<'de, 'a, 's> Trait<'de> for UnitVariantAccess<'a, 'de, 's> {}
LL + impl<'de> Trait<'de> for UnitVariantAccess<'_, 'de, '_> {}
|
error: the following explicit lifetimes could be elided: 'a
--> tests/ui/elidable_lifetime_names.rs:226:10
|
LL | impl<'a> T for S1<'a> {}
| ^^ ^^
|
help: elide the lifetimes
|
LL - impl<'a> T for S1<'a> {}
LL + impl T for S1<'_> {}
|
error: the following explicit lifetimes could be elided: 'b
--> tests/ui/elidable_lifetime_names.rs:234:14
|
LL | impl<'a, 'b> TA<'a> for S2<'a, 'b> {}
| ^^ ^^
|
help: elide the lifetimes
|
LL - impl<'a, 'b> TA<'a> for S2<'a, 'b> {}
LL + impl<'a> TA<'a> for S2<'a, '_> {}
|
error: the following explicit lifetimes could be elided: 'a
--> tests/ui/elidable_lifetime_names.rs:238:10
|
LL | impl<'a, 'b> TB<'b> for S2<'a, 'b> {}
| ^^ ^^
|
help: elide the lifetimes
|
LL - impl<'a, 'b> TB<'b> for S2<'a, 'b> {}
LL + impl<'b> TB<'b> for S2<'_, 'b> {}
|
error: the following explicit lifetimes could be elided: 'a, 'b
--> tests/ui/elidable_lifetime_names.rs:242:10
|
LL | impl<'a, 'b> T for S2<'a, 'b> {}
| ^^ ^^ ^^ ^^
|
help: elide the lifetimes
|
LL - impl<'a, 'b> T for S2<'a, 'b> {}
LL + impl T for S2<'_, '_> {}
|
error: the following explicit lifetimes could be elided: 'c
--> tests/ui/elidable_lifetime_names.rs:250:18
|
LL | impl<'a, 'b, 'c> TAB<'a, 'b> for S3<'a, 'b, 'c> {}
| ^^ ^^
|
help: elide the lifetimes
|
LL - impl<'a, 'b, 'c> TAB<'a, 'b> for S3<'a, 'b, 'c> {}
LL + impl<'a, 'b> TAB<'a, 'b> for S3<'a, 'b, '_> {}
|
error: the following explicit lifetimes could be elided: 'b
--> tests/ui/elidable_lifetime_names.rs:254:14
|
LL | impl<'a, 'b, 'c> TAC<'a, 'c> for S3<'a, 'b, 'c> {}
| ^^ ^^
|
help: elide the lifetimes
|
LL - impl<'a, 'b, 'c> TAC<'a, 'c> for S3<'a, 'b, 'c> {}
LL + impl<'a, 'c> TAC<'a, 'c> for S3<'a, '_, 'c> {}
|
error: the following explicit lifetimes could be elided: 'b, 'c
--> tests/ui/elidable_lifetime_names.rs:258:14
|
LL | impl<'a, 'b, 'c> TA<'a> for S3<'a, 'b, 'c> {}
| ^^ ^^ ^^ ^^
|
help: elide the lifetimes
|
LL - impl<'a, 'b, 'c> TA<'a> for S3<'a, 'b, 'c> {}
LL + impl<'a> TA<'a> for S3<'a, '_, '_> {}
|
error: the following explicit lifetimes could be elided: 'a
--> tests/ui/elidable_lifetime_names.rs:262:10
|
LL | impl<'a, 'b, 'c> TBC<'b, 'c> for S3<'a, 'b, 'c> {}
| ^^ ^^
|
help: elide the lifetimes
|
LL - impl<'a, 'b, 'c> TBC<'b, 'c> for S3<'a, 'b, 'c> {}
LL + impl<'b, 'c> TBC<'b, 'c> for S3<'_, 'b, 'c> {}
|
error: the following explicit lifetimes could be elided: 'a, 'c
--> tests/ui/elidable_lifetime_names.rs:266:10
|
LL | impl<'a, 'b, 'c> TB<'b> for S3<'a, 'b, 'c> {}
| ^^ ^^ ^^ ^^
|
help: elide the lifetimes
|
LL - impl<'a, 'b, 'c> TB<'b> for S3<'a, 'b, 'c> {}
LL + impl<'b> TB<'b> for S3<'_, 'b, '_> {}
|
error: the following explicit lifetimes could be elided: 'a, 'b
--> tests/ui/elidable_lifetime_names.rs:270:10
|
LL | impl<'a, 'b, 'c> TC<'c> for S3<'a, 'b, 'c> {}
| ^^ ^^ ^^ ^^
|
help: elide the lifetimes
|
LL - impl<'a, 'b, 'c> TC<'c> for S3<'a, 'b, 'c> {}
LL + impl<'c> TC<'c> for S3<'_, '_, 'c> {}
|
error: the following explicit lifetimes could be elided: 'a, 'b, 'c
--> tests/ui/elidable_lifetime_names.rs:274:10
|
LL | impl<'a, 'b, 'c> T for S3<'a, 'b, 'c> {}
| ^^ ^^ ^^ ^^ ^^ ^^
|
help: elide the lifetimes
|
LL - impl<'a, 'b, 'c> T for S3<'a, 'b, 'c> {}
LL + impl T for S3<'_, '_, '_> {}
|
error: aborting due to 24 previous errors