bevy_lint/lints/restriction/
missing_trait_for_unit_struct.rs

1//! Checks for unit structs that do not  implement `Copy`, `Clone` or `Default`.
2//!
3//! # Motivation
4//!
5//! This is mainly useful for the Bevy engine itself to ensure a consistent API. For example
6//! in order to use a component with required components the component needs to implement
7//! `Default`.
8//!
9//! # Example
10//!
11//! ```
12//! struct UnitStruct;
13//! ```
14//!
15//! Use instead:
16//!
17//! ```
18//! #[derive(Copy,Clone,Default)]
19//! struct MyComponent;
20//! ```
21use clippy_utils::{diagnostics::span_lint_and_then, sugg::DiagExt, ty::implements_trait};
22use rustc_errors::Applicability;
23use rustc_hir::{Item, ItemKind, VariantData, def_id::DefId};
24use rustc_lint::{LateContext, LateLintPass, Lint};
25
26use crate::{declare_bevy_lint, declare_bevy_lint_pass};
27
28declare_bevy_lint! {
29    pub(crate) MISSING_DEFAULT,
30    super::Restriction,
31    "defined a unit component without a `Default` implementation",
32}
33
34declare_bevy_lint! {
35    pub(crate) MISSING_CLONE,
36    super::Restriction,
37    "defined a unit component without a `Clone` implementation",
38}
39
40declare_bevy_lint! {
41    pub(crate) MISSING_COPY,
42    super::Restriction,
43    "defined a unit component without a `Copy` implementation",
44}
45
46declare_bevy_lint_pass! {
47    pub(crate) MissingTraitForUnitStruct => [MISSING_DEFAULT,MISSING_CLONE,MISSING_COPY],
48}
49
50impl<'tcx> LateLintPass<'tcx> for MissingTraitForUnitStruct {
51    fn check_item(&mut self, cx: &LateContext<'tcx>, item: &Item) {
52        // Skip if this Item originates from a foreign crate's macro
53        if item.span.in_external_macro(cx.tcx.sess.source_map()) {
54            return;
55        }
56
57        // Check that the item is a Struct.
58        let ItemKind::Struct(_, _, data) = item.kind else {
59            return;
60        };
61
62        // Check if the struct is a unit struct (contains no fields).
63        if !matches!(data, VariantData::Unit(..)) {
64            return;
65        }
66
67        // Skip binder as unit types cannot have generics.
68        let ty = cx.tcx.type_of(item.owner_id.to_def_id()).skip_binder();
69
70        for trait_to_implement in Trait::all() {
71            // get the def_id of the trait that should be implement for unit structures.
72            if let Some(trait_def_id) = trait_to_implement.get_def_id(cx)
73                    // Unit types cannot have generic arguments, so we don't need to pass any in.
74                    && !implements_trait(cx, ty, trait_def_id, &[])
75            {
76                span_lint_and_then(
77                    cx,
78                    trait_to_implement.lint(),
79                    // This tells `rustc` where to search for `#[allow(...)]` attributes.
80                    item.span,
81                    format!(
82                        "defined a unit struct without a `{}` implementation",
83                        trait_to_implement.name()
84                    ),
85                    |diag| {
86                        diag.suggest_item_with_attr(
87                            cx,
88                            item.span,
89                            format!(
90                                "`{}` can be automatically derived",
91                                trait_to_implement.name()
92                            )
93                            .as_str(),
94                            format!("#[derive({})]", trait_to_implement.name()).as_str(),
95                            // This lint is MachineApplicable, since we only lint structs
96                            // that do not have any
97                            // fields. This suggestion
98                            // may result in two consecutive
99                            // `#[derive(...)]` attributes, but `rustfmt` merges them
100                            // afterwards.
101                            Applicability::MachineApplicable,
102                        );
103                    },
104                );
105            }
106        }
107    }
108}
109
110enum Trait {
111    Copy,
112    Clone,
113    Default,
114}
115
116impl Trait {
117    const fn all() -> [Trait; 3] {
118        use Trait::*;
119
120        [Copy, Clone, Default]
121    }
122
123    const fn name(&self) -> &'static str {
124        match self {
125            Trait::Copy => "Copy",
126            Trait::Clone => "Clone",
127            Trait::Default => "Default",
128        }
129    }
130
131    const fn lint(&self) -> &'static Lint {
132        match self {
133            Trait::Copy => MISSING_COPY,
134            Trait::Clone => MISSING_CLONE,
135            Trait::Default => MISSING_DEFAULT,
136        }
137    }
138
139    fn get_def_id(&self, cx: &LateContext) -> std::option::Option<DefId> {
140        match self {
141            Trait::Copy => cx.tcx.get_diagnostic_item(rustc_span::symbol::sym::Copy),
142            Trait::Clone => cx.tcx.get_diagnostic_item(rustc_span::symbol::sym::Clone),
143            Trait::Default => cx.tcx.get_diagnostic_item(rustc_span::symbol::sym::Default),
144        }
145    }
146}