diff --git a/bevy_lint/src/lints/restriction/missing_reflect.rs b/bevy_lint/src/lints/restriction/missing_reflect.rs index 08109263..abef0f8a 100644 --- a/bevy_lint/src/lints/restriction/missing_reflect.rs +++ b/bevy_lint/src/lints/restriction/missing_reflect.rs @@ -56,16 +56,16 @@ use clippy_utils::{ diagnostics::span_lint_hir_and_then, - paths::PathLookup, sugg::DiagExt, ty::{implements_trait, ty_from_hir_ty}, }; use rustc_errors::Applicability; -use rustc_hir::{HirId, Item, ItemKind, Node, OwnerId, QPath, TyKind, def::DefKind}; +use rustc_hir::ItemKind; use rustc_lint::{LateContext, LateLintPass}; -use rustc_span::Span; -use crate::{declare_bevy_lint, declare_bevy_lint_pass, span_unreachable}; +use crate::{ + declare_bevy_lint, declare_bevy_lint_pass, span_unreachable, utils::traits::TraitType, +}; declare_bevy_lint! { pub(crate) MISSING_REFLECT, @@ -201,99 +201,3 @@ impl<'tcx> LateLintPass<'tcx> for MissingReflect { } } } - -/// Represents a type that implements a specific trait. -#[derive(Debug)] -struct TraitType { - /// The [`HirId`] pointing to the type item declaration. - hir_id: HirId, - /// The span where the type was declared. - item_span: Span, - /// The span where the trait was implemented. - impl_span: Span, -} - -impl TraitType { - fn from_local_crate<'tcx, 'a>( - cx: &'a LateContext<'tcx>, - trait_path: &'a PathLookup, - ) -> impl Iterator + use<'tcx, 'a> { - // Find the `DefId` of the trait. There may be multiple if there are multiple versions of - // the same crate. - let trait_def_ids = trait_path - .get(cx) - .iter() - .filter(|&def_id| cx.tcx.def_kind(def_id) == DefKind::Trait); - - // Find a map of all trait `impl` items within the current crate. The key is the `DefId` of - // the trait, and the value is a `Vec` for all `impl` items. - let all_trait_impls = cx.tcx.all_local_trait_impls(()); - - // Find all `impl` items for the specific trait. - let trait_impls = trait_def_ids - .filter_map(|def_id| all_trait_impls.get(def_id)) - .flatten() - .copied(); - - // Map the `DefId`s of `impl` items to `TraitType`s. Sometimes this conversion can fail, so - // we use `filter_map()` to skip errors. - trait_impls.filter_map(move |local_def_id| { - // Retrieve the node of the `impl` item from its `DefId`. - let node = cx.tcx.hir_node_by_def_id(local_def_id); - - // Verify that it's an `impl` item and not something else. - let Node::Item(Item { - kind: ItemKind::Impl(impl_), - span: impl_span, - .. - }) = node - else { - return None; - }; - - // Find where `T` in `impl T` was originally defined, after peeling away all references - // `&`. This was adapted from `clippy_utils::path_res()` in order to avoid passing - // `LateContext` to this function. - let def_id = match impl_.self_ty.peel_refs().kind { - TyKind::Path(QPath::Resolved(_, path)) => path.res.opt_def_id()?, - _ => return None, - }; - - // Tries to convert the `DefId` to a `LocalDefId`, exiting early if it cannot be done. - // This will only work if `T` in `impl T` is defined within the same crate. - // - // In most cases this will succeed due to Rust's orphan rule, but it notably fails - // within `bevy_reflect` itself, since that crate implements `Reflect` for `std` types - // such as `String`. - let local_def_id = def_id.as_local()?; - - // Find the `HirId` from the `LocalDefId`. This is like a `DefId`, but with further - // constraints on what it can represent. - let hir_id = OwnerId { - def_id: local_def_id, - } - .into(); - - // Find the span where the type was declared. This is guaranteed to be an item, so we - // can safely call `expect_item()` without it panicking. - let item_span = cx.tcx.hir_node(hir_id).expect_item().span; - - Some(TraitType { - hir_id, - item_span, - impl_span: *impl_span, - }) - }) - } -} - -/// A custom equality implementation that just checks the [`HirId`] of the [`TraitType`], and skips -/// the other values. -/// -/// [`TraitType`]s with equal [`HirId`]s are guaranteed to be equal in all other fields, so this -/// takes advantage of that fact. -impl PartialEq for TraitType { - fn eq(&self, other: &Self) -> bool { - self.hir_id == other.hir_id - } -} diff --git a/bevy_lint/src/lints/restriction/missing_trait_impls.rs b/bevy_lint/src/lints/restriction/missing_trait_impls.rs new file mode 100644 index 00000000..0fd5cdae --- /dev/null +++ b/bevy_lint/src/lints/restriction/missing_trait_impls.rs @@ -0,0 +1,144 @@ +use clippy_utils::{diagnostics::span_lint_hir_and_then, sugg::DiagExt, ty::implements_trait}; +use rustc_errors::Applicability; +use rustc_hir::{ItemKind, def_id::DefId}; +use rustc_lint::{LateContext, LateLintPass, Lint}; + +use crate::{declare_bevy_lint, declare_bevy_lint_pass, utils::traits::TraitType}; + +declare_bevy_lint! { + pub(crate) MISSING_DEFAULT, + super::Restriction, + "defined a unit component without a `Default` implementation", +} + +declare_bevy_lint! { + pub(crate) MISSING_CLONE, + super::Restriction, + "defined a unit component without a `Clone` implementation", +} + +declare_bevy_lint! { + pub(crate) MISSING_COPY, + super::Restriction, + "defined a unit component without a `Copy` implementation", +} + +declare_bevy_lint_pass! { + pub(crate) MissingTraitImpls => [MISSING_DEFAULT,MISSING_CLONE,MISSING_COPY], +} + +impl<'tcx> LateLintPass<'tcx> for MissingTraitImpls { + fn check_crate(&mut self, cx: &LateContext<'tcx>) { + // Finds all types that implement `Component` in this crate. + let components: Vec = + TraitType::from_local_crate(cx, &crate::paths::COMPONENT).collect(); + + for component in components { + // Skip if a types originates from a foreign crate's macro + if component + .item_span + .in_external_macro(cx.tcx.sess.source_map()) + { + continue; + } + + let def_id = component.hir_id.owner.to_def_id(); + + // Skip binder as we are not interested in the generics + let ty = cx.tcx.type_of(def_id).skip_binder(); + + for trait_to_implement in Trait::all() { + // get the def_id of the trait that should be implement for unit structures. + if let Some(trait_def_id) = trait_to_implement.get_def_id(cx) + && !implements_trait(cx, ty, trait_def_id, &[]) + { + // Find the `Item` definition of the Component missing the trait derive. + let missing_trait_imp_item = cx + .tcx + .hir_expect_item(component.hir_id.expect_owner().def_id); + + let fields = match missing_trait_imp_item.kind { + ItemKind::Struct(_, _, data) => data.fields().to_vec(), + // If this item is not a struct, continue + _ => continue, + }; + + // Check if the struct is a unit struct (contains no fields) + if !fields.is_empty() { + continue; + } + + span_lint_hir_and_then( + cx, + trait_to_implement.lint(), + // This tells `rustc` where to search for `#[allow(...)]` attributes. + component.hir_id, + component.item_span, + format!( + "defined a unit struct without a `{}` implementation", + trait_to_implement.name() + ), + |diag| { + diag.span_note(component.impl_span, "`Component` implemented here") + .suggest_item_with_attr( + cx, + component.item_span, + format!( + "`{}` can be automatically derived", + trait_to_implement.name() + ) + .as_str(), + format!("#[derive({})]", trait_to_implement.name()).as_str(), + // This lint is MachineApplicable, since we only lint structs + // that do not have any + // fields. This suggestion + // may result in two consecutive + // `#[derive(...)]` attributes, but `rustfmt` merges them + // afterwards. + Applicability::MachineApplicable, + ); + }, + ); + } + } + } + } +} + +enum Trait { + Copy, + Clone, + Default, +} + +impl Trait { + const fn all() -> [Trait; 3] { + use Trait::*; + + [Copy, Clone, Default] + } + + const fn name(&self) -> &'static str { + match self { + Trait::Copy => "Copy", + Trait::Clone => "Clone", + Trait::Default => "Default", + } + } + + const fn lint(&self) -> &'static Lint { + match self { + Trait::Copy => MISSING_COPY, + Trait::Clone => MISSING_CLONE, + Trait::Default => MISSING_DEFAULT, + } + } + + fn get_def_id(&self, cx: &LateContext) -> std::option::Option { + match self { + Trait::Copy => cx.tcx.get_diagnostic_item(rustc_span::symbol::sym::Copy), + Trait::Clone => cx.tcx.get_diagnostic_item(rustc_span::symbol::sym::Clone), + Trait::Default => cx.tcx.get_diagnostic_item(rustc_span::symbol::sym::Default), + } + } +} diff --git a/bevy_lint/src/lints/restriction/mod.rs b/bevy_lint/src/lints/restriction/mod.rs index af25f4c4..6eed6e02 100644 --- a/bevy_lint/src/lints/restriction/mod.rs +++ b/bevy_lint/src/lints/restriction/mod.rs @@ -11,6 +11,7 @@ use rustc_lint::{Level, Lint, LintStore}; use crate::lint::LintGroup; pub mod missing_reflect; +pub mod missing_trait_impls; pub mod panicking_methods; pub mod schedule; @@ -21,6 +22,9 @@ impl LintGroup for Restriction { const LEVEL: Level = Level::Allow; const LINTS: &[&Lint] = &[ missing_reflect::MISSING_REFLECT, + missing_trait_impls::MISSING_CLONE, + missing_trait_impls::MISSING_COPY, + missing_trait_impls::MISSING_DEFAULT, panicking_methods::PANICKING_METHODS, schedule::FIXED_UPDATE_SCHEDULE, schedule::UPDATE_SCHEDULE, @@ -28,6 +32,7 @@ impl LintGroup for Restriction { fn register_passes(store: &mut LintStore) { store.register_late_pass(|_| Box::new(missing_reflect::MissingReflect)); + store.register_late_pass(|_| Box::new(missing_trait_impls::MissingTraitImpls)); store.register_late_pass(|_| Box::new(panicking_methods::PanickingMethods)); store.register_late_pass(|_| Box::new(schedule::Schedule)); } diff --git a/bevy_lint/src/utils/mod.rs b/bevy_lint/src/utils/mod.rs index d6c9a254..e917b7cf 100644 --- a/bevy_lint/src/utils/mod.rs +++ b/bevy_lint/src/utils/mod.rs @@ -4,3 +4,4 @@ pub mod cargo; pub mod hir_parse; pub mod method_call; mod panic; +pub mod traits; diff --git a/bevy_lint/src/utils/traits.rs b/bevy_lint/src/utils/traits.rs new file mode 100644 index 00000000..57714170 --- /dev/null +++ b/bevy_lint/src/utils/traits.rs @@ -0,0 +1,100 @@ +use clippy_utils::paths::PathLookup; +use rustc_hir::{HirId, Item, ItemKind, Node, OwnerId, QPath, TyKind, def::DefKind}; +use rustc_lint::LateContext; +use rustc_span::Span; + +/// Represents a type that implements a specific trait. +#[derive(Debug)] +pub struct TraitType { + /// The [`HirId`] pointing to the type item declaration. + pub hir_id: HirId, + /// The span where the type was declared. + pub item_span: Span, + /// The span where the trait was implemented. + pub impl_span: Span, +} + +impl TraitType { + pub fn from_local_crate<'tcx, 'a>( + cx: &'a LateContext<'tcx>, + trait_path: &'a PathLookup, + ) -> impl Iterator + use<'tcx, 'a> { + // Find the `DefId` of the trait. There may be multiple if there are multiple versions of + // the same crate. + let trait_def_ids = trait_path + .get(cx) + .iter() + .filter(|&def_id| cx.tcx.def_kind(def_id) == DefKind::Trait); + + // Find a map of all trait `impl` items within the current crate. The key is the `DefId` of + // the trait, and the value is a `Vec` for all `impl` items. + let all_trait_impls = cx.tcx.all_local_trait_impls(()); + + // Find all `impl` items for the specific trait. + let trait_impls = trait_def_ids + .filter_map(|def_id| all_trait_impls.get(def_id)) + .flatten() + .copied(); + + // Map the `DefId`s of `impl` items to `TraitType`s. Sometimes this conversion can fail, so + // we use `filter_map()` to skip errors. + trait_impls.filter_map(move |local_def_id| { + // Retrieve the node of the `impl` item from its `DefId`. + let node = cx.tcx.hir_node_by_def_id(local_def_id); + + // Verify that it's an `impl` item and not something else. + let Node::Item(Item { + kind: ItemKind::Impl(impl_), + span: impl_span, + .. + }) = node + else { + return None; + }; + + // Find where `T` in `impl T` was originally defined, after peeling away all references + // `&`. This was adapted from `clippy_utils::path_res()` in order to avoid passing + // `LateContext` to this function. + let def_id = match impl_.self_ty.peel_refs().kind { + TyKind::Path(QPath::Resolved(_, path)) => path.res.opt_def_id()?, + _ => return None, + }; + + // Tries to convert the `DefId` to a `LocalDefId`, exiting early if it cannot be done. + // This will only work if `T` in `impl T` is defined within the same crate. + // + // In most cases this will succeed due to Rust's orphan rule, but it notably fails + // within `bevy_reflect` itself, since that crate implements `Reflect` for `std` types + // such as `String`. + let local_def_id = def_id.as_local()?; + + // Find the `HirId` from the `LocalDefId`. This is like a `DefId`, but with further + // constraints on what it can represent. + let hir_id = OwnerId { + def_id: local_def_id, + } + .into(); + + // Find the span where the type was declared. This is guaranteed to be an item, so we + // can safely call `expect_item()` without it panicking. + let item_span = cx.tcx.hir_node(hir_id).expect_item().span; + + Some(TraitType { + hir_id, + item_span, + impl_span: *impl_span, + }) + }) + } +} + +/// A custom equality implementation that just checks the [`HirId`] of the [`TraitType`], and skips +/// the other values. +/// +/// [`TraitType`]s with equal [`HirId`]s are guaranteed to be equal in all other fields, so this +/// takes advantage of that fact. +impl PartialEq for TraitType { + fn eq(&self, other: &Self) -> bool { + self.hir_id == other.hir_id + } +} diff --git a/bevy_lint/tests/ui/missing_trait_impls/missing_clone.fixed b/bevy_lint/tests/ui/missing_trait_impls/missing_clone.fixed new file mode 100644 index 00000000..0c7068a2 --- /dev/null +++ b/bevy_lint/tests/ui/missing_trait_impls/missing_clone.fixed @@ -0,0 +1,37 @@ +#![feature(register_tool)] +#![register_tool(bevy)] +//~v NOTE: the lint level is defined here +#![deny(bevy::missing_clone)] + +use bevy::prelude::*; + +//~v NOTE: `Component` implemented here +#[derive(Component)] +//~| HELP: `Clone` can be automatically derived +//~v ERROR: defined a unit struct without a `Clone` implementation +#[derive(Clone)] +pub struct IsDefaultUiCamera; + +// This should not raise an ERROR, since `Clone` is derived. +#[derive(Component, Clone)] +pub struct DeriveClone; + +// This should not raise an ERROR, since `Clone` is implemented. +#[derive(Component)] +pub struct ImplClone; + +impl Clone for ImplClone { + fn clone(&self) -> Self { + ImplClone + } +} + +// This should not raise an ERROR, since this is not a unit struct. +#[derive(Component)] +pub struct ComponentWithFields(#[allow(dead_code)] f32); + +// This should not raise an ERROR, since this struct does not implement `Component`. +pub struct Foo; + +#[allow(bevy::missing_clone)] +pub struct AllowMissingClone; diff --git a/bevy_lint/tests/ui/missing_trait_impls/missing_clone.rs b/bevy_lint/tests/ui/missing_trait_impls/missing_clone.rs new file mode 100644 index 00000000..71b8d3ea --- /dev/null +++ b/bevy_lint/tests/ui/missing_trait_impls/missing_clone.rs @@ -0,0 +1,36 @@ +#![feature(register_tool)] +#![register_tool(bevy)] +//~v NOTE: the lint level is defined here +#![deny(bevy::missing_clone)] + +use bevy::prelude::*; + +//~v NOTE: `Component` implemented here +#[derive(Component)] +//~| HELP: `Clone` can be automatically derived +//~v ERROR: defined a unit struct without a `Clone` implementation +pub struct IsDefaultUiCamera; + +// This should not raise an ERROR, since `Clone` is derived. +#[derive(Component, Clone)] +pub struct DeriveClone; + +// This should not raise an ERROR, since `Clone` is implemented. +#[derive(Component)] +pub struct ImplClone; + +impl Clone for ImplClone { + fn clone(&self) -> Self { + ImplClone + } +} + +// This should not raise an ERROR, since this is not a unit struct. +#[derive(Component)] +pub struct ComponentWithFields(#[allow(dead_code)] f32); + +// This should not raise an ERROR, since this struct does not implement `Component`. +pub struct Foo; + +#[allow(bevy::missing_clone)] +pub struct AllowMissingClone; diff --git a/bevy_lint/tests/ui/missing_trait_impls/missing_clone.stderr b/bevy_lint/tests/ui/missing_trait_impls/missing_clone.stderr new file mode 100644 index 00000000..000d8e23 --- /dev/null +++ b/bevy_lint/tests/ui/missing_trait_impls/missing_clone.stderr @@ -0,0 +1,25 @@ +error: defined a unit struct without a `Clone` implementation + --> tests/ui/missing_trait_impls/missing_clone.rs:12:1 + | +12 | pub struct IsDefaultUiCamera; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +note: `Component` implemented here + --> tests/ui/missing_trait_impls/missing_clone.rs:9:10 + | + 9 | #[derive(Component)] + | ^^^^^^^^^ +note: the lint level is defined here + --> tests/ui/missing_trait_impls/missing_clone.rs:4:9 + | + 4 | #![deny(bevy::missing_clone)] + | ^^^^^^^^^^^^^^^^^^^ + = note: this error originates in the derive macro `Component` (in Nightly builds, run with -Z macro-backtrace for more info) +help: `Clone` can be automatically derived + | +12 + #[derive(Clone)] +13 | pub struct IsDefaultUiCamera; + | + +error: aborting due to 1 previous error + diff --git a/bevy_lint/tests/ui/missing_trait_impls/missing_copy.fixed b/bevy_lint/tests/ui/missing_trait_impls/missing_copy.fixed new file mode 100644 index 00000000..31ef7227 --- /dev/null +++ b/bevy_lint/tests/ui/missing_trait_impls/missing_copy.fixed @@ -0,0 +1,33 @@ +#![feature(register_tool)] +#![register_tool(bevy)] +//~v NOTE: the lint level is defined here +#![deny(bevy::missing_copy)] + +use bevy::prelude::*; + +//~v NOTE: `Component` implemented here +#[derive(Component, Clone)] +//~| HELP: `Copy` can be automatically derived +//~v ERROR: defined a unit struct without a `Copy` implementation +#[derive(Copy)] +pub struct IsDefaultUiCamera; + +// This should not raise an ERROR, since `Copy` is derived. +#[derive(Component, Copy, Clone)] +pub struct DeriveCopy; + +// This should not raise an ERROR, since `Copy` is implemented. +#[derive(Component, Clone)] +pub struct ImplCopy; + +impl Copy for ImplCopy {} + +// This should not raise an ERROR, since this is not a unit struct. +#[derive(Component)] +pub struct ComponentWithFields(#[allow(dead_code)] f32); + +// This should not raise an ERROR, since this struct does not implement `Component`. +pub struct Foo; + +#[allow(bevy::missing_copy)] +pub struct AllowMissingCopy; diff --git a/bevy_lint/tests/ui/missing_trait_impls/missing_copy.rs b/bevy_lint/tests/ui/missing_trait_impls/missing_copy.rs new file mode 100644 index 00000000..50b22c5c --- /dev/null +++ b/bevy_lint/tests/ui/missing_trait_impls/missing_copy.rs @@ -0,0 +1,32 @@ +#![feature(register_tool)] +#![register_tool(bevy)] +//~v NOTE: the lint level is defined here +#![deny(bevy::missing_copy)] + +use bevy::prelude::*; + +//~v NOTE: `Component` implemented here +#[derive(Component, Clone)] +//~| HELP: `Copy` can be automatically derived +//~v ERROR: defined a unit struct without a `Copy` implementation +pub struct IsDefaultUiCamera; + +// This should not raise an ERROR, since `Copy` is derived. +#[derive(Component, Copy, Clone)] +pub struct DeriveCopy; + +// This should not raise an ERROR, since `Copy` is implemented. +#[derive(Component, Clone)] +pub struct ImplCopy; + +impl Copy for ImplCopy {} + +// This should not raise an ERROR, since this is not a unit struct. +#[derive(Component)] +pub struct ComponentWithFields(#[allow(dead_code)] f32); + +// This should not raise an ERROR, since this struct does not implement `Component`. +pub struct Foo; + +#[allow(bevy::missing_copy)] +pub struct AllowMissingCopy; diff --git a/bevy_lint/tests/ui/missing_trait_impls/missing_copy.stderr b/bevy_lint/tests/ui/missing_trait_impls/missing_copy.stderr new file mode 100644 index 00000000..6d8b982b --- /dev/null +++ b/bevy_lint/tests/ui/missing_trait_impls/missing_copy.stderr @@ -0,0 +1,25 @@ +error: defined a unit struct without a `Copy` implementation + --> tests/ui/missing_trait_impls/missing_copy.rs:12:1 + | +12 | pub struct IsDefaultUiCamera; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +note: `Component` implemented here + --> tests/ui/missing_trait_impls/missing_copy.rs:9:10 + | + 9 | #[derive(Component, Clone)] + | ^^^^^^^^^ +note: the lint level is defined here + --> tests/ui/missing_trait_impls/missing_copy.rs:4:9 + | + 4 | #![deny(bevy::missing_copy)] + | ^^^^^^^^^^^^^^^^^^ + = note: this error originates in the derive macro `Component` (in Nightly builds, run with -Z macro-backtrace for more info) +help: `Copy` can be automatically derived + | +12 + #[derive(Copy)] +13 | pub struct IsDefaultUiCamera; + | + +error: aborting due to 1 previous error + diff --git a/bevy_lint/tests/ui/missing_trait_impls/missing_default.fixed b/bevy_lint/tests/ui/missing_trait_impls/missing_default.fixed new file mode 100644 index 00000000..8882e396 --- /dev/null +++ b/bevy_lint/tests/ui/missing_trait_impls/missing_default.fixed @@ -0,0 +1,39 @@ +#![feature(register_tool)] +#![register_tool(bevy)] +//~v NOTE: the lint level is defined here +#![deny(bevy::missing_default)] + +use bevy::prelude::*; + +//~v NOTE: `Component` implemented here +#[derive(Component)] +//~| HELP: `Default` can be automatically derived +//~v ERROR: defined a unit struct without a `Default` implementation +#[derive(Default)] +pub struct IsDefaultUiCamera; + +// This should not raise an ERROR, since `Default` is derived. +#[derive(Component, Default)] +pub struct DeriveDefault; + +// This should not raise an ERROR, since `Default` is derived. +#[derive(Component)] +pub struct ImplDefault; + +// This should not raise an ERROR, since `Default` is implemented. +impl Default for ImplDefault { + fn default() -> Self { + ImplDefault + } +} + +// This should not raise an ERROR, since this is not a unit struct. +#[derive(Component)] +pub struct ComponentWithFields(#[allow(dead_code)] f32); + +// This should not raise an ERROR, since this struct does not implement `Component`. +pub struct Foo; + +#[derive(Component)] +#[allow(bevy::missing_default)] +pub struct AllowMissingDefault; diff --git a/bevy_lint/tests/ui/missing_trait_impls/missing_default.rs b/bevy_lint/tests/ui/missing_trait_impls/missing_default.rs new file mode 100644 index 00000000..689fc3f5 --- /dev/null +++ b/bevy_lint/tests/ui/missing_trait_impls/missing_default.rs @@ -0,0 +1,38 @@ +#![feature(register_tool)] +#![register_tool(bevy)] +//~v NOTE: the lint level is defined here +#![deny(bevy::missing_default)] + +use bevy::prelude::*; + +//~v NOTE: `Component` implemented here +#[derive(Component)] +//~| HELP: `Default` can be automatically derived +//~v ERROR: defined a unit struct without a `Default` implementation +pub struct IsDefaultUiCamera; + +// This should not raise an ERROR, since `Default` is derived. +#[derive(Component, Default)] +pub struct DeriveDefault; + +// This should not raise an ERROR, since `Default` is derived. +#[derive(Component)] +pub struct ImplDefault; + +// This should not raise an ERROR, since `Default` is implemented. +impl Default for ImplDefault { + fn default() -> Self { + ImplDefault + } +} + +// This should not raise an ERROR, since this is not a unit struct. +#[derive(Component)] +pub struct ComponentWithFields(#[allow(dead_code)] f32); + +// This should not raise an ERROR, since this struct does not implement `Component`. +pub struct Foo; + +#[derive(Component)] +#[allow(bevy::missing_default)] +pub struct AllowMissingDefault; diff --git a/bevy_lint/tests/ui/missing_trait_impls/missing_default.stderr b/bevy_lint/tests/ui/missing_trait_impls/missing_default.stderr new file mode 100644 index 00000000..3a5c0a34 --- /dev/null +++ b/bevy_lint/tests/ui/missing_trait_impls/missing_default.stderr @@ -0,0 +1,25 @@ +error: defined a unit struct without a `Default` implementation + --> tests/ui/missing_trait_impls/missing_default.rs:12:1 + | +12 | pub struct IsDefaultUiCamera; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | +note: `Component` implemented here + --> tests/ui/missing_trait_impls/missing_default.rs:9:10 + | + 9 | #[derive(Component)] + | ^^^^^^^^^ +note: the lint level is defined here + --> tests/ui/missing_trait_impls/missing_default.rs:4:9 + | + 4 | #![deny(bevy::missing_default)] + | ^^^^^^^^^^^^^^^^^^^^^ + = note: this error originates in the derive macro `Component` (in Nightly builds, run with -Z macro-backtrace for more info) +help: `Default` can be automatically derived + | +12 + #[derive(Default)] +13 | pub struct IsDefaultUiCamera; + | + +error: aborting due to 1 previous error +