bevy_lint/lints/restriction/
missing_reflect.rs

1//! Checks for components, resources, and events that do not implement `Reflect`.
2//!
3//! # Motivation
4//!
5//! Reflection lets programs inspect type information at runtime. It is commonly used by tools to
6//! view and edit ECS information while the program is running. Reflection is opt-in, however, and
7//! easy to forget since you need to `#[derive(Reflect)]` for each type that uses it.
8//!
9//! # Known issues
10//!
11//! This lint will suggest `#[derive(Reflect)]` even if it cannot be applied. (E.g. if one of the
12//! fields does not implement `Reflect`.) For more information, please see [#141].
13//!
14//! [#141]: https://github.com/TheBevyFlock/bevy_cli/issues/141
15//!
16//! # Example
17//!
18//! ```
19//! # use bevy::prelude::*;
20//! #
21//! #[derive(Component)]
22//! struct MyComponent;
23//! ```
24//!
25//! Use instead:
26//!
27//! ```
28//! # use bevy::prelude::*;
29//! #
30//! // Remember to also register this component in the `App` type registry.
31//! #[derive(Component, Reflect)]
32//! struct MyComponent;
33//! ```
34//!
35//! Often you'll only want to enable this lint for a specific module:
36//!
37//! <!-- We currently ignore this doc test because any reference to `bevy_lint` causes it to be
38//! linked, which raises a compile error due to the linter's use of `rustc_private`. -->
39//!
40//! ```ignore
41//! mod types {
42//!     #![cfg_attr(bevy_lint, warn(bevy::missing_reflect))]
43//! #
44//! #   use bevy::prelude::*;
45//!
46//!     #[derive(Resource, Reflect)]
47//!     struct Score(u32);
48//!
49//!     #[derive(Component, Reflect)]
50//!     struct Happiness(i8);
51//! }
52//! ```
53//!
54//! For more information, please see [Toggling Lints in
55//! Code](../../index.html#toggling-lints-in-code).
56
57use crate::{declare_bevy_lint, declare_bevy_lint_pass};
58use clippy_utils::{
59    def_path_res,
60    diagnostics::span_lint_hir_and_then,
61    get_trait_def_id,
62    sugg::DiagExt,
63    ty::{implements_trait, ty_from_hir_ty},
64};
65use rustc_errors::Applicability;
66use rustc_hir::{
67    HirId, Item, ItemKind, Node, OwnerId, QPath, TyKind,
68    def::{DefKind, Res},
69};
70use rustc_lint::{LateContext, LateLintPass};
71use rustc_middle::{span_bug, ty::TyCtxt};
72use rustc_span::Span;
73
74declare_bevy_lint! {
75    pub MISSING_REFLECT,
76    super::Restriction,
77    "defined a component, resource, or event without a `Reflect` implementation",
78    // We only override `check_crate()`.
79    @crate_level_only = true,
80}
81
82declare_bevy_lint_pass! {
83    pub MissingReflect => [MISSING_REFLECT],
84}
85
86impl<'tcx> LateLintPass<'tcx> for MissingReflect {
87    fn check_crate(&mut self, cx: &LateContext<'tcx>) {
88        // Finds all types that implement `Reflect` in this crate.
89        let reflected: Vec<TraitType> =
90            TraitType::from_local_crate(cx.tcx, &crate::paths::REFLECT).collect();
91
92        // Finds all non-`Reflect` types that implement `Event` in this crate.
93        let events: Vec<TraitType> = TraitType::from_local_crate(cx.tcx, &crate::paths::EVENT)
94            .filter(|trait_type| !reflected.contains(trait_type))
95            .collect();
96
97        // Finds all non-`Reflect` types that implement `Component` and *not* `Event` in this
98        // crate. Because events are also components, we need to deduplicate the two to avoid
99        // emitting multiple diagnostics for the same type.
100        let components: Vec<TraitType> =
101            TraitType::from_local_crate(cx.tcx, &crate::paths::COMPONENT)
102                .filter(|trait_type| {
103                    !(reflected.contains(trait_type) || events.contains(trait_type))
104                })
105                .collect();
106
107        // Finds all non-`Reflect` types that implement `Resource` in this crate.
108        let resources: Vec<TraitType> =
109            TraitType::from_local_crate(cx.tcx, &crate::paths::RESOURCE)
110                .filter(|trait_type| !reflected.contains(trait_type))
111                .collect();
112
113        // This is an expensive function that is purposefully called outside of the `for` loop. Note
114        // that this will only return `None` if `PartialReflect` does not exist (e.g. `bevy_reflect`
115        // is not available.)
116        let Some(reflect_trait_def_id) = get_trait_def_id(cx.tcx, &crate::paths::PARTIAL_REFLECT)
117        else {
118            return;
119        };
120        // Emit diagnostics for each of these types.
121        for (checked_trait, trait_name, message_phrase) in [
122            (events, "Event", "an event"),
123            (components, "Component", "a component"),
124            (resources, "Resource", "a resource"),
125        ] {
126            for without_reflect in checked_trait {
127                // Skip if a types originates from a foreign crate's macro
128                if without_reflect
129                    .item_span
130                    .in_external_macro(cx.tcx.sess.source_map())
131                {
132                    continue;
133                }
134
135                // This lint is machine applicable unless any of the struct's fields do not
136                // implement `PartialReflect`.
137                let mut applicability = Applicability::MachineApplicable;
138
139                // Find the `Item` definition of the struct missing `#[derive(Reflect)]`. We can use
140                // `expect_owner()` because the HIR ID was originally created from a `LocalDefId`,
141                // and we can use `expect_item()` because `TraitType::from_local_crate()` only
142                // returns items.
143                let without_reflect_item = cx
144                    .tcx
145                    .hir_expect_item(without_reflect.hir_id.expect_owner().def_id);
146
147                // Extract a list of all fields within the structure definition.
148                let fields = match without_reflect_item.kind {
149                    ItemKind::Struct(_, data, _) => data.fields().to_vec(),
150                    ItemKind::Enum(_, enum_def, _) => enum_def
151                        .variants
152                        .iter()
153                        .flat_map(|variant| variant.data.fields())
154                        .copied()
155                        .collect(),
156                    // Unions are explicitly unsupported by `#[derive(Reflect)]`, so we don't even
157                    // both checking the fields and just set the applicability to "maybe incorrect".
158                    ItemKind::Union(..) => {
159                        applicability = Applicability::MaybeIncorrect;
160                        Vec::new()
161                    }
162                    // This shouldn't be possible, as only structs, enums, and unions can implement
163                    // traits, so panic if this branch is reached.
164                    _ => span_bug!(
165                        without_reflect.item_span,
166                        "found a type that implements `Event`, `Component`, or `Resource` but is not a struct, enum, or union",
167                    ),
168                };
169
170                for field in fields {
171                    let ty = ty_from_hir_ty(cx, field.ty);
172
173                    // Check if the field's type implements the `PartialReflect` trait. If it does
174                    // not, change the `Applicability` level to `MaybeIncorrect` because `Reflect`
175                    // cannot be automatically derived.
176                    if !implements_trait(cx, ty, reflect_trait_def_id, &[]) {
177                        applicability = Applicability::MaybeIncorrect;
178                        break;
179                    }
180                }
181
182                span_lint_hir_and_then(
183                    cx,
184                    MISSING_REFLECT,
185                    // This tells `rustc` where to search for `#[allow(...)]` attributes.
186                    without_reflect.hir_id,
187                    without_reflect.item_span,
188                    format!("defined {message_phrase} without a `Reflect` implementation"),
189                    |diag| {
190                        diag.span_note(
191                            without_reflect.impl_span,
192                            format!("`{trait_name}` implemented here"),
193                        )
194                        .suggest_item_with_attr(
195                            cx,
196                            without_reflect.item_span,
197                            "`Reflect` can be automatically derived",
198                            "#[derive(Reflect)]",
199                            // This suggestion may result in two consecutive
200                            // `#[derive(...)]` attributes, but `rustfmt` merges them
201                            // afterwards.
202                            applicability,
203                        );
204                    },
205                );
206            }
207        }
208    }
209}
210
211/// Represents a type that implements a specific trait.
212#[derive(Debug)]
213struct TraitType {
214    /// The [`HirId`] pointing to the type item declaration.
215    hir_id: HirId,
216    /// The span where the type was declared.
217    item_span: Span,
218    /// The span where the trait was implemented.
219    impl_span: Span,
220}
221
222impl TraitType {
223    fn from_local_crate<'tcx>(
224        tcx: TyCtxt<'tcx>,
225        trait_path: &[&str],
226    ) -> impl Iterator<Item = Self> + use<'tcx> {
227        // Find the `DefId` of the trait. There may be multiple if there are multiple versions of
228        // the same crate.
229        let trait_def_ids = def_path_res(tcx, trait_path)
230            .into_iter()
231            .filter_map(|res| match res {
232                Res::Def(DefKind::Trait, def_id) => Some(def_id),
233                _ => None,
234            });
235
236        // Find a map of all trait `impl` items within the current crate. The key is the `DefId` of
237        // the trait, and the value is a `Vec<LocalDefId>` for all `impl` items.
238        let all_trait_impls = tcx.all_local_trait_impls(());
239
240        // Find all `impl` items for the specific trait.
241        let trait_impls = trait_def_ids
242            .filter_map(|def_id| all_trait_impls.get(&def_id))
243            .flatten()
244            .copied();
245
246        // Map the `DefId`s of `impl` items to `TraitType`s. Sometimes this conversion can fail, so
247        // we use `filter_map()` to skip errors.
248        trait_impls.filter_map(move |local_def_id| {
249            // Retrieve the node of the `impl` item from its `DefId`.
250            let node = tcx.hir_node_by_def_id(local_def_id);
251
252            // Verify that it's an `impl` item and not something else.
253            let Node::Item(Item {
254                kind: ItemKind::Impl(impl_),
255                span: impl_span,
256                ..
257            }) = node
258            else {
259                return None;
260            };
261
262            // Find where `T` in `impl T` was originally defined, after peeling away all references
263            // `&`. This was adapted from `clippy_utils::path_res()` in order to avoid passing
264            // `LateContext` to this function.
265            let def_id = match impl_.self_ty.peel_refs().kind {
266                TyKind::Path(QPath::Resolved(_, path)) => path.res.opt_def_id()?,
267                _ => return None,
268            };
269
270            // Tries to convert the `DefId` to a `LocalDefId`, exiting early if it cannot be done.
271            // This will only work if `T` in `impl T` is defined within the same crate.
272            //
273            // In most cases this will succeed due to Rust's orphan rule, but it notably fails
274            // within `bevy_reflect` itself, since that crate implements `Reflect` for `std` types
275            // such as `String`.
276            let local_def_id = def_id.as_local()?;
277
278            // Find the `HirId` from the `LocalDefId`. This is like a `DefId`, but with further
279            // constraints on what it can represent.
280            let hir_id = OwnerId {
281                def_id: local_def_id,
282            }
283            .into();
284
285            // Find the span where the type was declared. This is guaranteed to be an item, so we
286            // can safely call `expect_item()` without it panicking.
287            let item_span = tcx.hir_node(hir_id).expect_item().span;
288
289            Some(TraitType {
290                hir_id,
291                item_span,
292                impl_span: *impl_span,
293            })
294        })
295    }
296}
297
298/// A custom equality implementation that just checks the [`HirId`] of the [`TraitType`], and skips
299/// the other values.
300///
301/// [`TraitType`]s with equal [`HirId`]s are guaranteed to be equal in all other fields, so this
302/// takes advantage of that fact.
303impl PartialEq for TraitType {
304    fn eq(&self, other: &Self) -> bool {
305        self.hir_id == other.hir_id
306    }
307}