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 clippy_utils::{
58    diagnostics::span_lint_hir_and_then,
59    sugg::DiagExt,
60    ty::{implements_trait, ty_from_hir_ty},
61};
62use rustc_errors::Applicability;
63use rustc_hir::ItemKind;
64use rustc_lint::{LateContext, LateLintPass};
65
66use crate::{
67    declare_bevy_lint, declare_bevy_lint_pass, span_unreachable, utils::traits::TraitType,
68};
69
70declare_bevy_lint! {
71    pub(crate) MISSING_REFLECT,
72    super::Restriction,
73    "defined a component, resource, or event without a `Reflect` implementation",
74    // We only override `check_crate()`.
75    @crate_level_only = true,
76}
77
78declare_bevy_lint_pass! {
79    pub(crate) MissingReflect => [MISSING_REFLECT],
80}
81
82impl<'tcx> LateLintPass<'tcx> for MissingReflect {
83    fn check_crate(&mut self, cx: &LateContext<'tcx>) {
84        // Finds all types that implement `Reflect` in this crate.
85        let reflected: Vec<TraitType> =
86            TraitType::from_local_crate(cx, &crate::paths::REFLECT).collect();
87
88        // Finds all non-`Reflect` types that implement `Event` in this crate.
89        let events: Vec<TraitType> = TraitType::from_local_crate(cx, &crate::paths::EVENT)
90            .filter(|trait_type| !reflected.contains(trait_type))
91            .collect();
92
93        // Finds all non-`Reflect` types that implement `Message` in this crate.
94        let messages: Vec<TraitType> = TraitType::from_local_crate(cx, &crate::paths::MESSAGE)
95            .filter(|trait_type| !reflected.contains(trait_type))
96            .collect();
97
98        // Finds all non-`Reflect` types that implement `Component` in this crate.
99        let components: Vec<TraitType> = TraitType::from_local_crate(cx, &crate::paths::COMPONENT)
100            .filter(|trait_type| !reflected.contains(trait_type))
101            .collect();
102
103        // Finds all non-`Reflect` types that implement `Resource` in this crate.
104        let resources: Vec<TraitType> = TraitType::from_local_crate(cx, &crate::paths::RESOURCE)
105            .filter(|trait_type| !reflected.contains(trait_type))
106            .collect();
107
108        let reflect_trait_def_ids = crate::paths::PARTIAL_REFLECT.get(cx);
109
110        // Emit diagnostics for each of these types.
111        for (checked_trait, trait_name, message_phrase) in [
112            (events, "Event", "an event"),
113            (messages, "Message", "a message"),
114            (components, "Component", "a component"),
115            (resources, "Resource", "a resource"),
116        ] {
117            for without_reflect in checked_trait {
118                // Skip if a types originates from a foreign crate's macro
119                if without_reflect
120                    .item_span
121                    .in_external_macro(cx.tcx.sess.source_map())
122                {
123                    continue;
124                }
125
126                // This lint is machine applicable unless any of the struct's fields do not
127                // implement `PartialReflect`.
128                let mut applicability = Applicability::MachineApplicable;
129
130                // Find the `Item` definition of the struct missing `#[derive(Reflect)]`. We can use
131                // `expect_owner()` because the HIR ID was originally created from a `LocalDefId`,
132                // and we can use `expect_item()` because `TraitType::from_local_crate()` only
133                // returns items.
134                let without_reflect_item = cx
135                    .tcx
136                    .hir_expect_item(without_reflect.hir_id.expect_owner().def_id);
137
138                // Extract a list of all fields within the structure definition.
139                let fields = match without_reflect_item.kind {
140                    ItemKind::Struct(_, _, data) => data.fields().to_vec(),
141                    ItemKind::Enum(_, _, enum_def) => enum_def
142                        .variants
143                        .iter()
144                        .flat_map(|variant| variant.data.fields())
145                        .copied()
146                        .collect(),
147                    // Unions are explicitly unsupported by `#[derive(Reflect)]`, so we don't even
148                    // both checking the fields and just set the applicability to "maybe incorrect".
149                    ItemKind::Union(..) => {
150                        applicability = Applicability::MaybeIncorrect;
151                        Vec::new()
152                    }
153                    // This shouldn't be possible, as only structs, enums, and unions can implement
154                    // traits, so panic if this branch is reached.
155                    _ => span_unreachable!(
156                        without_reflect.item_span,
157                        "found a type that implements `Event`, `Component`, `Message`, or `Resource` but is not a struct, enum, or union",
158                    ),
159                };
160
161                for field in fields {
162                    let ty = ty_from_hir_ty(cx, field.ty);
163
164                    // Check if the field's type implements the `PartialReflect` trait. If it does
165                    // not, change the `Applicability` level to `MaybeIncorrect` because `Reflect`
166                    // cannot be automatically derived.
167                    if !reflect_trait_def_ids
168                        .iter()
169                        .any(|&trait_id| implements_trait(cx, ty, trait_id, &[]))
170                    {
171                        applicability = Applicability::MaybeIncorrect;
172                        break;
173                    }
174                }
175
176                span_lint_hir_and_then(
177                    cx,
178                    MISSING_REFLECT,
179                    // This tells `rustc` where to search for `#[allow(...)]` attributes.
180                    without_reflect.hir_id,
181                    without_reflect.item_span,
182                    format!("defined {message_phrase} without a `Reflect` implementation"),
183                    |diag| {
184                        diag.span_note(
185                            without_reflect.impl_span,
186                            format!("`{trait_name}` implemented here"),
187                        )
188                        .suggest_item_with_attr(
189                            cx,
190                            without_reflect.item_span,
191                            "`Reflect` can be automatically derived",
192                            "#[derive(Reflect)]",
193                            // This suggestion may result in two consecutive
194                            // `#[derive(...)]` attributes, but `rustfmt` merges them
195                            // afterwards.
196                            applicability,
197                        );
198                    },
199                );
200            }
201        }
202    }
203}