]> git.lizzy.rs Git - rust.git/commitdiff
Add the manual_async_fn lint
authorEduardo Broto <ebroto@tutanota.com>
Thu, 7 May 2020 19:41:23 +0000 (21:41 +0200)
committerEduardo Broto <ebroto@tutanota.com>
Thu, 7 May 2020 19:42:40 +0000 (21:42 +0200)
CHANGELOG.md
clippy_lints/src/lib.rs
clippy_lints/src/manual_async_fn.rs [new file with mode: 0644]
clippy_lints/src/utils/paths.rs
src/lintlist/mod.rs
tests/ui/future_not_send.rs
tests/ui/future_not_send.stderr
tests/ui/manual_async_fn.fixed [new file with mode: 0644]
tests/ui/manual_async_fn.rs [new file with mode: 0644]
tests/ui/manual_async_fn.stderr [new file with mode: 0644]

index facf363e371e0f56e7729e19d0a4ad1246b1f5b6..8457d6ad05cf9d876fe7739d364189234bb9980d 100644 (file)
@@ -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
index 97785cba8835522dc34bdabdd3b346de004102e6..fb2e9932b8e49a885f4fb45f443107b7ba1eb416 100644 (file)
@@ -247,6 +247,7 @@ macro_rules! declare_clippy_lint {
 mod loops;
 mod macro_use;
 mod main_recursion;
+mod manual_async_fn;
 mod manual_non_exhaustive;
 mod map_clone;
 mod map_unit_fn;
@@ -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,
@@ -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),
@@ -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),
@@ -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),
diff --git a/clippy_lints/src/manual_async_fn.rs b/clippy_lints/src/manual_async_fn.rs
new file mode 100644 (file)
index 0000000..ab8829a
--- /dev/null
@@ -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() -> 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",
+                    |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);
+        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 {
+        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)))
+        },
+    }
+}
index 79ca6845c6f2e8845169be7f7be569e3897b1870..74040a96c78a4a0ceafc4c2df4a8eb8456f6f3eb 100644 (file)
@@ -42,6 +42,9 @@
 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"];
index a7685f67211f8dc8e460b4265b1c1f63023b64fa..51d1cb2216a954c55e5907abc8c5885af1ea1a10 100644 (file)
         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",
index 6d09d71a630a28b6e85b089b6b74cf94a9558f45..d3a920de4b6ad8a685f3c04096a3afd414826998 100644 (file)
@@ -41,6 +41,7 @@ pub async fn public_future(&self) {
         self.private_future().await;
     }
 
+    #[allow(clippy::manual_async_fn)]
     pub fn public_send(&self) -> impl std::future::Future<Output = bool> {
         async { false }
     }
index 3b4968ef0a63c528c8060966465c349c55dc6b94..d1863701bfe7c14dd0cd0a161d3b224530b4343e 100644 (file)
@@ -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`
@@ -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) {}
    |                                  ^
diff --git a/tests/ui/manual_async_fn.fixed b/tests/ui/manual_async_fn.fixed
new file mode 100644 (file)
index 0000000..84c02bb
--- /dev/null
@@ -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() {}
diff --git a/tests/ui/manual_async_fn.rs b/tests/ui/manual_async_fn.rs
new file mode 100644 (file)
index 0000000..bd5f9d1
--- /dev/null
@@ -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 {
+    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() {}
diff --git a/tests/ui/manual_async_fn.stderr b/tests/ui/manual_async_fn.stderr
new file mode 100644 (file)
index 0000000..016fdf9
--- /dev/null
@@ -0,0 +1,93 @@
+error: this function can be simplified using async syntax
+  --> $DIR/manual_async_fn.rs:8:1
+   |
+LL | fn fut() -> impl Future<Output = i32> {
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = note: `-D clippy::manual-async-fn` implied by `-D warnings`
+help: make the function `async` and return the output of the future directly
+   |
+LL | async fn fut() -> i32 {
+   | ^^^^^^^^^^^^^^^^^^^^^
+help: move the body of the async block to the enclosing function
+   |
+LL | fn fut() -> impl Future<Output = i32> { 42 }
+   |                                       ^^^^^^
+
+error: this function can be simplified using async syntax
+  --> $DIR/manual_async_fn.rs:12:1
+   |
+LL | fn empty_fut() -> impl Future<Output = ()> {
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+help: make the function `async` and remove the return type
+   |
+LL | async fn empty_fut()  {
+   | ^^^^^^^^^^^^^^^^^^^^
+help: move the body of the async block to the enclosing function
+   |
+LL | fn empty_fut() -> impl Future<Output = ()> {}
+   |                                            ^^
+
+error: this function can be simplified using async syntax
+  --> $DIR/manual_async_fn.rs:16:1
+   |
+LL | fn core_fut() -> impl core::future::Future<Output = i32> {
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+help: make the function `async` and return the output of the future directly
+   |
+LL | async fn core_fut() -> i32 {
+   | ^^^^^^^^^^^^^^^^^^^^^^^^^^
+help: move the body of the async block to the enclosing function
+   |
+LL | fn core_fut() -> impl core::future::Future<Output = i32> { 42 }
+   |                                                          ^^^^^^
+
+error: this function can be simplified using async syntax
+  --> $DIR/manual_async_fn.rs:38:5
+   |
+LL |     fn inh_fut() -> impl Future<Output = i32> {
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+help: make the function `async` and return the output of the future directly
+   |
+LL |     async fn inh_fut() -> i32 {
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^
+help: move the body of the async block to the enclosing function
+   |
+LL |     fn inh_fut() -> impl Future<Output = i32> { 42 }
+   |                                               ^^^^^^
+
+error: this function can be simplified using async syntax
+  --> $DIR/manual_async_fn.rs:42:5
+   |
+LL |     fn meth_fut(&self) -> impl Future<Output = i32> {
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+help: make the function `async` and return the output of the future directly
+   |
+LL |     async fn meth_fut(&self) -> i32 {
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+help: move the body of the async block to the enclosing function
+   |
+LL |     fn meth_fut(&self) -> impl Future<Output = i32> { 42 }
+   |                                                     ^^^^^^
+
+error: this function can be simplified using async syntax
+  --> $DIR/manual_async_fn.rs:46:5
+   |
+LL |     fn empty_fut(&self) -> impl Future<Output = ()> {
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   |
+help: make the function `async` and remove the return type
+   |
+LL |     async fn empty_fut(&self)  {
+   |     ^^^^^^^^^^^^^^^^^^^^^^^^^
+help: move the body of the async block to the enclosing function
+   |
+LL |     fn empty_fut(&self) -> impl Future<Output = ()> {}
+   |                                                     ^^
+
+error: aborting due to 6 previous errors
+