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

Add the manual_async_fn lint #5576

Merged
merged 3 commits into from
May 8, 2020
Merged
Show file tree
Hide file tree
Changes from 2 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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -1422,6 +1422,7 @@ Released 2018-09-13
[`lossy_float_literal`]: https://rust-lang.github.io/rust-clippy/master/index.html#lossy_float_literal
[`macro_use_imports`]: https://rust-lang.github.io/rust-clippy/master/index.html#macro_use_imports
[`main_recursion`]: https://rust-lang.github.io/rust-clippy/master/index.html#main_recursion
[`manual_async_fn`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_async_fn
[`manual_memcpy`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_memcpy
[`manual_non_exhaustive`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_non_exhaustive
[`manual_saturating_arithmetic`]: https://rust-lang.github.io/rust-clippy/master/index.html#manual_saturating_arithmetic
Expand Down
5 changes: 5 additions & 0 deletions clippy_lints/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,7 @@ mod literal_representation;
mod loops;
mod macro_use;
mod main_recursion;
mod manual_async_fn;
mod manual_non_exhaustive;
mod map_clone;
mod map_unit_fn;
Expand Down Expand Up @@ -629,6 +630,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
&loops::WHILE_LET_ON_ITERATOR,
&macro_use::MACRO_USE_IMPORTS,
&main_recursion::MAIN_RECURSION,
&manual_async_fn::MANUAL_ASYNC_FN,
&manual_non_exhaustive::MANUAL_NON_EXHAUSTIVE,
&map_clone::MAP_CLONE,
&map_unit_fn::OPTION_MAP_UNIT_FN,
Expand Down Expand Up @@ -1067,6 +1069,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
store.register_late_pass(|| box if_let_mutex::IfLetMutex);
store.register_late_pass(|| box match_on_vec_items::MatchOnVecItems);
store.register_early_pass(|| box manual_non_exhaustive::ManualNonExhaustive);
store.register_late_pass(|| box manual_async_fn::ManualAsyncFn);

store.register_group(true, "clippy::restriction", Some("clippy_restriction"), vec![
LintId::of(&arithmetic::FLOAT_ARITHMETIC),
Expand Down Expand Up @@ -1284,6 +1287,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
LintId::of(&loops::WHILE_LET_LOOP),
LintId::of(&loops::WHILE_LET_ON_ITERATOR),
LintId::of(&main_recursion::MAIN_RECURSION),
LintId::of(&manual_async_fn::MANUAL_ASYNC_FN),
LintId::of(&manual_non_exhaustive::MANUAL_NON_EXHAUSTIVE),
LintId::of(&map_clone::MAP_CLONE),
LintId::of(&map_unit_fn::OPTION_MAP_UNIT_FN),
Expand Down Expand Up @@ -1478,6 +1482,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
LintId::of(&loops::NEEDLESS_RANGE_LOOP),
LintId::of(&loops::WHILE_LET_ON_ITERATOR),
LintId::of(&main_recursion::MAIN_RECURSION),
LintId::of(&manual_async_fn::MANUAL_ASYNC_FN),
LintId::of(&manual_non_exhaustive::MANUAL_NON_EXHAUSTIVE),
LintId::of(&map_clone::MAP_CLONE),
LintId::of(&matches::INFALLIBLE_DESTRUCTURING_MATCH),
Expand Down
160 changes: 160 additions & 0 deletions clippy_lints/src/manual_async_fn.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
use crate::utils::paths::{FUTURE_CORE, FUTURE_FROM_GENERATOR, FUTURE_STD};
use crate::utils::{match_function_call, match_path, snippet_block, snippet_opt, span_lint_and_then};
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::intravisit::FnKind;
use rustc_hir::{
AsyncGeneratorKind, Block, Body, Expr, ExprKind, FnDecl, FnRetTy, GeneratorKind, GenericBound, HirId, IsAsync,
ItemKind, TraitRef, Ty, TyKind, TypeBindingKind,
};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};
use rustc_span::Span;

declare_clippy_lint! {
/// **What it does:** It checks for manual implementations of `async` functions.
///
/// **Why is this bad?** It's more idiomatic to use the dedicated syntax.
///
/// **Known problems:** None.
///
/// **Example:**
///
/// ```rust
/// use std::future::Future;
///
/// fn foo() -> impl Future<Output = i32> { async { 42 } }
/// ```
/// Use instead:
/// ```rust
/// use std::future::Future;
///
/// async fn foo() -> i32 { 42 }
/// ```
pub MANUAL_ASYNC_FN,
style,
"manual implementations of `async` functions can be simplified using the dedicated syntax"
}

declare_lint_pass!(ManualAsyncFn => [MANUAL_ASYNC_FN]);

impl<'a, 'tcx> LateLintPass<'a, 'tcx> for ManualAsyncFn {
fn check_fn(
&mut self,
cx: &LateContext<'a, 'tcx>,
kind: FnKind<'tcx>,
decl: &'tcx FnDecl<'_>,
body: &'tcx Body<'_>,
span: Span,
_: HirId,
) {
if_chain! {
if let Some(header) = kind.header();
if let IsAsync::NotAsync = header.asyncness;
// Check that this function returns `impl Future`
if let FnRetTy::Return(ret_ty) = decl.output;
if let Some(trait_ref) = future_trait_ref(cx, ret_ty);
if let Some(output) = future_output_ty(trait_ref);
// Check that the body of the function consists of one async block
if let ExprKind::Block(block, _) = body.value.kind;
if block.stmts.is_empty();
if let Some(closure_body) = desugared_async_block(cx, block);
then {
let header_span = span.with_hi(ret_ty.span.hi());

span_lint_and_then(
cx,
MANUAL_ASYNC_FN,
header_span,
"this function can be simplified using async syntax",
ebroto marked this conversation as resolved.
Show resolved Hide resolved
|diag| {
if_chain! {
if let Some(header_snip) = snippet_opt(cx, header_span);
if let Some(ret_pos) = header_snip.rfind("->");
if let Some((ret_sugg, ret_snip)) = suggested_ret(cx, output);
then {
let help = format!("make the function `async` and {}", ret_sugg);
diag.span_suggestion(
header_span,
&help,
format!("async {}{}", &header_snip[..ret_pos], ret_snip),
Applicability::MachineApplicable
);

let body_snip = snippet_block(cx, closure_body.value.span, "..", Some(block.span));
diag.span_suggestion(
block.span,
"move the body of the async block to the enclosing function",
body_snip.to_string(),
Applicability::MachineApplicable
);
}
}
},
);
}
}
}
}

fn future_trait_ref<'tcx>(cx: &LateContext<'_, 'tcx>, ty: &'tcx Ty<'tcx>) -> Option<&'tcx TraitRef<'tcx>> {
if_chain! {
if let TyKind::Def(item_id, _) = ty.kind;
let item = cx.tcx.hir().item(item_id.id);
if let ItemKind::OpaqueTy(opaque) = &item.kind;
if opaque.bounds.len() == 1;
if let GenericBound::Trait(poly, _) = &opaque.bounds[0];
let path = poly.trait_ref.path;
if match_path(&path, &FUTURE_CORE) || match_path(&path, &FUTURE_STD);
ebroto marked this conversation as resolved.
Show resolved Hide resolved
then {
return Some(&poly.trait_ref);
}
}

None
}

fn future_output_ty<'tcx>(trait_ref: &'tcx TraitRef<'tcx>) -> Option<&'tcx Ty<'tcx>> {
if_chain! {
if let Some(segment) = trait_ref.path.segments.last();
if let Some(args) = segment.args;
if args.bindings.len() == 1;
let binding = &args.bindings[0];
if binding.ident.as_str() == "Output";
if let TypeBindingKind::Equality{ty: output} = binding.kind;
then {
return Some(output)
}
}

None
}

fn desugared_async_block<'tcx>(cx: &LateContext<'_, 'tcx>, block: &'tcx Block<'tcx>) -> Option<&'tcx Body<'tcx>> {
if_chain! {
if let Some(block_expr) = block.expr;
if let Some(args) = match_function_call(cx, block_expr, &FUTURE_FROM_GENERATOR);
if args.len() == 1;
if let Expr{kind: ExprKind::Closure(_, _, body_id, ..), ..} = args[0];
let closure_body = cx.tcx.hir().body(body_id);
if let Some(GeneratorKind::Async(AsyncGeneratorKind::Block)) = closure_body.generator_kind;
then {
return Some(closure_body);
}
}

None
}

fn suggested_ret(cx: &LateContext<'_, '_>, output: &Ty<'_>) -> Option<(&'static str, String)> {
match output.kind {
flip1995 marked this conversation as resolved.
Show resolved Hide resolved
TyKind::Tup(tys) if tys.is_empty() => {
let sugg = "remove the return type";
Some((sugg, "".into()))
},
_ => {
let sugg = "return the output of the future directly";
snippet_opt(cx, output.span).map(|snip| (sugg, format!("-> {}", snip)))
},
}
}
3 changes: 3 additions & 0 deletions clippy_lints/src/utils/paths.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ pub const FMT_ARGUMENTS_NEW_V1_FORMATTED: [&str; 4] = ["core", "fmt", "Arguments
pub const FMT_ARGUMENTV1_NEW: [&str; 4] = ["core", "fmt", "ArgumentV1", "new"];
pub const FROM_FROM: [&str; 4] = ["core", "convert", "From", "from"];
pub const FROM_TRAIT: [&str; 3] = ["core", "convert", "From"];
pub const FUTURE_CORE: [&str; 3] = ["core", "future", "Future"];
pub const FUTURE_FROM_GENERATOR: [&str; 3] = ["core", "future", "from_generator"];
pub const FUTURE_STD: [&str; 3] = ["std", "future", "Future"];
pub const HASH: [&str; 2] = ["hash", "Hash"];
pub const HASHMAP: [&str; 5] = ["std", "collections", "hash", "map", "HashMap"];
pub const HASHMAP_ENTRY: [&str; 5] = ["std", "collections", "hash", "map", "Entry"];
Expand Down
7 changes: 7 additions & 0 deletions src/lintlist/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1081,6 +1081,13 @@ pub static ref ALL_LINTS: Vec<Lint> = vec![
deprecation: None,
module: "main_recursion",
},
Lint {
name: "manual_async_fn",
group: "style",
desc: "manual implementations of `async` functions can be simplified using the dedicated syntax",
deprecation: None,
module: "manual_async_fn",
},
Lint {
name: "manual_memcpy",
group: "perf",
Expand Down
1 change: 1 addition & 0 deletions tests/ui/future_not_send.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ impl Dummy {
self.private_future().await;
}

#[allow(clippy::manual_async_fn)]
pub fn public_send(&self) -> impl std::future::Future<Output = bool> {
async { false }
}
Expand Down
6 changes: 3 additions & 3 deletions tests/ui/future_not_send.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -96,13 +96,13 @@ LL | }
= note: `std::rc::Rc<[u8]>` doesn't implement `std::marker::Sync`

error: future cannot be sent between threads safely
--> $DIR/future_not_send.rs:49:37
--> $DIR/future_not_send.rs:50:37
|
LL | async fn generic_future<T>(t: T) -> T
| ^ future returned by `generic_future` is not `Send`
|
note: future is not `Send` as this value is used across an await
--> $DIR/future_not_send.rs:54:5
--> $DIR/future_not_send.rs:55:5
|
LL | let rt = &t;
| -- has type `&T` which is not `Send`
Expand All @@ -114,7 +114,7 @@ LL | }
= note: `T` doesn't implement `std::marker::Sync`

error: future cannot be sent between threads safely
--> $DIR/future_not_send.rs:65:34
--> $DIR/future_not_send.rs:66:34
|
LL | async fn unclear_future<T>(t: T) {}
| ^
Expand Down
55 changes: 55 additions & 0 deletions tests/ui/manual_async_fn.fixed
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// run-rustfix
// edition:2018
#![warn(clippy::manual_async_fn)]
#![allow(unused)]

use std::future::Future;

async fn fut() -> i32 { 42 }

async fn empty_fut() {}

async fn core_fut() -> i32 { 42 }

// should be ignored
fn has_other_stmts() -> impl core::future::Future<Output = i32> {
let _ = 42;
async move { 42 }
}

// should be ignored
fn not_fut() -> i32 {
42
}

// should be ignored
async fn already_async() -> impl Future<Output = i32> {
async { 42 }
}

struct S {}
impl S {
async fn inh_fut() -> i32 { 42 }

async fn meth_fut(&self) -> i32 { 42 }

async fn empty_fut(&self) {}

// should be ignored
fn not_fut(&self) -> i32 {
42
}

// should be ignored
fn has_other_stmts() -> impl core::future::Future<Output = i32> {
let _ = 42;
async move { 42 }
}

// should be ignored
async fn already_async(&self) -> impl Future<Output = i32> {
async { 42 }
}
}

fn main() {}
67 changes: 67 additions & 0 deletions tests/ui/manual_async_fn.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// run-rustfix
// edition:2018
#![warn(clippy::manual_async_fn)]
#![allow(unused)]

use std::future::Future;

fn fut() -> impl Future<Output = i32> {
async { 42 }
}

fn empty_fut() -> impl Future<Output = ()> {
async {}
}

fn core_fut() -> impl core::future::Future<Output = i32> {
async move { 42 }
}

// should be ignored
fn has_other_stmts() -> impl core::future::Future<Output = i32> {
let _ = 42;
async move { 42 }
}

// should be ignored
fn not_fut() -> i32 {
42
}

// should be ignored
async fn already_async() -> impl Future<Output = i32> {
async { 42 }
}

struct S {}
impl S {
ebroto marked this conversation as resolved.
Show resolved Hide resolved
fn inh_fut() -> impl Future<Output = i32> {
async { 42 }
}

fn meth_fut(&self) -> impl Future<Output = i32> {
async { 42 }
}

fn empty_fut(&self) -> impl Future<Output = ()> {
async {}
}

// should be ignored
fn not_fut(&self) -> i32 {
42
}

// should be ignored
fn has_other_stmts() -> impl core::future::Future<Output = i32> {
let _ = 42;
async move { 42 }
}

// should be ignored
async fn already_async(&self) -> impl Future<Output = i32> {
async { 42 }
}
}

fn main() {}
Loading