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 paths::PathLookup,
60 sugg::DiagExt,
61 ty::{implements_trait, ty_from_hir_ty},
62};
63use rustc_errors::Applicability;
64use rustc_hir::{HirId, Item, ItemKind, Node, OwnerId, QPath, TyKind, def::DefKind};
65use rustc_lint::{LateContext, LateLintPass};
66use rustc_middle::span_bug;
67use rustc_span::Span;
68
69use crate::{declare_bevy_lint, declare_bevy_lint_pass};
70
71declare_bevy_lint! {
72 pub(crate) MISSING_REFLECT,
73 super::Restriction,
74 "defined a component, resource, or event without a `Reflect` implementation",
75 // We only override `check_crate()`.
76 @crate_level_only = true,
77}
78
79declare_bevy_lint_pass! {
80 pub(crate) MissingReflect => [MISSING_REFLECT],
81}
82
83impl<'tcx> LateLintPass<'tcx> for MissingReflect {
84 fn check_crate(&mut self, cx: &LateContext<'tcx>) {
85 // Finds all types that implement `Reflect` in this crate.
86 let reflected: Vec<TraitType> =
87 TraitType::from_local_crate(cx, &crate::paths::REFLECT).collect();
88
89 // Finds all non-`Reflect` types that implement `Event` in this crate.
90 let events: Vec<TraitType> = TraitType::from_local_crate(cx, &crate::paths::EVENT)
91 .filter(|trait_type| !reflected.contains(trait_type))
92 .collect();
93
94 // Finds all non-`Reflect` types that implement `Component` and *not* `Event` in this
95 // crate. Because events are also components, we need to deduplicate the two to avoid
96 // emitting multiple diagnostics for the same type.
97 let components: Vec<TraitType> = TraitType::from_local_crate(cx, &crate::paths::COMPONENT)
98 .filter(|trait_type| !(reflected.contains(trait_type) || events.contains(trait_type)))
99 .collect();
100
101 // Finds all non-`Reflect` types that implement `Resource` in this crate.
102 let resources: Vec<TraitType> = TraitType::from_local_crate(cx, &crate::paths::RESOURCE)
103 .filter(|trait_type| !reflected.contains(trait_type))
104 .collect();
105
106 let reflect_trait_def_ids = crate::paths::PARTIAL_REFLECT.get(cx);
107
108 // Emit diagnostics for each of these types.
109 for (checked_trait, trait_name, message_phrase) in [
110 (events, "Event", "an event"),
111 (components, "Component", "a component"),
112 (resources, "Resource", "a resource"),
113 ] {
114 for without_reflect in checked_trait {
115 // Skip if a types originates from a foreign crate's macro
116 if without_reflect
117 .item_span
118 .in_external_macro(cx.tcx.sess.source_map())
119 {
120 continue;
121 }
122
123 // This lint is machine applicable unless any of the struct's fields do not
124 // implement `PartialReflect`.
125 let mut applicability = Applicability::MachineApplicable;
126
127 // Find the `Item` definition of the struct missing `#[derive(Reflect)]`. We can use
128 // `expect_owner()` because the HIR ID was originally created from a `LocalDefId`,
129 // and we can use `expect_item()` because `TraitType::from_local_crate()` only
130 // returns items.
131 let without_reflect_item = cx
132 .tcx
133 .hir_expect_item(without_reflect.hir_id.expect_owner().def_id);
134
135 // Extract a list of all fields within the structure definition.
136 let fields = match without_reflect_item.kind {
137 ItemKind::Struct(_, data, _) => data.fields().to_vec(),
138 ItemKind::Enum(_, enum_def, _) => enum_def
139 .variants
140 .iter()
141 .flat_map(|variant| variant.data.fields())
142 .copied()
143 .collect(),
144 // Unions are explicitly unsupported by `#[derive(Reflect)]`, so we don't even
145 // both checking the fields and just set the applicability to "maybe incorrect".
146 ItemKind::Union(..) => {
147 applicability = Applicability::MaybeIncorrect;
148 Vec::new()
149 }
150 // This shouldn't be possible, as only structs, enums, and unions can implement
151 // traits, so panic if this branch is reached.
152 _ => span_bug!(
153 without_reflect.item_span,
154 "found a type that implements `Event`, `Component`, or `Resource` but is not a struct, enum, or union",
155 ),
156 };
157
158 for field in fields {
159 let ty = ty_from_hir_ty(cx, field.ty);
160
161 // Check if the field's type implements the `PartialReflect` trait. If it does
162 // not, change the `Applicability` level to `MaybeIncorrect` because `Reflect`
163 // cannot be automatically derived.
164 if !reflect_trait_def_ids
165 .iter()
166 .any(|&trait_id| implements_trait(cx, ty, trait_id, &[]))
167 {
168 applicability = Applicability::MaybeIncorrect;
169 break;
170 }
171 }
172
173 span_lint_hir_and_then(
174 cx,
175 MISSING_REFLECT,
176 // This tells `rustc` where to search for `#[allow(...)]` attributes.
177 without_reflect.hir_id,
178 without_reflect.item_span,
179 format!("defined {message_phrase} without a `Reflect` implementation"),
180 |diag| {
181 diag.span_note(
182 without_reflect.impl_span,
183 format!("`{trait_name}` implemented here"),
184 )
185 .suggest_item_with_attr(
186 cx,
187 without_reflect.item_span,
188 "`Reflect` can be automatically derived",
189 "#[derive(Reflect)]",
190 // This suggestion may result in two consecutive
191 // `#[derive(...)]` attributes, but `rustfmt` merges them
192 // afterwards.
193 applicability,
194 );
195 },
196 );
197 }
198 }
199 }
200}
201
202/// Represents a type that implements a specific trait.
203#[derive(Debug)]
204struct TraitType {
205 /// The [`HirId`] pointing to the type item declaration.
206 hir_id: HirId,
207 /// The span where the type was declared.
208 item_span: Span,
209 /// The span where the trait was implemented.
210 impl_span: Span,
211}
212
213impl TraitType {
214 fn from_local_crate<'tcx, 'a>(
215 cx: &'a LateContext<'tcx>,
216 trait_path: &'a PathLookup,
217 ) -> impl Iterator<Item = Self> + use<'tcx, 'a> {
218 // Find the `DefId` of the trait. There may be multiple if there are multiple versions of
219 // the same crate.
220 let trait_def_ids = trait_path
221 .get(cx)
222 .iter()
223 .filter(|&def_id| cx.tcx.def_kind(def_id) == DefKind::Trait);
224
225 // Find a map of all trait `impl` items within the current crate. The key is the `DefId` of
226 // the trait, and the value is a `Vec<LocalDefId>` for all `impl` items.
227 let all_trait_impls = cx.tcx.all_local_trait_impls(());
228
229 // Find all `impl` items for the specific trait.
230 let trait_impls = trait_def_ids
231 .filter_map(|def_id| all_trait_impls.get(def_id))
232 .flatten()
233 .copied();
234
235 // Map the `DefId`s of `impl` items to `TraitType`s. Sometimes this conversion can fail, so
236 // we use `filter_map()` to skip errors.
237 trait_impls.filter_map(move |local_def_id| {
238 // Retrieve the node of the `impl` item from its `DefId`.
239 let node = cx.tcx.hir_node_by_def_id(local_def_id);
240
241 // Verify that it's an `impl` item and not something else.
242 let Node::Item(Item {
243 kind: ItemKind::Impl(impl_),
244 span: impl_span,
245 ..
246 }) = node
247 else {
248 return None;
249 };
250
251 // Find where `T` in `impl T` was originally defined, after peeling away all references
252 // `&`. This was adapted from `clippy_utils::path_res()` in order to avoid passing
253 // `LateContext` to this function.
254 let def_id = match impl_.self_ty.peel_refs().kind {
255 TyKind::Path(QPath::Resolved(_, path)) => path.res.opt_def_id()?,
256 _ => return None,
257 };
258
259 // Tries to convert the `DefId` to a `LocalDefId`, exiting early if it cannot be done.
260 // This will only work if `T` in `impl T` is defined within the same crate.
261 //
262 // In most cases this will succeed due to Rust's orphan rule, but it notably fails
263 // within `bevy_reflect` itself, since that crate implements `Reflect` for `std` types
264 // such as `String`.
265 let local_def_id = def_id.as_local()?;
266
267 // Find the `HirId` from the `LocalDefId`. This is like a `DefId`, but with further
268 // constraints on what it can represent.
269 let hir_id = OwnerId {
270 def_id: local_def_id,
271 }
272 .into();
273
274 // Find the span where the type was declared. This is guaranteed to be an item, so we
275 // can safely call `expect_item()` without it panicking.
276 let item_span = cx.tcx.hir_node(hir_id).expect_item().span;
277
278 Some(TraitType {
279 hir_id,
280 item_span,
281 impl_span: *impl_span,
282 })
283 })
284 }
285}
286
287/// A custom equality implementation that just checks the [`HirId`] of the [`TraitType`], and skips
288/// the other values.
289///
290/// [`TraitType`]s with equal [`HirId`]s are guaranteed to be equal in all other fields, so this
291/// takes advantage of that fact.
292impl PartialEq for TraitType {
293 fn eq(&self, other: &Self) -> bool {
294 self.hir_id == other.hir_id
295 }
296}