diff --git a/clippy_lints/src/needless_arbitrary_self_type.rs b/clippy_lints/src/needless_arbitrary_self_type.rs index 38bdd0f7ed2..7687962bdd9 100644 --- a/clippy_lints/src/needless_arbitrary_self_type.rs +++ b/clippy_lints/src/needless_arbitrary_self_type.rs @@ -1,4 +1,4 @@ -use crate::utils::span_lint_and_sugg; +use crate::utils::{in_macro, span_lint_and_sugg}; use if_chain::if_chain; use rustc_ast::ast::{BindingMode, Lifetime, Mutability, Param, PatKind, Path, TyKind}; use rustc_errors::Applicability; @@ -69,11 +69,30 @@ fn check_param_inner(cx: &EarlyContext<'_>, path: &Path, span: Span, binding_mod if let [segment] = &path.segments[..]; if segment.ident.name == kw::SelfUpper; then { + // In case we have a named lifetime, we check if the name comes from expansion. + // If it does, at this point we know the rest of the parameter was written by the user, + // so let them decide what the name of the lifetime should be. + // See #6089 for more details. + let mut applicability = Applicability::MachineApplicable; let self_param = match (binding_mode, mutbl) { (Mode::Ref(None), Mutability::Mut) => "&mut self".to_string(), - (Mode::Ref(Some(lifetime)), Mutability::Mut) => format!("&{} mut self", &lifetime.ident.name), + (Mode::Ref(Some(lifetime)), Mutability::Mut) => { + if in_macro(lifetime.ident.span) { + applicability = Applicability::HasPlaceholders; + "&'_ mut self".to_string() + } else { + format!("&{} mut self", &lifetime.ident.name) + } + }, (Mode::Ref(None), Mutability::Not) => "&self".to_string(), - (Mode::Ref(Some(lifetime)), Mutability::Not) => format!("&{} self", &lifetime.ident.name), + (Mode::Ref(Some(lifetime)), Mutability::Not) => { + if in_macro(lifetime.ident.span) { + applicability = Applicability::HasPlaceholders; + "&'_ self".to_string() + } else { + format!("&{} self", &lifetime.ident.name) + } + }, (Mode::Value, Mutability::Mut) => "mut self".to_string(), (Mode::Value, Mutability::Not) => "self".to_string(), }; @@ -85,7 +104,7 @@ fn check_param_inner(cx: &EarlyContext<'_>, path: &Path, span: Span, binding_mod "the type of the `self` parameter does not need to be arbitrary", "consider to change this parameter to", self_param, - Applicability::MachineApplicable, + applicability, ) } } @@ -93,7 +112,8 @@ fn check_param_inner(cx: &EarlyContext<'_>, path: &Path, span: Span, binding_mod impl EarlyLintPass for NeedlessArbitrarySelfType { fn check_param(&mut self, cx: &EarlyContext<'_>, p: &Param) { - if !p.is_self() { + // Bail out if the parameter it's not a receiver or was not written by the user + if !p.is_self() || in_macro(p.span) { return; } diff --git a/tests/ui/auxiliary/proc_macro_attr.rs b/tests/ui/auxiliary/proc_macro_attr.rs index 01796d45f13..de670cdfc31 100644 --- a/tests/ui/auxiliary/proc_macro_attr.rs +++ b/tests/ui/auxiliary/proc_macro_attr.rs @@ -2,7 +2,7 @@ // no-prefer-dynamic #![crate_type = "proc-macro"] -#![feature(repr128, proc_macro_hygiene, proc_macro_quote)] +#![feature(repr128, proc_macro_hygiene, proc_macro_quote, box_patterns)] #![allow(clippy::useless_conversion)] extern crate proc_macro; @@ -12,7 +12,11 @@ extern crate syn; use proc_macro::TokenStream; use quote::{quote, quote_spanned}; use syn::parse_macro_input; -use syn::{parse_quote, ItemTrait, TraitItem}; +use syn::spanned::Spanned; +use syn::token::Star; +use syn::{ + parse_quote, FnArg, ImplItem, ItemImpl, ItemTrait, Lifetime, Pat, PatIdent, PatType, Signature, TraitItem, Type, +}; #[proc_macro_attribute] pub fn fake_async_trait(_args: TokenStream, input: TokenStream) -> TokenStream { @@ -36,3 +40,56 @@ pub fn fake_async_trait(_args: TokenStream, input: TokenStream) -> TokenStream { } TokenStream::from(quote!(#item)) } + +#[proc_macro_attribute] +pub fn rename_my_lifetimes(_args: TokenStream, input: TokenStream) -> TokenStream { + fn make_name(count: usize) -> String { + format!("'life{}", count) + } + + fn mut_receiver_of(sig: &mut Signature) -> Option<&mut FnArg> { + let arg = sig.inputs.first_mut()?; + if let FnArg::Typed(PatType { pat, .. }) = arg { + if let Pat::Ident(PatIdent { ident, .. }) = &**pat { + if ident == "self" { + return Some(arg); + } + } + } + None + } + + let mut elided = 0; + let mut item = parse_macro_input!(input as ItemImpl); + + // Look for methods having arbitrary self type taken by &mut ref + for inner in &mut item.items { + if let ImplItem::Method(method) = inner { + if let Some(FnArg::Typed(pat_type)) = mut_receiver_of(&mut method.sig) { + if let box Type::Reference(reference) = &mut pat_type.ty { + // Target only unnamed lifetimes + let name = match &reference.lifetime { + Some(lt) if lt.ident == "_" => make_name(elided), + None => make_name(elided), + _ => continue, + }; + elided += 1; + + // HACK: Syn uses `Span` from the proc_macro2 crate, and does not seem to reexport it. + // In order to avoid adding the dependency, get a default span from a non-existent token. + // A default span is needed to mark the code as coming from expansion. + let span = Star::default().span(); + + // Replace old lifetime with the named one + let lifetime = Lifetime::new(&name, span); + reference.lifetime = Some(parse_quote!(#lifetime)); + + // Add lifetime to the generics of the method + method.sig.generics.params.push(parse_quote!(#lifetime)); + } + } + } + } + + TokenStream::from(quote!(#item)) +} diff --git a/tests/ui/needless_arbitrary_self_type_unfixable.rs b/tests/ui/needless_arbitrary_self_type_unfixable.rs new file mode 100644 index 00000000000..a39d96109f1 --- /dev/null +++ b/tests/ui/needless_arbitrary_self_type_unfixable.rs @@ -0,0 +1,45 @@ +// aux-build:proc_macro_attr.rs + +#![warn(clippy::needless_arbitrary_self_type)] + +#[macro_use] +extern crate proc_macro_attr; + +mod issue_6089 { + // Check that we don't lint if the `self` parameter comes from expansion + + macro_rules! test_from_expansion { + () => { + trait T1 { + fn test(self: &Self); + } + + struct S1 {} + + impl T1 for S1 { + fn test(self: &Self) {} + } + }; + } + + test_from_expansion!(); + + // If only the lifetime name comes from expansion we will lint, but the suggestion will have + // placeholders and will not be applied automatically, as we can't reliably know the original name. + // This specific case happened with async_trait. + + trait T2 { + fn call_with_mut_self(&mut self); + } + + struct S2 {} + + // The method's signature will be expanded to: + // fn call_with_mut_self<'life0>(self: &'life0 mut Self) {} + #[rename_my_lifetimes] + impl T2 for S2 { + fn call_with_mut_self(self: &mut Self) {} + } +} + +fn main() {} diff --git a/tests/ui/needless_arbitrary_self_type_unfixable.stderr b/tests/ui/needless_arbitrary_self_type_unfixable.stderr new file mode 100644 index 00000000000..44a0e6ddeac --- /dev/null +++ b/tests/ui/needless_arbitrary_self_type_unfixable.stderr @@ -0,0 +1,10 @@ +error: the type of the `self` parameter does not need to be arbitrary + --> $DIR/needless_arbitrary_self_type_unfixable.rs:41:31 + | +LL | fn call_with_mut_self(self: &mut Self) {} + | ^^^^^^^^^^^^^^^ help: consider to change this parameter to: `&'_ mut self` + | + = note: `-D clippy::needless-arbitrary-self-type` implied by `-D warnings` + +error: aborting due to previous error +