-
Notifications
You must be signed in to change notification settings - Fork 1.9k
new lint: missing_must_use_on_future_types
#14066
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
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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(); | ||
| /// } | ||
| /// ``` | ||
| #[clippy::version = "1.86.0"] | ||
| pub MISSING_MUST_USE_ON_FUTURE_TYPES, | ||
| style, | ||
rmehri01 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| "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 { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 Not sure about performance tho
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I think this wouldn't work for the
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can't use |
||
| 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() | ||
rmehri01 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| && cx.tcx.get_attr(adt_def.did(), sym::must_use).is_none() | ||
| { | ||
rmehri01 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| 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, | ||
| ); | ||
| }, | ||
| ); | ||
| } | ||
| } | ||
| } | ||
| 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; | ||
| } |
| 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` | ||
rmehri01 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| struct NonFuture; | ||
|
|
||
| // should still trigger if a type only sometimes implements `Future` | ||
| struct SometimesFuture<T>(PhantomData<T>); | ||
|
|
||
| impl<T: Copy> Future for SometimesFuture<T> { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would like to make sure two One idea if they do: Store all the data needed to lint in the struct, then lint in
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just storing the |
||
| 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 {}) | ||
| } | ||
rmehri01 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| // struct with multiple fields and impls | ||
| #[derive(Debug)] | ||
| pub struct ComplexStruct { | ||
| x: usize, | ||
| y: &'static str, | ||
| } | ||
rmehri01 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| 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; | ||
| } | ||
rmehri01 marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
There was a problem hiding this comment.
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 acrossAlso, we can remove
no_runif you add.await, can't we?There was a problem hiding this comment.
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
.awaitafter callingfoo()but yeah I agree this might not be the best example