bevy_lint/lints/suspicious/
iter_current_update_messages.rs

1//! Checks for calls to `Messages::<T>::iter_current_update_messages()`.
2//!
3//! # Motivation
4//!
5//! `Messages::<T>::iter_current_update_messages()` lets you read all of the current messages since
6//! `Messages::<T>::update()` was last called, similar to `MessageReader<T>`. Unlike
7//! `MessageReader<T>`, `iter_current_update_messages()` does not track which messages have already
8//! been read. As such, `iter_current_update_messages()` is highly discouraged because it may skip
9//! messages or yield them multiple times.
10//!
11//! # Example
12//!
13//! ```
14//! # use bevy::prelude::*;
15//! #
16//! #[derive(Message)]
17//! struct MyMessage;
18//!
19//! fn my_system(messages: Res<Messages<MyMessage>>) {
20//!     for message in messages.iter_current_update_messages() {
21//!         // ...
22//!     }
23//! }
24//! ```
25//!
26//! Use instead:
27//!
28//! ```
29//! # use bevy::prelude::*;
30//! #
31//! #[derive(Message)]
32//! struct MyMessage;
33//!
34//! fn my_system(mut messages: MessageReader<MyMessage>) {
35//!     for message in messages.read() {
36//!         // ...
37//!     }
38//! }
39//! ```
40
41use clippy_utils::diagnostics::span_lint_and_help;
42use rustc_hir::Expr;
43use rustc_lint::{LateContext, LateLintPass};
44
45use crate::{declare_bevy_lint, declare_bevy_lint_pass, sym, utils::method_call::MethodCall};
46
47declare_bevy_lint! {
48    pub(crate) ITER_CURRENT_UPDATE_MESSAGES,
49    super::Suspicious,
50    "called `Messages::<T>::iter_current_update_messages()`",
51}
52
53declare_bevy_lint_pass! {
54    pub(crate) IterCurrentUpdateMessages => [ITER_CURRENT_UPDATE_MESSAGES],
55}
56
57impl<'tcx> LateLintPass<'tcx> for IterCurrentUpdateMessages {
58    fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) {
59        // If this expression was generated by an external macro, don't lint it.
60        if expr.span.in_external_macro(cx.tcx.sess.source_map()) {
61            return;
62        }
63
64        if let Some(method_call) = MethodCall::try_from(cx, expr) {
65            // Find the adjusted type of the receiver. Type adjustment does things like
66            // auto-dereference and type coercion. In this example, we use the adjusted type so
67            // that we can also handle `Res<Message<T>>`.
68            //
69            // ```
70            // fn plain(messages: Messages<T>) {
71            //     // Original type is `Messages<T>`, adjusted type is `Messages<T>`.
72            //     let _ = messages.iter_current_update_messages();
73            // }
74            //
75            // fn res(messages: Res<Messages<T>>) {
76            //     // Original type is `Res<Messages<T>>`, adjusted type is `Messages<T>`.
77            //     let _ = messages.iter_current_update_messages();
78            // }
79            // ```
80            let src_ty = cx
81                .typeck_results()
82                .expr_ty_adjusted(method_call.receiver)
83                .peel_refs();
84
85            // Events is deprecated in favor of `Messages` in bevy `0.17`
86            if !(crate::paths::EVENTS.matches_ty(cx, src_ty)
87                || crate::paths::MESSAGES.matches_ty(cx, src_ty))
88            {
89                return;
90            }
91
92            if method_call.method_path.ident.name == sym::iter_current_update_messages {
93                span_lint_and_help(
94                    cx,
95                    ITER_CURRENT_UPDATE_MESSAGES,
96                    method_call.span,
97                    ITER_CURRENT_UPDATE_MESSAGES.desc,
98                    None,
99                    "`iter_current_update_messages()` does not track which messages have already been seen, consider using `MessageReader<T>` instead",
100                );
101            }
102        }
103    }
104}