Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

needless arbitrary self: handle macros #6093

Merged
merged 1 commit into from
Sep 29, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 25 additions & 5 deletions clippy_lints/src/needless_arbitrary_self_type.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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(),
};
Expand All @@ -85,15 +104,16 @@ 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,
)
}
}
}

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;
}

Expand Down
61 changes: 59 additions & 2 deletions tests/ui/auxiliary/proc_macro_attr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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 {
Expand All @@ -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))
}
45 changes: 45 additions & 0 deletions tests/ui/needless_arbitrary_self_type_unfixable.rs
Original file line number Diff line number Diff line change
@@ -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() {}
10 changes: 10 additions & 0 deletions tests/ui/needless_arbitrary_self_type_unfixable.stderr
Original file line number Diff line number Diff line change
@@ -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