Skip to content
Closed
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 @@ -5827,6 +5827,7 @@ Released 2018-09-13
[`missing_errors_doc`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_errors_doc
[`missing_fields_in_debug`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_fields_in_debug
[`missing_inline_in_public_items`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_inline_in_public_items
[`missing_must_use_on_future_types`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_must_use_on_future_types
[`missing_panics_doc`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_panics_doc
[`missing_safety_doc`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_safety_doc
[`missing_spin_loop`]: https://rust-lang.github.io/rust-clippy/master/index.html#missing_spin_loop
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 @@ -529,6 +529,7 @@ pub static LINTS: &[&crate::LintInfo] = &[
crate::missing_enforced_import_rename::MISSING_ENFORCED_IMPORT_RENAMES_INFO,
crate::missing_fields_in_debug::MISSING_FIELDS_IN_DEBUG_INFO,
crate::missing_inline::MISSING_INLINE_IN_PUBLIC_ITEMS_INFO,
crate::missing_must_use_on_future_types::MISSING_MUST_USE_ON_FUTURE_TYPES_INFO,
crate::missing_trait_methods::MISSING_TRAIT_METHODS_INFO,
crate::mixed_read_write_in_expression::DIVERGING_SUB_EXPRESSION_INFO,
crate::mixed_read_write_in_expression::MIXED_READ_WRITE_IN_EXPRESSION_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 @@ -243,6 +243,7 @@ mod missing_doc;
mod missing_enforced_import_rename;
mod missing_fields_in_debug;
mod missing_inline;
mod missing_must_use_on_future_types;
mod missing_trait_methods;
mod mixed_read_write_in_expression;
mod module_style;
Expand Down Expand Up @@ -974,5 +975,6 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
store.register_late_pass(move |_| Box::new(arbitrary_source_item_ordering::ArbitrarySourceItemOrdering::new(conf)));
store.register_late_pass(|_| Box::new(unneeded_struct_pattern::UnneededStructPattern));
store.register_late_pass(|_| Box::<unnecessary_semicolon::UnnecessarySemicolon>::default());
store.register_late_pass(|_| Box::new(missing_must_use_on_future_types::MissingMustUseOnFutureTypes));
// add lints here, do not remove this comment, it's used in `new_lint`
}
101 changes: 101 additions & 0 deletions clippy_lints/src/missing_must_use_on_future_types.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
use rustc_errors::Applicability;
use rustc_hir::{Impl, Item, ItemKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty;
use rustc_session::declare_lint_pass;
use rustc_span::sym;

use clippy_utils::diagnostics::span_lint_and_then;

declare_clippy_lint! {
/// ### What it does
/// Checks for a missing `#[must_use]` attribute on types
/// that implement `Future`.
///
/// ### Why is this bad?
/// Futures must be polled for work to be done and it
/// can be easy to accidentally forget.
///
/// ### Example
/// ```no_run
/// struct Foo;
///
/// // impl Future for Foo { ... }
/// # impl Future for Foo {
/// # type Output = ();
/// #
/// # fn poll(
/// # self: std::pin::Pin<&mut Self>,
/// # _cx: &mut std::task::Context<'_>,
/// # ) -> std::task::Poll<Self::Output> {
/// # std::task::Poll::Ready(())
/// # }
/// # }
///
/// fn foo() -> Foo { Foo }
///
/// fn main() {
/// foo();
/// }
/// ```
/// Use instead:
/// ```no_run
/// #[must_use]
/// struct Foo;
///
/// // impl Future for Foo { ... }
/// # impl Future for Foo {
/// # type Output = ();
/// #
/// # fn poll(
/// # self: std::pin::Pin<&mut Self>,
/// # _cx: &mut std::task::Context<'_>,
/// # ) -> std::task::Poll<Self::Output> {
/// # std::task::Poll::Ready(())
/// # }
/// # }
///
/// fn foo() -> Foo { Foo }
///
/// fn main() {
/// foo();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this would be better as foo().await, or something else that shows what the correct code is. A more complex example would be nice, i.e., this example might not get the point across

Also, we can remove no_run if you add .await, can't we?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was trying to show accidentally missing the .await after calling foo() but yeah I agree this might not be the best example

/// }
/// ```
#[clippy::version = "1.86.0"]
pub MISSING_MUST_USE_ON_FUTURE_TYPES,
style,
"missing `#[must_use]` annotation on a type implementing `Future`"
}

declare_lint_pass!(MissingMustUseOnFutureTypes => [MISSING_MUST_USE_ON_FUTURE_TYPES]);

impl<'tcx> LateLintPass<'tcx> for MissingMustUseOnFutureTypes {
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
if let ItemKind::Impl(Impl {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it not be better to start at the type instead and use implements_trait? It seems like it'd be a lot simpler...

Not sure about performance tho

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should also check if we're in an external macro here.

You can add //@aux-build:proc_macros.rs at the top of your test to test macros.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the local macro case, we mostly should ensure that the span points to the right place. This could be problematic

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it not be better to start at the type instead and use implements_trait? It seems like it'd be a lot simpler...

Not sure about performance tho

I think this wouldn't work for the SometimesFuture since it would always need to hold?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm honestly not sure! If not then yeah, this is fine.

I'd look and see anyways because I'm not sure how implements_trait/what it calls handles not passing any arguments, or whether there's a way to make it check for any impl at all (I believe this same code would be used for T: Clone for example). It uses Obligation + InferCtxt if curious (I am).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can't use implements_trait here since there's also potential ambiguity and you wouldn't want to from a perf perspective.

of_trait: Some(trait_ref),
..
}) = item.kind
&& let Some(trait_def_id) = trait_ref.trait_def_id()
&& let Some(future_def_id) = cx.tcx.lang_items().future_trait()
&& trait_def_id == future_def_id
&& let &ty::Adt(adt_def, _) = cx.tcx.type_of(item.owner_id).instantiate_identity().kind()
&& cx.tcx.get_attr(adt_def.did(), sym::must_use).is_none()
{
let adt_span = cx.tcx.def_span(adt_def.did());
span_lint_and_then(
cx,
MISSING_MUST_USE_ON_FUTURE_TYPES,
adt_span,
"this struct implements `Future` but is missing a `#[must_use]` attribute",
|diag| {
diag.span_suggestion(
adt_span.shrink_to_lo(),
"add the attribute",
"#[must_use]\n",
Applicability::MachineApplicable,
);
},
);
}
}
}
2 changes: 1 addition & 1 deletion tests/ui/async_yields_async.fixed
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#![warn(clippy::async_yields_async)]
#![allow(clippy::redundant_async_block)]
#![allow(clippy::redundant_async_block, clippy::missing_must_use_on_future_types)]

use core::future::Future;
use core::pin::Pin;
Expand Down
2 changes: 1 addition & 1 deletion tests/ui/async_yields_async.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#![warn(clippy::async_yields_async)]
#![allow(clippy::redundant_async_block)]
#![allow(clippy::redundant_async_block, clippy::missing_must_use_on_future_types)]

use core::future::Future;
use core::pin::Pin;
Expand Down
1 change: 1 addition & 0 deletions tests/ui/crashes/ice-13862.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#![crate_type = "lib"]
#![no_std]
#![allow(clippy::missing_must_use_on_future_types)]

use core::future::Future;
use core::pin::Pin;
Expand Down
118 changes: 118 additions & 0 deletions tests/ui/missing_must_use_on_future_types.fixed
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
#![warn(clippy::missing_must_use_on_future_types)]
#![allow(path_statements, unused, clippy::no_effect)]

use std::fmt::Display;
use std::future::Future;
use std::marker::PhantomData;
use std::pin::Pin;
use std::task::{Context, Poll};

// basic struct case
#[must_use]
struct BasicStruct;

impl Future for BasicStruct {
type Output = ();

fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Self::Output> {
Poll::Ready(())
}
}

// basic enum case
#[must_use]
enum BasicEnum {
Var1,
Var2,
}

impl Future for BasicEnum {
type Output = ();

fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Self::Output> {
Poll::Ready(())
}
}

// should be ignored if type doesn't implement `Future`
struct NonFuture;

// should still trigger if a type only sometimes implements `Future`
#[must_use]
struct SometimesFuture<T>(PhantomData<T>);

impl<T: Copy> Future for SometimesFuture<T> {
type Output = ();

fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Self::Output> {
Poll::Ready(())
}
}

// should be ignored on trait objects
trait Trait {}

impl Future for dyn Trait {
type Output = ();

fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Self::Output> {
Poll::Ready(())
}
}

struct TraitImpl {}
impl Trait for TraitImpl {}

fn trait_obj() -> Box<dyn Trait> {
Box::new(TraitImpl {})
}

// struct with multiple fields and impls
#[derive(Debug)]
#[must_use]
pub struct ComplexStruct {
x: usize,
y: &'static str,
}

impl ComplexStruct {
fn sum(&self) -> usize {
self.x + self.y.len()
}
}

impl Display for ComplexStruct {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "complex")
}
}

impl Future for ComplexStruct {
type Output = ();

fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Self::Output> {
Poll::Ready(())
}
}

// should be ignored on already #[must_use] struct
#[must_use]
struct AlreadyMustUse;

impl Future for AlreadyMustUse {
type Output = ();

fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Self::Output> {
Poll::Ready(())
}
}

fn main() {
BasicStruct;
BasicEnum::Var2;
NonFuture;
SometimesFuture::<String>(PhantomData);
trait_obj();
ComplexStruct { x: 42, y: "complex" };
AlreadyMustUse;
}
114 changes: 114 additions & 0 deletions tests/ui/missing_must_use_on_future_types.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
#![warn(clippy::missing_must_use_on_future_types)]
#![allow(path_statements, unused, clippy::no_effect)]

use std::fmt::Display;
use std::future::Future;
use std::marker::PhantomData;
use std::pin::Pin;
use std::task::{Context, Poll};

// basic struct case
struct BasicStruct;

impl Future for BasicStruct {
type Output = ();

fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Self::Output> {
Poll::Ready(())
}
}

// basic enum case
enum BasicEnum {
Var1,
Var2,
}

impl Future for BasicEnum {
type Output = ();

fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Self::Output> {
Poll::Ready(())
}
}

// should be ignored if type doesn't implement `Future`
struct NonFuture;

// should still trigger if a type only sometimes implements `Future`
struct SometimesFuture<T>(PhantomData<T>);

impl<T: Copy> Future for SometimesFuture<T> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would like to make sure two Future impls doesn't lint twice.

One idea if they do: Store all the data needed to lint in the struct, then lint in check_crate_post using span_lint_hir_and_then.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just storing the LocalDefId of every linted type will be enough.

type Output = ();

fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Self::Output> {
Poll::Ready(())
}
}

// should be ignored on trait objects
trait Trait {}

impl Future for dyn Trait {
type Output = ();

fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Self::Output> {
Poll::Ready(())
}
}

struct TraitImpl {}
impl Trait for TraitImpl {}

fn trait_obj() -> Box<dyn Trait> {
Box::new(TraitImpl {})
}

// struct with multiple fields and impls
#[derive(Debug)]
pub struct ComplexStruct {
x: usize,
y: &'static str,
}

impl ComplexStruct {
fn sum(&self) -> usize {
self.x + self.y.len()
}
}

impl Display for ComplexStruct {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "complex")
}
}

impl Future for ComplexStruct {
type Output = ();

fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Self::Output> {
Poll::Ready(())
}
}

// should be ignored on already #[must_use] struct
#[must_use]
struct AlreadyMustUse;

impl Future for AlreadyMustUse {
type Output = ();

fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Self::Output> {
Poll::Ready(())
}
}

fn main() {
BasicStruct;
BasicEnum::Var2;
NonFuture;
SometimesFuture::<String>(PhantomData);
trait_obj();
ComplexStruct { x: 42, y: "complex" };
AlreadyMustUse;
}
Loading
Loading