bevy_lint/lints/restriction/
schedule.rs

1//! Checks for adding systems to a disallowed schedule.
2//!
3//! - `fixed_update_schedule`: Disallows using the `FixedUpdate` schedule.
4//! - `update_schedule`: Disallows using the `Update` schedule.
5//!
6//! # Motivation
7//!
8//! Often, projects will prefer certain systems be in either the `Update` or `FixedUpdate`
9//! schedule. These two lints are useful for denying an unwanted schedule throughout entire
10//! modules. For example, a project may deny the `Update` schedule in a module that only contains
11//! physics logic, or deny the `FixedUpdate` schedule in a module that likewise contains only
12//! rendering code.
13//!
14//! # Example
15//!
16//! ```
17//! # #![feature(register_tool)]
18//! # #![register_tool(bevy)]
19//! mod physics {
20//!     #![warn(bevy::update_schedule)]
21//! #
22//! #     use bevy::prelude::*;
23//!
24//!     fn plugin(app: &mut App) {
25//!         // This isn't allowed, use `FixedUpdate` instead!
26//!         app.add_systems(Update, my_system);
27//!     }
28//!
29//!     fn my_system() {
30//!         // ...
31//!     }
32//! }
33//! ```
34//!
35//! Use instead:
36//!
37//! ```
38//! # #![feature(register_tool)]
39//! # #![register_tool(bevy)]
40//! mod physics {
41//!     #![warn(bevy::update_schedule)]
42//! #
43//! #     use bevy::prelude::*;
44//!
45//!     fn plugin(app: &mut App) {
46//!         app.add_systems(FixedUpdate, my_system);
47//!     }
48//!
49//!     fn my_system() {
50//!         // ...
51//!     }
52//! }
53//! ```
54
55use clippy_utils::diagnostics::span_lint_hir;
56use rustc_lint::{LateContext, LateLintPass, Lint};
57use rustc_middle::ty::Ty;
58
59use crate::{declare_bevy_lint, declare_bevy_lint_pass, sym, utils::hir_parse::MethodCall};
60
61declare_bevy_lint! {
62    pub(crate) UPDATE_SCHEDULE,
63    super::Restriction,
64    "defined a system in the `FixedUpdate` schedule",
65}
66
67declare_bevy_lint! {
68    pub(crate) FIXED_UPDATE_SCHEDULE,
69    super::Restriction,
70    "defined a system in the `Update` schedule",
71}
72
73declare_bevy_lint_pass! {
74    pub(crate) Schedule => [UPDATE_SCHEDULE, FIXED_UPDATE_SCHEDULE],
75}
76
77impl<'tcx> LateLintPass<'tcx> for Schedule {
78    fn check_expr(
79        &mut self,
80        cx: &rustc_lint::LateContext<'tcx>,
81        expr: &'tcx rustc_hir::Expr<'tcx>,
82    ) {
83        if expr.span.in_external_macro(cx.tcx.sess.source_map()) {
84            return;
85        }
86
87        let Some(MethodCall {
88            method_path,
89            args,
90            receiver,
91            ..
92        }) = MethodCall::try_from(cx, expr)
93        else {
94            return;
95        };
96
97        let receiver_ty = cx.typeck_results().expr_ty_adjusted(receiver).peel_refs();
98
99        // Match calls to `App::add_systems(schedule, systems)`
100        if !crate::paths::APP.matches_ty(cx, receiver_ty)
101            || method_path.ident.name != sym::add_systems
102        {
103            return;
104        }
105
106        // First argument must be the `ScheduleLabel`
107        let Some(schedule_label) = args.first() else {
108            return;
109        };
110
111        let schedule_ty = cx.typeck_results().expr_ty_adjusted(schedule_label);
112
113        let Some(schedule_type) = ScheduleType::try_from_ty(cx, schedule_ty) else {
114            return;
115        };
116
117        span_lint_hir(
118            cx,
119            schedule_type.lint(),
120            schedule_label.hir_id,
121            schedule_label.span,
122            format!("the `{}` schedule is disallowed", schedule_type.name()),
123        );
124    }
125}
126
127enum ScheduleType {
128    FixedUpdate,
129    Update,
130}
131
132impl ScheduleType {
133    /// Returns the corresponding variant for the given [`Ty`], if it is supported by this lint.
134    fn try_from_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<Self> {
135        if crate::paths::FIXED_UPDATE.matches_ty(cx, ty) {
136            Some(Self::FixedUpdate)
137        } else if crate::paths::UPDATE.matches_ty(cx, ty) {
138            Some(Self::Update)
139        } else {
140            None
141        }
142    }
143
144    fn name(&self) -> &'static str {
145        match self {
146            ScheduleType::FixedUpdate => "FixedUpdate",
147            ScheduleType::Update => "Update",
148        }
149    }
150
151    fn lint(&self) -> &'static Lint {
152        match self {
153            Self::FixedUpdate => FIXED_UPDATE_SCHEDULE,
154            Self::Update => UPDATE_SCHEDULE,
155        }
156    }
157}