about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--clippy_lints/src/needless_arbitrary_self_type.rs30
-rw-r--r--tests/ui/auxiliary/proc_macro_attr.rs61
-rw-r--r--tests/ui/needless_arbitrary_self_type_unfixable.rs45
-rw-r--r--tests/ui/needless_arbitrary_self_type_unfixable.stderr10
4 files changed, 139 insertions, 7 deletions
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
+