Skip to content

Commit e7421cc

Browse files
fix(elidable_lifetime_names): avoid overlapping spans in suggestions (#15667)
Fixes #14157 Fixes #15666 changelog: [`elidable_lifetime_names`]: avoid overlapping spans in suggestions r? clippy
2 parents 1810347 + 25881bf commit e7421cc

File tree

7 files changed

+416
-31
lines changed

7 files changed

+416
-31
lines changed

clippy_lints/src/lifetimes.rs

Lines changed: 79 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -745,7 +745,7 @@ fn report_elidable_impl_lifetimes<'tcx>(
745745
impl_: &'tcx Impl<'_>,
746746
map: &FxIndexMap<LocalDefId, Vec<Usage>>,
747747
) {
748-
let single_usages = map
748+
let (elidable_lts, usages): (Vec<_>, Vec<_>) = map
749749
.iter()
750750
.filter_map(|(def_id, usages)| {
751751
if let [
@@ -762,14 +762,12 @@ fn report_elidable_impl_lifetimes<'tcx>(
762762
None
763763
}
764764
})
765-
.collect::<Vec<_>>();
765+
.unzip();
766766

767-
if single_usages.is_empty() {
767+
if elidable_lts.is_empty() {
768768
return;
769769
}
770770

771-
let (elidable_lts, usages): (Vec<_>, Vec<_>) = single_usages.into_iter().unzip();
772-
773771
report_elidable_lifetimes(cx, impl_.generics, &elidable_lts, &usages, true);
774772
}
775773

@@ -795,9 +793,7 @@ fn report_elidable_lifetimes(
795793
// In principle, the result of the call to `Node::ident` could be `unwrap`ped, as `DefId` should refer to a
796794
// `Node::GenericParam`.
797795
.filter_map(|&def_id| cx.tcx.hir_node_by_def_id(def_id).ident())
798-
.map(|ident| ident.to_string())
799-
.collect::<Vec<_>>()
800-
.join(", ");
796+
.format(", ");
801797

802798
let elidable_usages: Vec<ElidableUsage> = usages
803799
.iter()
@@ -860,36 +856,89 @@ fn elision_suggestions(
860856
.filter(|param| !param.is_elided_lifetime() && !param.is_impl_trait())
861857
.collect::<Vec<_>>();
862858

863-
let mut suggestions = if elidable_lts.len() == explicit_params.len() {
859+
if !elidable_lts
860+
.iter()
861+
.all(|lt| explicit_params.iter().any(|param| param.def_id == *lt))
862+
{
863+
return None;
864+
}
865+
866+
let mut suggestions = if elidable_lts.is_empty() {
867+
vec![]
868+
} else if elidable_lts.len() == explicit_params.len() {
864869
// if all the params are elided remove the whole generic block
865870
//
866871
// fn x<'a>() {}
867872
// ^^^^
868873
vec![(generics.span, String::new())]
869874
} else {
870-
elidable_lts
871-
.iter()
872-
.map(|&id| {
873-
let pos = explicit_params.iter().position(|param| param.def_id == id)?;
874-
let param = explicit_params.get(pos)?;
875-
876-
let span = if let Some(next) = explicit_params.get(pos + 1) {
877-
// fn x<'prev, 'a, 'next>() {}
878-
// ^^^^
879-
param.span.until(next.span)
875+
match &explicit_params[..] {
876+
// no params, nothing to elide
877+
[] => unreachable!("handled by `elidable_lts.is_empty()`"),
878+
[param] => {
879+
if elidable_lts.contains(&param.def_id) {
880+
unreachable!("handled by `elidable_lts.len() == explicit_params.len()`")
880881
} else {
881-
// `pos` should be at least 1 here, because the param in position 0 would either have a `next`
882-
// param or would have taken the `elidable_lts.len() == explicit_params.len()` branch.
883-
let prev = explicit_params.get(pos - 1)?;
884-
885-
// fn x<'prev, 'a>() {}
886-
// ^^^^
887-
param.span.with_lo(prev.span.hi())
882+
unreachable!("handled by `elidable_lts.is_empty()`")
883+
}
884+
},
885+
[_, _, ..] => {
886+
// Given a list like `<'a, 'b, 'c, 'd, ..>`,
887+
//
888+
// If there is a cluster of elidable lifetimes at the beginning, say `'a` and `'b`, we should
889+
// suggest removing them _and_ the trailing comma. The span for that is `a.span.until(c.span)`:
890+
// <'a, 'b, 'c, 'd, ..> => <'a, 'b, 'c, 'd, ..>
891+
// ^^ ^^ ^^^^^^^^
892+
//
893+
// And since we know that `'c` isn't elidable--otherwise it would've been in the cluster--we can go
894+
// over all the lifetimes after it, and for each elidable one, add a suggestion spanning the
895+
// lifetime itself and the comma before, because each individual suggestion is guaranteed to leave
896+
// the list valid:
897+
// <.., 'c, 'd, 'e, 'f, 'g, ..> => <.., 'c, 'd, 'e, 'f, 'g, ..>
898+
// ^^ ^^ ^^ ^^^^ ^^^^^^^^
899+
//
900+
// In case there is no such starting cluster, we only need to do the second part of the algorithm:
901+
// <'a, 'b, 'c, 'd, 'e, 'f, 'g, ..> => <'a, 'b , 'c, 'd, 'e, 'f, 'g, ..>
902+
// ^^ ^^ ^^ ^^ ^^^^^^^^^ ^^^^^^^^
903+
904+
// Split off the starting cluster
905+
// TODO: use `slice::split_once` once stabilized (github.com/rust-lang/rust/issues/112811):
906+
// ```
907+
// let Some(split) = explicit_params.split_once(|param| !elidable_lts.contains(&param.def_id)) else {
908+
// // there were no lifetime param that couldn't be elided
909+
// unreachable!("handled by `elidable_lts.len() == explicit_params.len()`")
910+
// };
911+
// match split { /* .. */ }
912+
// ```
913+
let Some(split_pos) = explicit_params
914+
.iter()
915+
.position(|param| !elidable_lts.contains(&param.def_id))
916+
else {
917+
// there were no lifetime param that couldn't be elided
918+
unreachable!("handled by `elidable_lts.len() == explicit_params.len()`")
888919
};
889-
890-
Some((span, String::new()))
891-
})
892-
.collect::<Option<Vec<_>>>()?
920+
let split = explicit_params
921+
.split_at_checked(split_pos)
922+
.expect("got `split_pos` from `position` on the same Vec");
923+
924+
match split {
925+
([..], []) => unreachable!("handled by `elidable_lts.len() == explicit_params.len()`"),
926+
([], [_]) => unreachable!("handled by `explicit_params.len() == 1`"),
927+
(cluster, rest @ [rest_first, ..]) => {
928+
// the span for the cluster
929+
(cluster.first().map(|fw| fw.span.until(rest_first.span)).into_iter())
930+
// the span for the remaining lifetimes (calculations independent of the cluster)
931+
.chain(
932+
rest.array_windows()
933+
.filter(|[_, curr]| elidable_lts.contains(&curr.def_id))
934+
.map(|[prev, curr]| curr.span.with_lo(prev.span.hi())),
935+
)
936+
.map(|sp| (sp, String::new()))
937+
.collect()
938+
},
939+
}
940+
},
941+
}
893942
};
894943

895944
suggestions.extend(usages.iter().map(|&usage| {

tests/ui/crashes/ice-15666.fixed

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#![warn(clippy::elidable_lifetime_names)]
2+
3+
struct UnitVariantAccess<'a, 'b, 's>(&'a &'b &'s ());
4+
trait Trait<'de> {}
5+
impl<'de> Trait<'de> for UnitVariantAccess<'_, 'de, '_> {}
6+
//~^ elidable_lifetime_names

tests/ui/crashes/ice-15666.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#![warn(clippy::elidable_lifetime_names)]
2+
3+
struct UnitVariantAccess<'a, 'b, 's>(&'a &'b &'s ());
4+
trait Trait<'de> {}
5+
impl<'de, 'a, 's> Trait<'de> for UnitVariantAccess<'a, 'de, 's> {}
6+
//~^ elidable_lifetime_names

tests/ui/crashes/ice-15666.stderr

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
error: the following explicit lifetimes could be elided: 'a, 's
2+
--> tests/ui/crashes/ice-15666.rs:5:11
3+
|
4+
LL | impl<'de, 'a, 's> Trait<'de> for UnitVariantAccess<'a, 'de, 's> {}
5+
| ^^ ^^ ^^ ^^
6+
|
7+
= note: `-D clippy::elidable-lifetime-names` implied by `-D warnings`
8+
= help: to override `-D warnings` add `#[allow(clippy::elidable_lifetime_names)]`
9+
help: elide the lifetimes
10+
|
11+
LL - impl<'de, 'a, 's> Trait<'de> for UnitVariantAccess<'a, 'de, 's> {}
12+
LL + impl<'de> Trait<'de> for UnitVariantAccess<'_, 'de, '_> {}
13+
|
14+
15+
error: aborting due to 1 previous error
16+

tests/ui/elidable_lifetime_names.fixed

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,3 +192,85 @@ mod issue13923 {
192192
x.b
193193
}
194194
}
195+
196+
fn issue15666_original() {
197+
struct UnitVariantAccess<'a, 'b, 's>(&'a &'b &'s ());
198+
199+
trait Trait<'de> {}
200+
201+
//~v elidable_lifetime_names
202+
impl<'de> Trait<'de> for UnitVariantAccess<'_, 'de, '_> {}
203+
// ^^ ^^ ^^ ^^
204+
}
205+
206+
#[allow(clippy::upper_case_acronyms)]
207+
fn issue15666() {
208+
struct S1<'a>(&'a ());
209+
struct S2<'a, 'b>(&'a &'b ());
210+
struct S3<'a, 'b, 'c>(&'a &'b &'c ());
211+
212+
trait T {}
213+
trait TA<'a> {}
214+
trait TB<'b> {}
215+
trait TC<'c> {}
216+
trait TAB<'a, 'b> {}
217+
trait TAC<'a, 'c> {}
218+
trait TBC<'b, 'c> {}
219+
trait TABC<'a, 'b, 'c> {}
220+
221+
// 1 lifetime
222+
223+
impl<'a> TA<'a> for S1<'a> {}
224+
225+
//~v elidable_lifetime_names
226+
impl T for S1<'_> {}
227+
// ^^
228+
229+
// 2 lifetimes
230+
231+
impl<'a, 'b> TAB<'a, 'b> for S2<'a, 'b> {}
232+
233+
//~v elidable_lifetime_names
234+
impl<'a> TA<'a> for S2<'a, '_> {}
235+
// ^^
236+
237+
//~v elidable_lifetime_names
238+
impl<'b> TB<'b> for S2<'_, 'b> {}
239+
// ^^
240+
241+
//~v elidable_lifetime_names
242+
impl T for S2<'_, '_> {}
243+
// ^^ ^^
244+
245+
// 3 lifetimes
246+
247+
impl<'a, 'b, 'c> TABC<'a, 'b, 'c> for S3<'a, 'b, 'c> {}
248+
249+
//~v elidable_lifetime_names
250+
impl<'a, 'b> TAB<'a, 'b> for S3<'a, 'b, '_> {}
251+
// ^^
252+
253+
//~v elidable_lifetime_names
254+
impl<'a, 'c> TAC<'a, 'c> for S3<'a, '_, 'c> {}
255+
// ^^
256+
257+
//~v elidable_lifetime_names
258+
impl<'a> TA<'a> for S3<'a, '_, '_> {}
259+
// ^^ ^^
260+
261+
//~v elidable_lifetime_names
262+
impl<'b, 'c> TBC<'b, 'c> for S3<'_, 'b, 'c> {}
263+
// ^^
264+
265+
//~v elidable_lifetime_names
266+
impl<'b> TB<'b> for S3<'_, 'b, '_> {}
267+
// ^^ ^^
268+
269+
//~v elidable_lifetime_names
270+
impl<'c> TC<'c> for S3<'_, '_, 'c> {}
271+
// ^^ ^^
272+
273+
//~v elidable_lifetime_names
274+
impl T for S3<'_, '_, '_> {}
275+
// ^^ ^^ ^^
276+
}

tests/ui/elidable_lifetime_names.rs

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,3 +192,85 @@ mod issue13923 {
192192
x.b
193193
}
194194
}
195+
196+
fn issue15666_original() {
197+
struct UnitVariantAccess<'a, 'b, 's>(&'a &'b &'s ());
198+
199+
trait Trait<'de> {}
200+
201+
//~v elidable_lifetime_names
202+
impl<'de, 'a, 's> Trait<'de> for UnitVariantAccess<'a, 'de, 's> {}
203+
// ^^ ^^ ^^ ^^
204+
}
205+
206+
#[allow(clippy::upper_case_acronyms)]
207+
fn issue15666() {
208+
struct S1<'a>(&'a ());
209+
struct S2<'a, 'b>(&'a &'b ());
210+
struct S3<'a, 'b, 'c>(&'a &'b &'c ());
211+
212+
trait T {}
213+
trait TA<'a> {}
214+
trait TB<'b> {}
215+
trait TC<'c> {}
216+
trait TAB<'a, 'b> {}
217+
trait TAC<'a, 'c> {}
218+
trait TBC<'b, 'c> {}
219+
trait TABC<'a, 'b, 'c> {}
220+
221+
// 1 lifetime
222+
223+
impl<'a> TA<'a> for S1<'a> {}
224+
225+
//~v elidable_lifetime_names
226+
impl<'a> T for S1<'a> {}
227+
// ^^
228+
229+
// 2 lifetimes
230+
231+
impl<'a, 'b> TAB<'a, 'b> for S2<'a, 'b> {}
232+
233+
//~v elidable_lifetime_names
234+
impl<'a, 'b> TA<'a> for S2<'a, 'b> {}
235+
// ^^
236+
237+
//~v elidable_lifetime_names
238+
impl<'a, 'b> TB<'b> for S2<'a, 'b> {}
239+
// ^^
240+
241+
//~v elidable_lifetime_names
242+
impl<'a, 'b> T for S2<'a, 'b> {}
243+
// ^^ ^^
244+
245+
// 3 lifetimes
246+
247+
impl<'a, 'b, 'c> TABC<'a, 'b, 'c> for S3<'a, 'b, 'c> {}
248+
249+
//~v elidable_lifetime_names
250+
impl<'a, 'b, 'c> TAB<'a, 'b> for S3<'a, 'b, 'c> {}
251+
// ^^
252+
253+
//~v elidable_lifetime_names
254+
impl<'a, 'b, 'c> TAC<'a, 'c> for S3<'a, 'b, 'c> {}
255+
// ^^
256+
257+
//~v elidable_lifetime_names
258+
impl<'a, 'b, 'c> TA<'a> for S3<'a, 'b, 'c> {}
259+
// ^^ ^^
260+
261+
//~v elidable_lifetime_names
262+
impl<'a, 'b, 'c> TBC<'b, 'c> for S3<'a, 'b, 'c> {}
263+
// ^^
264+
265+
//~v elidable_lifetime_names
266+
impl<'a, 'b, 'c> TB<'b> for S3<'a, 'b, 'c> {}
267+
// ^^ ^^
268+
269+
//~v elidable_lifetime_names
270+
impl<'a, 'b, 'c> TC<'c> for S3<'a, 'b, 'c> {}
271+
// ^^ ^^
272+
273+
//~v elidable_lifetime_names
274+
impl<'a, 'b, 'c> T for S3<'a, 'b, 'c> {}
275+
// ^^ ^^ ^^
276+
}

0 commit comments

Comments
 (0)