Skip elidable_lifetime_names lint for proc-macro generated code (#16402)

When linting code generated by proc macros (like `derivative`), the
`elidable_lifetime_names` lint can produce fix suggestions with
overlapping spans. This causes `cargo clippy --fix` to fail with "cannot
replace slice of data that was already replaced".

This change adds `is_from_proc_macro` checks to the three lint entry
points (`check_item`, `check_impl_item`, `check_trait_item`) to skip
linting proc-macro generated code entirely.

Fixes rust-lang/rust-clippy#16316

---

changelog: [`elidable_lifetime_names`]: skip linting proc-macro
generated code to avoid broken fix suggestions
This commit is contained in:
Samuel Tardieu 2026-01-17 15:20:59 +00:00 committed by GitHub
commit d25a26df71
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 118 additions and 16 deletions

View file

@ -1,7 +1,7 @@
use clippy_config::Conf;
use clippy_utils::diagnostics::{span_lint, span_lint_and_then};
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::trait_ref_of_method;
use clippy_utils::{is_from_proc_macro, trait_ref_of_method};
use itertools::Itertools;
use rustc_ast::visit::{try_visit, walk_list};
use rustc_data_structures::fx::{FxHashSet, FxIndexMap, FxIndexSet};
@ -149,9 +149,12 @@ impl<'tcx> LateLintPass<'tcx> for Lifetimes {
..
} = item.kind
{
check_fn_inner(cx, sig, Some(id), None, generics, item.span, true, self.msrv);
check_fn_inner(cx, sig, Some(id), None, generics, item.span, true, self.msrv, || {
is_from_proc_macro(cx, item)
});
} else if let ItemKind::Impl(impl_) = &item.kind
&& !item.span.from_expansion()
&& !is_from_proc_macro(cx, item)
{
report_extra_impl_lifetimes(cx, impl_);
}
@ -169,6 +172,7 @@ impl<'tcx> LateLintPass<'tcx> for Lifetimes {
item.span,
report_extra_lifetimes,
self.msrv,
|| is_from_proc_macro(cx, item),
);
}
}
@ -179,7 +183,17 @@ impl<'tcx> LateLintPass<'tcx> for Lifetimes {
TraitFn::Required(sig) => (None, Some(sig)),
TraitFn::Provided(id) => (Some(id), None),
};
check_fn_inner(cx, sig, body, trait_sig, item.generics, item.span, true, self.msrv);
check_fn_inner(
cx,
sig,
body,
trait_sig,
item.generics,
item.span,
true,
self.msrv,
|| is_from_proc_macro(cx, item),
);
}
}
}
@ -194,6 +208,7 @@ fn check_fn_inner<'tcx>(
span: Span,
report_extra_lifetimes: bool,
msrv: Msrv,
is_from_proc_macro: impl FnOnce() -> bool,
) {
if span.in_external_macro(cx.sess().source_map()) || has_where_lifetimes(cx, generics) {
return;
@ -245,10 +260,19 @@ fn check_fn_inner<'tcx>(
}
}
if let Some((elidable_lts, usages)) = could_use_elision(cx, sig.decl, body, trait_sig, generics.params, msrv) {
if usages.iter().any(|usage| !usage.ident.span.eq_ctxt(span)) {
return;
}
let elidable = could_use_elision(cx, sig.decl, body, trait_sig, generics.params, msrv);
let has_elidable_lts = elidable
.as_ref()
.is_some_and(|(_, usages)| !usages.iter().any(|usage| !usage.ident.span.eq_ctxt(span)));
// Only check is_from_proc_macro if we're about to emit a lint (it's an expensive check)
if (has_elidable_lts || report_extra_lifetimes) && is_from_proc_macro() {
return;
}
if let Some((elidable_lts, usages)) = elidable
&& has_elidable_lts
{
// async functions have usages whose spans point at the lifetime declaration which messes up
// suggestions
let include_suggestions = !sig.header.is_async();

View file

@ -1,4 +1,5 @@
//@aux-build:proc_macro_derive.rs
//@aux-build:proc_macros.rs
#![allow(
unused,
@ -11,6 +12,7 @@
#[macro_use]
extern crate proc_macro_derive;
extern crate proc_macros;
fn empty() {}
@ -148,4 +150,34 @@ mod issue_13578 {
impl<'a, T: 'a> Foo for Option<T> where &'a T: Foo {}
}
// no lint on proc macro generated code
mod proc_macro_generated {
use proc_macros::external;
// no lint on external macro (extra unused lifetimes in impl block)
external! {
struct ExternalImplStruct;
impl<'a> ExternalImplStruct {
fn foo() {}
}
}
// no lint on external macro (extra unused lifetimes in method)
external! {
struct ExternalMethodStruct;
impl ExternalMethodStruct {
fn bar<'a>(&self) {}
}
}
// no lint on external macro (extra unused lifetimes in trait method)
external! {
trait ExternalUnusedLifetimeTrait {
fn unused_lt<'a>(x: u8) {}
}
}
}
fn main() {}

View file

@ -1,5 +1,5 @@
error: this lifetime isn't used in the function definition
--> tests/ui/extra_unused_lifetimes.rs:19:14
--> tests/ui/extra_unused_lifetimes.rs:21:14
|
LL | fn unused_lt<'a>(x: u8) {}
| ^^
@ -8,37 +8,37 @@ LL | fn unused_lt<'a>(x: u8) {}
= help: to override `-D warnings` add `#[allow(clippy::extra_unused_lifetimes)]`
error: this lifetime isn't used in the function definition
--> tests/ui/extra_unused_lifetimes.rs:47:10
--> tests/ui/extra_unused_lifetimes.rs:49:10
|
LL | fn x<'a>(&self) {}
| ^^
error: this lifetime isn't used in the function definition
--> tests/ui/extra_unused_lifetimes.rs:74:22
--> tests/ui/extra_unused_lifetimes.rs:76:22
|
LL | fn unused_lt<'a>(x: u8) {}
| ^^
error: this lifetime isn't used in the impl
--> tests/ui/extra_unused_lifetimes.rs:86:10
--> tests/ui/extra_unused_lifetimes.rs:88:10
|
LL | impl<'a> std::ops::AddAssign<&Scalar> for &mut Scalar {
| ^^
error: this lifetime isn't used in the impl
--> tests/ui/extra_unused_lifetimes.rs:93:10
--> tests/ui/extra_unused_lifetimes.rs:95:10
|
LL | impl<'b> Scalar {
| ^^
error: this lifetime isn't used in the function definition
--> tests/ui/extra_unused_lifetimes.rs:95:26
--> tests/ui/extra_unused_lifetimes.rs:97:26
|
LL | pub fn something<'c>() -> Self {
| ^^
error: this lifetime isn't used in the impl
--> tests/ui/extra_unused_lifetimes.rs:125:10
--> tests/ui/extra_unused_lifetimes.rs:127:10
|
LL | impl<'a, T: Source + ?Sized + 'a> Source for Box<T> {
| ^^

View file

@ -470,13 +470,36 @@ mod in_macro {
}
}
// no lint on external macro
// no lint on external macro (standalone function)
external! {
fn needless_lifetime<'a>(x: &'a u8) -> &'a u8 {
unimplemented!()
}
}
// no lint on external macro (method in impl block)
external! {
struct ExternalStruct;
impl ExternalStruct {
fn needless_lifetime_method<'a>(x: &'a u8) -> &'a u8 {
unimplemented!()
}
}
}
// no lint on external macro (trait method)
external! {
trait ExternalTrait {
fn needless_lifetime_trait_method<'a>(x: &'a u8) -> &'a u8;
}
}
// no lint on external macro (extra unused lifetimes in function)
external! {
fn extra_unused_lifetime<'a>(x: u8) {}
}
inline! {
fn f<$'a>(arg: &$'a str) -> &$'a str {
arg

View file

@ -470,13 +470,36 @@ mod in_macro {
}
}
// no lint on external macro
// no lint on external macro (standalone function)
external! {
fn needless_lifetime<'a>(x: &'a u8) -> &'a u8 {
unimplemented!()
}
}
// no lint on external macro (method in impl block)
external! {
struct ExternalStruct;
impl ExternalStruct {
fn needless_lifetime_method<'a>(x: &'a u8) -> &'a u8 {
unimplemented!()
}
}
}
// no lint on external macro (trait method)
external! {
trait ExternalTrait {
fn needless_lifetime_trait_method<'a>(x: &'a u8) -> &'a u8;
}
}
// no lint on external macro (extra unused lifetimes in function)
external! {
fn extra_unused_lifetime<'a>(x: u8) {}
}
inline! {
fn f<$'a>(arg: &$'a str) -> &$'a str {
arg