Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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 @@ -7091,6 +7091,7 @@ Released 2018-09-13
[`unstable_as_mut_slice`]: https://rust-lang.github.io/rust-clippy/master/index.html#unstable_as_mut_slice
[`unstable_as_slice`]: https://rust-lang.github.io/rust-clippy/master/index.html#unstable_as_slice
[`unused_async`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_async
[`unused_async_trait_impl`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_async_trait_impl
[`unused_collect`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_collect
[`unused_enumerate_index`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_enumerate_index
[`unused_format_specs`]: https://rust-lang.github.io/rust-clippy/master/index.html#unused_format_specs
Expand Down
1 change: 1 addition & 0 deletions clippy_lints/src/declared_lints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -770,6 +770,7 @@ pub static LINTS: &[&::declare_clippy_lint::LintInfo] = &[
crate::unnested_or_patterns::UNNESTED_OR_PATTERNS_INFO,
crate::unsafe_removed_from_name::UNSAFE_REMOVED_FROM_NAME_INFO,
crate::unused_async::UNUSED_ASYNC_INFO,
crate::unused_async_trait_impl::UNUSED_ASYNC_TRAIT_IMPL_INFO,
crate::unused_io_amount::UNUSED_IO_AMOUNT_INFO,
crate::unused_peekable::UNUSED_PEEKABLE_INFO,
crate::unused_result_ok::UNUSED_RESULT_OK_INFO,
Expand Down
2 changes: 2 additions & 0 deletions clippy_lints/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,7 @@ mod unneeded_struct_pattern;
mod unnested_or_patterns;
mod unsafe_removed_from_name;
mod unused_async;
mod unused_async_trait_impl;
mod unused_io_amount;
mod unused_peekable;
mod unused_result_ok;
Expand Down Expand Up @@ -861,6 +862,7 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co
Box::new(move |_| Box::new(manual_ilog2::ManualIlog2::new(conf))),
Box::new(|_| Box::new(same_length_and_capacity::SameLengthAndCapacity)),
Box::new(move |tcx| Box::new(duration_suboptimal_units::DurationSuboptimalUnits::new(tcx, conf))),
Box::new(|_| Box::new(unused_async_trait_impl::UnusedAsyncTraitImpl)),
// add late passes here, used by `cargo dev new_lint`
];
store.late_passes.extend(late_lints);
Expand Down
184 changes: 184 additions & 0 deletions clippy_lints/src/unused_async_trait_impl.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
use clippy_utils::diagnostics::span_lint_hir_and_then;
use clippy_utils::is_def_id_trait_method;
use clippy_utils::source::SpanRangeExt;
use clippy_utils::usage::is_todo_unimplemented_stub;
use rustc_errors::Applicability;
use rustc_hir::intravisit::{FnKind, Visitor, walk_expr, walk_fn};
use rustc_hir::{
Body, Closure, ClosureKind, CoroutineDesugaring, CoroutineKind, Defaultness, Expr, ExprKind, FnDecl, IsAsync, Node,
TraitItem, YieldSource,
};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::hir::nested_filter;
use rustc_session::impl_lint_pass;
use rustc_span::Span;
use rustc_span::def_id::LocalDefId;

declare_clippy_lint! {
/// ### What it does
/// Checks for trait function implementations that are declared `async` but have no `.await`s inside of them.
///
/// ### Why is this bad?
/// Async functions with no async code create computational overhead.
/// Even though the trait requires the function to return a future,
/// returning a `core::future::ready` with the result is more efficient.
///
/// ### Example
/// ```no_run
/// trait AsyncTrait {
/// async fn get_random_number() -> i64;
/// }
///
/// impl AsyncTrait for () {
/// async fn get_random_number() -> i64 {
/// 4 // Chosen by fair dice roll. Guaranteed to be random.
/// }
/// }
/// ```
///
/// Use instead:
/// ```no_run
/// trait AsyncTrait {
/// async fn get_random_number() -> i64;
/// }
///
/// impl AsyncTrait for () {
/// fn get_random_number() -> impl Future<Output = i64> {
/// core::future::ready(4) // Chosen by fair dice roll. Guaranteed to be random.
/// }
/// }
/// ```
///
/// ### Note
/// An `async` block generates code that defers execution until the Future is polled.
/// When using `core::future::ready` the code is executed immediately.
#[clippy::version = "1.94.0"]
pub UNUSED_ASYNC_TRAIT_IMPL,
pedantic,
"finds async trait impl functions with no await statements"
}

pub struct UnusedAsyncTraitImpl;

impl_lint_pass!(UnusedAsyncTraitImpl => [UNUSED_ASYNC_TRAIT_IMPL]);

struct AsyncFnVisitor<'a, 'tcx> {
cx: &'a LateContext<'tcx>,
found_await: bool,
async_depth: usize,
}

impl<'tcx> Visitor<'tcx> for AsyncFnVisitor<'_, 'tcx> {
type NestedFilter = nested_filter::OnlyBodies;

fn visit_expr(&mut self, ex: &'tcx Expr<'tcx>) {
if let ExprKind::Yield(_, YieldSource::Await { .. }) = ex.kind
&& self.async_depth == 1
{
self.found_await = true;
}

let is_async_block = matches!(
ex.kind,
ExprKind::Closure(Closure {
kind: ClosureKind::Coroutine(CoroutineKind::Desugared(CoroutineDesugaring::Async, _)),
..
})
);

if is_async_block {
self.async_depth += 1;
}

walk_expr(self, ex);

if is_async_block {
self.async_depth -= 1;
}
}

fn maybe_tcx(&mut self) -> Self::MaybeTyCtxt {
self.cx.tcx
}
}

impl<'tcx> LateLintPass<'tcx> for UnusedAsyncTraitImpl {
fn check_fn(
&mut self,
cx: &LateContext<'tcx>,
fn_kind: FnKind<'tcx>,
fn_decl: &'tcx FnDecl<'tcx>,
body: &Body<'tcx>,
span: Span,
def_id: LocalDefId,
) {
if let IsAsync::Async(async_span) = fn_kind.asyncness()
&& !span.from_expansion()
&& is_def_id_trait_method(cx, def_id)
&& !is_default_trait_impl(cx, def_id)
&& !async_fn_contains_todo_unimplemented_macro(cx, body)
{
let mut visitor = AsyncFnVisitor {
cx,
found_await: false,
async_depth: 0,
};
walk_fn(&mut visitor, fn_kind, fn_decl, body.id(), def_id);
if !visitor.found_await {
span_lint_hir_and_then(
cx,
UNUSED_ASYNC_TRAIT_IMPL,
cx.tcx.local_def_id_to_hir_id(def_id),
span,
"unused `async` for async trait impl function with no await statements",
|diag| {
if let Some(output_src) = fn_decl.output.span().get_source_text(cx)
&& let Some(body_src) = body.value.span.get_source_text(cx)
{
let output_str = output_src.as_str();
let body_str = body_src.as_str();

let sugg = vec![
(async_span, String::new()),
(fn_decl.output.span(), format!("impl Future<Output = {output_str}>")),
(body.value.span, format!("{{ core::future::ready({body_str}) }}")),
];

diag.help("a Future can be constructed from the return value with `core::future::ready`");
diag.multipart_suggestion(
format!("consider removing the `async` from this function and returning `impl Future<Output = {output_str}>` instead"),
sugg,
Applicability::MachineApplicable
);
}
},
);
}
}
}
}

fn is_default_trait_impl(cx: &LateContext<'_>, def_id: LocalDefId) -> bool {
matches!(
cx.tcx.hir_node_by_def_id(def_id),
Node::TraitItem(TraitItem {
defaultness: Defaultness::Default { .. },
..
})
)
}

fn async_fn_contains_todo_unimplemented_macro(cx: &LateContext<'_>, body: &Body<'_>) -> bool {
if let ExprKind::Closure(closure) = body.value.kind
&& let ClosureKind::Coroutine(CoroutineKind::Desugared(CoroutineDesugaring::Async, _)) = closure.kind
&& let body = cx.tcx.hir_body(closure.body)
&& let ExprKind::Block(block, _) = body.value.kind
&& block.stmts.is_empty()
&& let Some(expr) = block.expr
&& let ExprKind::DropTemps(inner) = expr.kind
{
return is_todo_unimplemented_stub(cx, inner);
}

false
}
26 changes: 26 additions & 0 deletions tests/ui/unused_async_trait_impl.fixed
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#![warn(clippy::unused_async_trait_impl)]

trait HasAsyncMethod {
async fn do_something() -> u32;
}

struct Inefficient;
struct Efficient;

impl HasAsyncMethod for Inefficient {
fn do_something() -> impl Future<Output = u32> { core::future::ready({
//~^ unused_async_trait_impl
1
}) }
}

impl HasAsyncMethod for Efficient {
fn do_something() -> impl Future<Output = u32> {
core::future::ready(1)
}
}

fn main() {
Inefficient::do_something();
Efficient::do_something();
}
26 changes: 26 additions & 0 deletions tests/ui/unused_async_trait_impl.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#![warn(clippy::unused_async_trait_impl)]

trait HasAsyncMethod {
async fn do_something() -> u32;
}

struct Inefficient;
struct Efficient;

impl HasAsyncMethod for Inefficient {
async fn do_something() -> u32 {
//~^ unused_async_trait_impl
1
}
}

impl HasAsyncMethod for Efficient {
fn do_something() -> impl Future<Output = u32> {
core::future::ready(1)
}
}

fn main() {
Inefficient::do_something();
Efficient::do_something();
}
22 changes: 22 additions & 0 deletions tests/ui/unused_async_trait_impl.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
error: unused `async` for async trait impl function with no await statements
--> tests/ui/unused_async_trait_impl.rs:11:5
|
LL | / async fn do_something() -> u32 {
LL | |
LL | | 1
LL | | }
| |_____^
|
= help: a Future can be constructed from the return value with `core::future::ready`
= note: `-D clippy::unused-async-trait-impl` implied by `-D warnings`
= help: to override `-D warnings` add `#[allow(clippy::unused_async_trait_impl)]`
help: consider removing the `async` from this function and returning `impl Future<Output = u32>` instead
|
LL ~ fn do_something() -> impl Future<Output = u32> { core::future::ready({
LL +
LL + 1
LL + }) }
|

error: aborting due to 1 previous error