Skip to content

Commit 101e76f

Browse files
committed
needless arbitrary self: handle macros
1 parent db6fb90 commit 101e76f

File tree

4 files changed

+139
-7
lines changed

4 files changed

+139
-7
lines changed

clippy_lints/src/needless_arbitrary_self_type.rs

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use crate::utils::span_lint_and_sugg;
1+
use crate::utils::{in_macro, span_lint_and_sugg};
22
use if_chain::if_chain;
33
use rustc_ast::ast::{BindingMode, Lifetime, Mutability, Param, PatKind, Path, TyKind};
44
use rustc_errors::Applicability;
@@ -69,11 +69,30 @@ fn check_param_inner(cx: &EarlyContext<'_>, path: &Path, span: Span, binding_mod
6969
if let [segment] = &path.segments[..];
7070
if segment.ident.name == kw::SelfUpper;
7171
then {
72+
// In case we have a named lifetime, we check if the name comes from expansion.
73+
// If it does, at this point we know the rest of the parameter was written by the user,
74+
// so let them decide what the name of the lifetime should be.
75+
// See #6089 for more details.
76+
let mut applicability = Applicability::MachineApplicable;
7277
let self_param = match (binding_mode, mutbl) {
7378
(Mode::Ref(None), Mutability::Mut) => "&mut self".to_string(),
74-
(Mode::Ref(Some(lifetime)), Mutability::Mut) => format!("&{} mut self", &lifetime.ident.name),
79+
(Mode::Ref(Some(lifetime)), Mutability::Mut) => {
80+
if in_macro(lifetime.ident.span) {
81+
applicability = Applicability::HasPlaceholders;
82+
"&'_ mut self".to_string()
83+
} else {
84+
format!("&{} mut self", &lifetime.ident.name)
85+
}
86+
},
7587
(Mode::Ref(None), Mutability::Not) => "&self".to_string(),
76-
(Mode::Ref(Some(lifetime)), Mutability::Not) => format!("&{} self", &lifetime.ident.name),
88+
(Mode::Ref(Some(lifetime)), Mutability::Not) => {
89+
if in_macro(lifetime.ident.span) {
90+
applicability = Applicability::HasPlaceholders;
91+
"&'_ self".to_string()
92+
} else {
93+
format!("&{} self", &lifetime.ident.name)
94+
}
95+
},
7796
(Mode::Value, Mutability::Mut) => "mut self".to_string(),
7897
(Mode::Value, Mutability::Not) => "self".to_string(),
7998
};
@@ -85,15 +104,16 @@ fn check_param_inner(cx: &EarlyContext<'_>, path: &Path, span: Span, binding_mod
85104
"the type of the `self` parameter does not need to be arbitrary",
86105
"consider to change this parameter to",
87106
self_param,
88-
Applicability::MachineApplicable,
107+
applicability,
89108
)
90109
}
91110
}
92111
}
93112

94113
impl EarlyLintPass for NeedlessArbitrarySelfType {
95114
fn check_param(&mut self, cx: &EarlyContext<'_>, p: &Param) {
96-
if !p.is_self() {
115+
// Bail out if the parameter it's not a receiver or was not written by the user
116+
if !p.is_self() || in_macro(p.span) {
97117
return;
98118
}
99119

tests/ui/auxiliary/proc_macro_attr.rs

Lines changed: 59 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// no-prefer-dynamic
33

44
#![crate_type = "proc-macro"]
5-
#![feature(repr128, proc_macro_hygiene, proc_macro_quote)]
5+
#![feature(repr128, proc_macro_hygiene, proc_macro_quote, box_patterns)]
66
#![allow(clippy::useless_conversion)]
77

88
extern crate proc_macro;
@@ -12,7 +12,11 @@ extern crate syn;
1212
use proc_macro::TokenStream;
1313
use quote::{quote, quote_spanned};
1414
use syn::parse_macro_input;
15-
use syn::{parse_quote, ItemTrait, TraitItem};
15+
use syn::spanned::Spanned;
16+
use syn::token::Star;
17+
use syn::{
18+
parse_quote, FnArg, ImplItem, ItemImpl, ItemTrait, Lifetime, Pat, PatIdent, PatType, Signature, TraitItem, Type,
19+
};
1620

1721
#[proc_macro_attribute]
1822
pub fn fake_async_trait(_args: TokenStream, input: TokenStream) -> TokenStream {
@@ -36,3 +40,56 @@ pub fn fake_async_trait(_args: TokenStream, input: TokenStream) -> TokenStream {
3640
}
3741
TokenStream::from(quote!(#item))
3842
}
43+
44+
#[proc_macro_attribute]
45+
pub fn rename_my_lifetimes(_args: TokenStream, input: TokenStream) -> TokenStream {
46+
fn make_name(count: usize) -> String {
47+
format!("'life{}", count)
48+
}
49+
50+
fn mut_receiver_of(sig: &mut Signature) -> Option<&mut FnArg> {
51+
let arg = sig.inputs.first_mut()?;
52+
if let FnArg::Typed(PatType { pat, .. }) = arg {
53+
if let Pat::Ident(PatIdent { ident, .. }) = &**pat {
54+
if ident == "self" {
55+
return Some(arg);
56+
}
57+
}
58+
}
59+
None
60+
}
61+
62+
let mut elided = 0;
63+
let mut item = parse_macro_input!(input as ItemImpl);
64+
65+
// Look for methods having arbitrary self type taken by &mut ref
66+
for inner in &mut item.items {
67+
if let ImplItem::Method(method) = inner {
68+
if let Some(FnArg::Typed(pat_type)) = mut_receiver_of(&mut method.sig) {
69+
if let box Type::Reference(reference) = &mut pat_type.ty {
70+
// Target only unnamed lifetimes
71+
let name = match &reference.lifetime {
72+
Some(lt) if lt.ident == "_" => make_name(elided),
73+
None => make_name(elided),
74+
_ => continue,
75+
};
76+
elided += 1;
77+
78+
// HACK: Syn uses `Span` from the proc_macro2 crate, and does not seem to reexport it.
79+
// In order to avoid adding the dependency, get a default span from a non-existent token.
80+
// A default span is needed to mark the code as coming from expansion.
81+
let span = Star::default().span();
82+
83+
// Replace old lifetime with the named one
84+
let lifetime = Lifetime::new(&name, span);
85+
reference.lifetime = Some(parse_quote!(#lifetime));
86+
87+
// Add lifetime to the generics of the method
88+
method.sig.generics.params.push(parse_quote!(#lifetime));
89+
}
90+
}
91+
}
92+
}
93+
94+
TokenStream::from(quote!(#item))
95+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// aux-build:proc_macro_attr.rs
2+
3+
#![warn(clippy::needless_arbitrary_self_type)]
4+
5+
#[macro_use]
6+
extern crate proc_macro_attr;
7+
8+
mod issue_6089 {
9+
// Check that we don't lint if the `self` parameter comes from expansion
10+
11+
macro_rules! test_from_expansion {
12+
() => {
13+
trait T1 {
14+
fn test(self: &Self);
15+
}
16+
17+
struct S1 {}
18+
19+
impl T1 for S1 {
20+
fn test(self: &Self) {}
21+
}
22+
};
23+
}
24+
25+
test_from_expansion!();
26+
27+
// If only the lifetime name comes from expansion we will lint, but the suggestion will have
28+
// placeholders and will not be applied automatically, as we can't reliably know the original name.
29+
// This specific case happened with async_trait.
30+
31+
trait T2 {
32+
fn call_with_mut_self(&mut self);
33+
}
34+
35+
struct S2 {}
36+
37+
// The method's signature will be expanded to:
38+
// fn call_with_mut_self<'life0>(self: &'life0 mut Self) {}
39+
#[rename_my_lifetimes]
40+
impl T2 for S2 {
41+
fn call_with_mut_self(self: &mut Self) {}
42+
}
43+
}
44+
45+
fn main() {}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
error: the type of the `self` parameter does not need to be arbitrary
2+
--> $DIR/needless_arbitrary_self_type_unfixable.rs:41:31
3+
|
4+
LL | fn call_with_mut_self(self: &mut Self) {}
5+
| ^^^^^^^^^^^^^^^ help: consider to change this parameter to: `&'_ mut self`
6+
|
7+
= note: `-D clippy::needless-arbitrary-self-type` implied by `-D warnings`
8+
9+
error: aborting due to previous error
10+

0 commit comments

Comments
 (0)