From 5d99f417966fcbeee4a45a1de0240b76f72e00ea Mon Sep 17 00:00:00 2001 From: Gino Valente <49806985+MrGVSV@users.noreply.github.com> Date: Tue, 17 Dec 2024 15:39:42 -0700 Subject: [PATCH] Add `borrowed_reborrowable` lint (#164) Fixes #127 Adds the pedantic `borrowed_reborrowable` lint to suggest that parameters using a mutable reference to a re-borrowable type instead switch to an owned instance of that type. See #127 for details. --------- Co-authored-by: BD103 <59022059+BD103@users.noreply.github.com> --- bevy_lint/src/lints/borrowed_reborrowable.rs | 272 ++++++++++++++++++ bevy_lint/src/lints/mod.rs | 3 + bevy_lint/src/paths.rs | 11 + .../tests/ui/borrowed_reborrowable/bug_174.rs | 26 ++ .../ui/borrowed_reborrowable/closures.rs | 20 ++ .../ui/borrowed_reborrowable/closures.stderr | 14 + .../ui/borrowed_reborrowable/commands.rs | 48 ++++ .../ui/borrowed_reborrowable/commands.stderr | 20 ++ .../ui/borrowed_reborrowable/deferred.rs | 49 ++++ .../ui/borrowed_reborrowable/deferred.stderr | 20 ++ .../borrowed_reborrowable/deferred_world.rs | 49 ++++ .../deferred_world.stderr | 20 ++ .../borrowed_reborrowable/entity_commands.rs | 50 ++++ .../entity_commands.stderr | 20 ++ .../entity_mut copy.stderr | 20 ++ .../ui/borrowed_reborrowable/entity_mut.rs | 50 ++++ .../borrowed_reborrowable/entity_mut.stderr | 20 ++ .../filtered_entity_mut.rs | 50 ++++ .../filtered_entity_mut.stderr | 20 ++ .../ui/borrowed_reborrowable/mut_typed.rs | 50 ++++ .../ui/borrowed_reborrowable/mut_typed.stderr | 20 ++ .../ui/borrowed_reborrowable/mut_untyped.rs | 55 ++++ .../borrowed_reborrowable/mut_untyped.stderr | 20 ++ .../ui/borrowed_reborrowable/non_send_mut.rs | 51 ++++ .../borrowed_reborrowable/non_send_mut.stderr | 20 ++ .../tests/ui/borrowed_reborrowable/ptr_mut.rs | 48 ++++ .../ui/borrowed_reborrowable/ptr_mut.stderr | 20 ++ .../tests/ui/borrowed_reborrowable/query.rs | 49 ++++ .../ui/borrowed_reborrowable/query.stderr | 20 ++ .../ui/borrowed_reborrowable/resource.rs | 51 ++++ .../ui/borrowed_reborrowable/resource.stderr | 20 ++ .../ui/borrowed_reborrowable/self_param.rs | 21 ++ 32 files changed, 1227 insertions(+) create mode 100644 bevy_lint/src/lints/borrowed_reborrowable.rs create mode 100644 bevy_lint/tests/ui/borrowed_reborrowable/bug_174.rs create mode 100644 bevy_lint/tests/ui/borrowed_reborrowable/closures.rs create mode 100644 bevy_lint/tests/ui/borrowed_reborrowable/closures.stderr create mode 100644 bevy_lint/tests/ui/borrowed_reborrowable/commands.rs create mode 100644 bevy_lint/tests/ui/borrowed_reborrowable/commands.stderr create mode 100644 bevy_lint/tests/ui/borrowed_reborrowable/deferred.rs create mode 100644 bevy_lint/tests/ui/borrowed_reborrowable/deferred.stderr create mode 100644 bevy_lint/tests/ui/borrowed_reborrowable/deferred_world.rs create mode 100644 bevy_lint/tests/ui/borrowed_reborrowable/deferred_world.stderr create mode 100644 bevy_lint/tests/ui/borrowed_reborrowable/entity_commands.rs create mode 100644 bevy_lint/tests/ui/borrowed_reborrowable/entity_commands.stderr create mode 100644 bevy_lint/tests/ui/borrowed_reborrowable/entity_mut copy.stderr create mode 100644 bevy_lint/tests/ui/borrowed_reborrowable/entity_mut.rs create mode 100644 bevy_lint/tests/ui/borrowed_reborrowable/entity_mut.stderr create mode 100644 bevy_lint/tests/ui/borrowed_reborrowable/filtered_entity_mut.rs create mode 100644 bevy_lint/tests/ui/borrowed_reborrowable/filtered_entity_mut.stderr create mode 100644 bevy_lint/tests/ui/borrowed_reborrowable/mut_typed.rs create mode 100644 bevy_lint/tests/ui/borrowed_reborrowable/mut_typed.stderr create mode 100644 bevy_lint/tests/ui/borrowed_reborrowable/mut_untyped.rs create mode 100644 bevy_lint/tests/ui/borrowed_reborrowable/mut_untyped.stderr create mode 100644 bevy_lint/tests/ui/borrowed_reborrowable/non_send_mut.rs create mode 100644 bevy_lint/tests/ui/borrowed_reborrowable/non_send_mut.stderr create mode 100644 bevy_lint/tests/ui/borrowed_reborrowable/ptr_mut.rs create mode 100644 bevy_lint/tests/ui/borrowed_reborrowable/ptr_mut.stderr create mode 100644 bevy_lint/tests/ui/borrowed_reborrowable/query.rs create mode 100644 bevy_lint/tests/ui/borrowed_reborrowable/query.stderr create mode 100644 bevy_lint/tests/ui/borrowed_reborrowable/resource.rs create mode 100644 bevy_lint/tests/ui/borrowed_reborrowable/resource.stderr create mode 100644 bevy_lint/tests/ui/borrowed_reborrowable/self_param.rs diff --git a/bevy_lint/src/lints/borrowed_reborrowable.rs b/bevy_lint/src/lints/borrowed_reborrowable.rs new file mode 100644 index 0000000..6c2a360 --- /dev/null +++ b/bevy_lint/src/lints/borrowed_reborrowable.rs @@ -0,0 +1,272 @@ +//! Checks for function parameters that take a mutable reference to a +//! re-borrowable type. +//! +//! # Motivation +//! +//! Several Bevy types look like they are owned, when in reality they contain an `&mut` reference +//! to the data owned by the ECS. `Commands` and `Query` are examples of such types that _pretend_ +//! to own data for better user ergonomics. +//! +//! This can be an issue when a user writes a function that takes a mutable reference to one of +//! these types, not realizing that it itself is _already_ a reference. These mutable references +//! can almost always be readily converted back to an owned instance of the type, which is a cheap +//! operation that avoids nested references. +//! +//! The only time a re-borrowable type cannot be re-borrowed is when the function returns +//! referenced data that is bound to the mutable reference of the re-borrowable type. +//! +//! # Known Issues +//! +//! This lint does not currently support the [`Fn`] traits or function pointers. This means the +//! following types will not be caught by the lint: +//! +//! - `impl FnOnce(&mut Commands)` +//! - `Box` +//! - `fn(&mut Commands)` +//! +//! # Example +//! +//! ``` +//! # use bevy::prelude::*; +//! # +//! fn system(mut commands: Commands) { +//! helper_function(&mut commands); +//! } +//! +//! // This takes `&mut Commands`, but it doesn't need to! +//! fn helper_function(commands: &mut Commands) { +//! // ... +//! } +//! # +//! # bevy::ecs::system::assert_is_system(system); +//! ``` +//! +//! Use instead: +//! +//! ``` +//! # use bevy::prelude::*; +//! # +//! fn system(mut commands: Commands) { +//! // Convert `&mut Commands` to `Commands`. +//! helper_function(commands.reborrow()); +//! } +//! +//! fn helper_function(mut commands: Commands) { +//! // ... +//! } +//! # +//! # bevy::ecs::system::assert_is_system(system); +//! ``` +//! +//! The following is an example where a type cannot be re-borrowed, for which this lint will not +//! emit any warning: +//! +//! ``` +//! # use bevy::{prelude::*, ecs::system::EntityCommands}; +//! # +//! fn system(mut commands: Commands) { +//! let entity_commands = helper_function(&mut commands); +//! } +//! +//! // Note how this function returns a reference with the same lifetime as `Commands`. +//! fn helper_function<'a>(commands: &'a mut Commands) -> EntityCommands<'a> { +//! commands.spawn_empty() +//! } +//! # +//! # bevy::ecs::system::assert_is_system(system); +//! ``` + +use std::ops::ControlFlow; + +use crate::declare_bevy_lint; +use clippy_utils::{diagnostics::span_lint_and_sugg, ty::match_type}; +use rustc_errors::Applicability; +use rustc_hir::{intravisit::FnKind, Body, FnDecl, Mutability}; +use rustc_lint::{LateContext, LateLintPass}; +use rustc_middle::ty::{Interner, Ty, TyKind, TypeVisitable, TypeVisitor}; +use rustc_session::declare_lint_pass; +use rustc_span::{ + def_id::LocalDefId, + symbol::{kw, Ident}, + Span, +}; + +declare_bevy_lint! { + pub BORROWED_REBORROWABLE, + PEDANTIC, + "parameter takes a mutable reference to a re-borrowable type", +} + +declare_lint_pass! { + BorrowedReborrowable => [BORROWED_REBORROWABLE.lint] +} + +impl<'tcx> LateLintPass<'tcx> for BorrowedReborrowable { + fn check_fn( + &mut self, + cx: &LateContext<'tcx>, + kind: FnKind<'tcx>, + decl: &'tcx FnDecl<'tcx>, + _: &'tcx Body<'tcx>, + _: Span, + def_id: LocalDefId, + ) { + let fn_sig = match kind { + FnKind::Closure => cx.tcx.closure_user_provided_sig(def_id).value, + // We use `instantiate_identity` to discharge the binder since we don't + // mind using placeholders for any bound arguments + _ => cx.tcx.fn_sig(def_id).instantiate_identity(), + }; + + let arg_names = cx.tcx.fn_arg_names(def_id); + + let args = fn_sig.inputs().skip_binder(); + + for (arg_index, arg_ty) in args.iter().enumerate() { + let TyKind::Ref(region, ty, Mutability::Mut) = arg_ty.kind() else { + // We only care about `&mut` parameters + continue; + }; + + let arg_ident = arg_names[arg_index]; + + // This lint would emit a warning on `&mut self` if `self` was reborrowable. This isn't + // useful, though, because it would hurt the ergonomics of using methods of + // reborrowable types. + // + // To avoid this, we skip any parameter named `self`. This won't false-positive on + // other function arguments named `self`, since it is a special keyword that is + // disallowed in other positions. + if arg_ident.name == kw::SelfLower { + continue; + } + + let Some(reborrowable) = Reborrowable::try_from_ty(cx, *ty) else { + // The type is not one of our known re-borrowable types + continue; + }; + + let is_output_bound_to_arg = fn_sig + .output() + .visit_with(&mut ContainsRegion(*region)) + .is_break(); + + if is_output_bound_to_arg { + // We don't want to suggest re-borrowing if the return type's + // lifetime is bound to the argument's reference. + // This is because it's impossible to convert something like: + // `for<'a> (&'a mut Commands<'_, '_>) -> EntityCommands<'a>` + // to something like: + // `for<'a> (Commands<'_, '_>) -> EntityCommands<'a>` + // without getting: `error[E0515]: cannot return value referencing function + // parameter `commands` `` + continue; + } + + let span = decl.inputs[arg_index].span.to(arg_ident.span); + + span_lint_and_sugg( + cx, + BORROWED_REBORROWABLE.lint, + span, + reborrowable.message(), + reborrowable.help(), + reborrowable.suggest(arg_ident, ty.to_string()), + // Not machine-applicable since the function body may need to + // also be updated to account for the removed ref + Applicability::MaybeIncorrect, + ); + } + } +} + +#[derive(Debug, Copy, Clone)] +enum Reborrowable { + Commands, + Deferred, + DeferredWorld, + EntityCommands, + EntityMut, + FilteredEntityMut, + Mut, + MutUntyped, + NonSendMut, + PtrMut, + Query, + ResMut, +} + +impl Reborrowable { + fn try_from_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option { + use crate::paths::*; + + const PATH_MAP: &[(&[&str], Reborrowable)] = &[ + (&COMMANDS, Reborrowable::Commands), + (&DEFERRED, Reborrowable::Deferred), + (&DEFERRED_WORLD, Reborrowable::DeferredWorld), + (&ENTITY_COMMANDS, Reborrowable::EntityCommands), + (&ENTITY_MUT, Reborrowable::EntityMut), + (&FILTERED_ENTITY_MUT, Reborrowable::FilteredEntityMut), + (&MUT, Reborrowable::Mut), + (&MUT_UNTYPED, Reborrowable::MutUntyped), + (&NON_SEND_MUT, Reborrowable::NonSendMut), + (&PTR_MUT, Reborrowable::PtrMut), + (&QUERY, Reborrowable::Query), + (&RES_MUT, Reborrowable::ResMut), + ]; + + for &(path, reborrowable) in PATH_MAP { + if match_type(cx, ty, path) { + return Some(reborrowable); + } + } + + None + } + + fn message(&self) -> String { + let name = self.name(); + format!("parameter takes `&mut {name}` instead of a re-borrowed `{name}`",) + } + + fn name(&self) -> &'static str { + match self { + Self::Commands => "Commands", + Self::Deferred => "Deferred", + Self::DeferredWorld => "DeferredWorld", + Self::EntityCommands => "EntityCommands", + Self::EntityMut => "EntityMut", + Self::FilteredEntityMut => "FilteredEntityMut", + Self::Mut => "Mut", + Self::MutUntyped => "MutUntyped", + Self::NonSendMut => "NonSendMut", + Self::PtrMut => "PtrMut", + Self::Query => "Query", + Self::ResMut => "ResMut", + } + } + + fn help(&self) -> String { + let name = self.name(); + format!("use `{name}` instead") + } + + fn suggest(&self, ident: Ident, ty: String) -> String { + format!("mut {ident}: {ty}") + } +} + +/// [`TypeVisitor`] for checking if the given region is contained in the type. +struct ContainsRegion(pub I::Region); + +impl TypeVisitor for ContainsRegion { + type Result = ControlFlow<()>; + + fn visit_region(&mut self, r: I::Region) -> Self::Result { + if self.0 == r { + ControlFlow::Break(()) + } else { + ControlFlow::Continue(()) + } + } +} diff --git a/bevy_lint/src/lints/mod.rs b/bevy_lint/src/lints/mod.rs index 22b9012..02e26ef 100644 --- a/bevy_lint/src/lints/mod.rs +++ b/bevy_lint/src/lints/mod.rs @@ -7,6 +7,7 @@ use crate::lint::BevyLint; use rustc_lint::{Lint, LintStore}; +pub mod borrowed_reborrowable; pub mod insert_event_resource; pub mod main_return_without_appexit; pub mod missing_reflect; @@ -15,6 +16,7 @@ pub mod plugin_not_ending_in_plugin; pub mod zst_query; pub(crate) static LINTS: &[&BevyLint] = &[ + borrowed_reborrowable::BORROWED_REBORROWABLE, insert_event_resource::INSERT_EVENT_RESOURCE, main_return_without_appexit::MAIN_RETURN_WITHOUT_APPEXIT, panicking_methods::PANICKING_QUERY_METHODS, @@ -30,6 +32,7 @@ pub(crate) fn register_lints(store: &mut LintStore) { } pub(crate) fn register_passes(store: &mut LintStore) { + store.register_late_pass(|_| Box::new(borrowed_reborrowable::BorrowedReborrowable)); store.register_late_pass(|_| Box::new(insert_event_resource::InsertEventResource)); store.register_late_pass(|_| Box::new(main_return_without_appexit::MainReturnWithoutAppExit)); store.register_late_pass(|_| Box::new(missing_reflect::MissingReflect)); diff --git a/bevy_lint/src/paths.rs b/bevy_lint/src/paths.rs index 507ce8c..b277b27 100644 --- a/bevy_lint/src/paths.rs +++ b/bevy_lint/src/paths.rs @@ -7,13 +7,24 @@ //! [`match_def_path()`](clippy_utils::match_def_path). pub const APP: [&str; 3] = ["bevy_app", "app", "App"]; +pub const COMMANDS: [&str; 4] = ["bevy_ecs", "system", "commands", "Commands"]; pub const COMPONENT: [&str; 3] = ["bevy_ecs", "component", "Component"]; +pub const DEFERRED: [&str; 4] = ["bevy_ecs", "system", "system_param", "Deferred"]; +pub const DEFERRED_WORLD: [&str; 4] = ["bevy_ecs", "world", "deferred_world", "DeferredWorld"]; +pub const ENTITY_COMMANDS: [&str; 4] = ["bevy_ecs", "system", "commands", "EntityCommands"]; +pub const ENTITY_MUT: [&str; 4] = ["bevy_ecs", "world", "entity_ref", "EntityMut"]; // Note that this moves to `bevy_ecs::event::base::Event` in 0.15. pub const EVENT: [&str; 3] = ["bevy_ecs", "event", "Event"]; pub const EVENTS: [&str; 3] = ["bevy_ecs", "event", "Events"]; +pub const FILTERED_ENTITY_MUT: [&str; 4] = ["bevy_ecs", "world", "entity_ref", "FilteredEntityMut"]; +pub const MUT: [&str; 3] = ["bevy_ecs", "change_detection", "Mut"]; +pub const MUT_UNTYPED: [&str; 3] = ["bevy_ecs", "change_detection", "MutUntyped"]; +pub const NON_SEND_MUT: [&str; 3] = ["bevy_ecs", "change_detection", "NonSendMut"]; pub const PLUGIN: [&str; 3] = ["bevy_app", "plugin", "Plugin"]; +pub const PTR_MUT: [&str; 2] = ["bevy_ptr", "PtrMut"]; pub const QUERY: [&str; 4] = ["bevy_ecs", "system", "query", "Query"]; pub const QUERY_STATE: [&str; 4] = ["bevy_ecs", "query", "state", "QueryState"]; pub const REFLECT: [&str; 3] = ["bevy_reflect", "reflect", "Reflect"]; +pub const RES_MUT: [&str; 3] = ["bevy_ecs", "change_detection", "ResMut"]; pub const RESOURCE: [&str; 4] = ["bevy_ecs", "system", "system_param", "Resource"]; pub const WORLD: [&str; 3] = ["bevy_ecs", "world", "World"]; diff --git a/bevy_lint/tests/ui/borrowed_reborrowable/bug_174.rs b/bevy_lint/tests/ui/borrowed_reborrowable/bug_174.rs new file mode 100644 index 0000000..f9c66d5 --- /dev/null +++ b/bevy_lint/tests/ui/borrowed_reborrowable/bug_174.rs @@ -0,0 +1,26 @@ +//! This test tracks the bug reported in [#174]. When this starts failing, the bug has been fixed. +//! +//! [#174]: https://github.com/TheBevyFlock/bevy_cli/issues/174 + +//@check-pass + +#![feature(register_tool)] +#![register_tool(bevy)] +#![deny(bevy::borrowed_reborrowable)] + +use bevy::prelude::*; + +fn main() { + let mut world = World::new(); + + closure_wrapper(world.commands(), |commands| commands.spawn_empty().id()); + closure_wrapper2(world.commands(), |commands| commands.spawn_empty().id()); +} + +fn closure_wrapper(mut commands: Commands, f: impl FnOnce(&mut Commands) -> Entity) { + f(&mut commands); +} + +fn closure_wrapper2(mut commands: Commands, f: fn(&mut Commands) -> Entity) { + f(&mut commands); +} diff --git a/bevy_lint/tests/ui/borrowed_reborrowable/closures.rs b/bevy_lint/tests/ui/borrowed_reborrowable/closures.rs new file mode 100644 index 0000000..0fdee2c --- /dev/null +++ b/bevy_lint/tests/ui/borrowed_reborrowable/closures.rs @@ -0,0 +1,20 @@ +//! This tests the `borrowed_reborrowable` lint, specifically when triggered on closure types. + +#![feature(register_tool)] +#![register_tool(bevy)] +#![deny(bevy::borrowed_reborrowable)] + +use bevy::prelude::*; + +fn main() { + let mut world = World::new(); + let mut commands = world.commands(); + + //~| HELP: use `Commands` instead + //~v ERROR: parameter takes `&mut Commands` instead of a re-borrowed `Commands` + let closure = |commands: &mut Commands| { + commands.spawn_empty(); + }; + + closure(&mut commands); +} diff --git a/bevy_lint/tests/ui/borrowed_reborrowable/closures.stderr b/bevy_lint/tests/ui/borrowed_reborrowable/closures.stderr new file mode 100644 index 0000000..060dc6b --- /dev/null +++ b/bevy_lint/tests/ui/borrowed_reborrowable/closures.stderr @@ -0,0 +1,14 @@ +error: parameter takes `&mut Commands` instead of a re-borrowed `Commands` + --> tests/ui/borrowed_reborrowable/closures.rs:15:20 + | +15 | let closure = |commands: &mut Commands| { + | ^^^^^^^^^^^^^^^^^^^^^^^ help: use `Commands` instead: `mut commands: bevy::prelude::Commands<'_, '_>` + | +note: the lint level is defined here + --> tests/ui/borrowed_reborrowable/closures.rs:5:9 + | +5 | #![deny(bevy::borrowed_reborrowable)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 1 previous error + diff --git a/bevy_lint/tests/ui/borrowed_reborrowable/commands.rs b/bevy_lint/tests/ui/borrowed_reborrowable/commands.rs new file mode 100644 index 0000000..1378d9d --- /dev/null +++ b/bevy_lint/tests/ui/borrowed_reborrowable/commands.rs @@ -0,0 +1,48 @@ +//! This tests the `borrowed_reborrowable` lint, specifically when triggered on the `Commands` type. + +#![feature(register_tool)] +#![register_tool(bevy)] +#![deny(bevy::borrowed_reborrowable)] + +use bevy::ecs::system::EntityCommands; +use bevy::prelude::*; + +// OK: Lint does not apply to immutable references +fn immutable_reference(_commands: &Commands) { + // ... +} + +//~| HELP: use `Commands` instead +//~v ERROR: parameter takes `&mut Commands` instead of a re-borrowed `Commands` +fn mutable_reference(commands: &mut Commands) { + commands.spawn_empty(); +} + +//~| HELP: use `Commands` instead +//~v ERROR: parameter takes `&mut Commands` instead of a re-borrowed `Commands` +fn mutable_reference_return<'a>(_commands: &'a mut Commands) -> usize { + 123 +} + +// OK: Lint does not apply when return type relies on reference lifetime +fn mutable_reference_bounded_return<'a>(commands: &'a mut Commands) -> EntityCommands<'a> { + commands.spawn_empty() +} + +// OK: Lint does not apply when return type relies on reference lifetime +fn mutable_reference_bounded_return_complex<'a>( + commands: &'a mut Commands, +) -> Vec<(usize, EntityCommands<'a>)> { + vec![(1, commands.spawn_empty())] +} + +fn main() { + let mut world = World::new(); + let mut commands = world.commands(); + + immutable_reference(&commands); + mutable_reference(&mut commands); + _ = mutable_reference_return(&mut commands); + _ = mutable_reference_bounded_return(&mut commands); + _ = mutable_reference_bounded_return_complex(&mut commands); +} diff --git a/bevy_lint/tests/ui/borrowed_reborrowable/commands.stderr b/bevy_lint/tests/ui/borrowed_reborrowable/commands.stderr new file mode 100644 index 0000000..da047b7 --- /dev/null +++ b/bevy_lint/tests/ui/borrowed_reborrowable/commands.stderr @@ -0,0 +1,20 @@ +error: parameter takes `&mut Commands` instead of a re-borrowed `Commands` + --> tests/ui/borrowed_reborrowable/commands.rs:17:22 + | +17 | fn mutable_reference(commands: &mut Commands) { + | ^^^^^^^^^^^^^^^^^^^^^^^ help: use `Commands` instead: `mut commands: bevy::prelude::Commands<'_, '_>` + | +note: the lint level is defined here + --> tests/ui/borrowed_reborrowable/commands.rs:5:9 + | +5 | #![deny(bevy::borrowed_reborrowable)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: parameter takes `&mut Commands` instead of a re-borrowed `Commands` + --> tests/ui/borrowed_reborrowable/commands.rs:23:33 + | +23 | fn mutable_reference_return<'a>(_commands: &'a mut Commands) -> usize { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `Commands` instead: `mut _commands: bevy::prelude::Commands<'_, '_>` + +error: aborting due to 2 previous errors + diff --git a/bevy_lint/tests/ui/borrowed_reborrowable/deferred.rs b/bevy_lint/tests/ui/borrowed_reborrowable/deferred.rs new file mode 100644 index 0000000..e3d777f --- /dev/null +++ b/bevy_lint/tests/ui/borrowed_reborrowable/deferred.rs @@ -0,0 +1,49 @@ +//! This tests the `borrowed_reborrowable` lint, specifically when triggered on the `Deferred` type. + +#![feature(register_tool)] +#![register_tool(bevy)] +#![deny(bevy::borrowed_reborrowable)] + +use bevy::ecs::world::CommandQueue; +use bevy::prelude::*; + +// OK: Lint does not apply to immutable references +fn immutable_reference(_param: &Deferred) { + // ... +} + +//~| HELP: use `Deferred` instead +//~v ERROR: parameter takes `&mut Deferred` instead of a re-borrowed `Deferred` +fn mutable_reference(_param: &mut Deferred) { + // ... +} + +//~| HELP: use `Deferred` instead +//~v ERROR: parameter takes `&mut Deferred` instead of a re-borrowed `Deferred` +fn mutable_reference_return<'a>(_param: &'a mut Deferred) -> usize { + 123 +} + +// OK: Lint does not apply when return type relies on reference lifetime +fn mutable_reference_bounded_return<'a>(param: &'a mut Deferred) -> &'a CommandQueue { + &*param +} + +// OK: Lint does not apply when return type relies on reference lifetime +fn mutable_reference_bounded_return_complex<'a>( + param: &'a mut Deferred, +) -> Vec<(usize, &'a CommandQueue)> { + vec![(1, &*param)] +} + +fn main() { + fn some_system(mut param: Deferred) { + immutable_reference(¶m); + mutable_reference(&mut param); + _ = mutable_reference_return(&mut param); + _ = mutable_reference_bounded_return(&mut param); + _ = mutable_reference_bounded_return_complex(&mut param); + } + + App::new().add_systems(Update, some_system).run(); +} diff --git a/bevy_lint/tests/ui/borrowed_reborrowable/deferred.stderr b/bevy_lint/tests/ui/borrowed_reborrowable/deferred.stderr new file mode 100644 index 0000000..48b70fb --- /dev/null +++ b/bevy_lint/tests/ui/borrowed_reborrowable/deferred.stderr @@ -0,0 +1,20 @@ +error: parameter takes `&mut Deferred` instead of a re-borrowed `Deferred` + --> tests/ui/borrowed_reborrowable/deferred.rs:17:22 + | +17 | fn mutable_reference(_param: &mut Deferred) { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `Deferred` instead: `mut _param: bevy::prelude::Deferred<'_, bevy::bevy_ecs::world::CommandQueue>` + | +note: the lint level is defined here + --> tests/ui/borrowed_reborrowable/deferred.rs:5:9 + | +5 | #![deny(bevy::borrowed_reborrowable)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: parameter takes `&mut Deferred` instead of a re-borrowed `Deferred` + --> tests/ui/borrowed_reborrowable/deferred.rs:23:33 + | +23 | fn mutable_reference_return<'a>(_param: &'a mut Deferred) -> usize { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `Deferred` instead: `mut _param: bevy::prelude::Deferred<'_, bevy::bevy_ecs::world::CommandQueue>` + +error: aborting due to 2 previous errors + diff --git a/bevy_lint/tests/ui/borrowed_reborrowable/deferred_world.rs b/bevy_lint/tests/ui/borrowed_reborrowable/deferred_world.rs new file mode 100644 index 0000000..ecea63c --- /dev/null +++ b/bevy_lint/tests/ui/borrowed_reborrowable/deferred_world.rs @@ -0,0 +1,49 @@ +//! This tests the `borrowed_reborrowable` lint, specifically when triggered on the `DeferredWorld` type. + +#![feature(register_tool)] +#![register_tool(bevy)] +#![deny(bevy::borrowed_reborrowable)] + +use bevy::prelude::*; +use bevy::ecs::world::DeferredWorld; + +// OK: Lint does not apply to immutable references +fn immutable_reference(_param: &DeferredWorld) { + // ... +} + +//~| HELP: use `DeferredWorld` instead +//~v ERROR: parameter takes `&mut DeferredWorld` instead of a re-borrowed `DeferredWorld` +fn mutable_reference(_param: &mut DeferredWorld) { + // ... +} + +//~| HELP: use `DeferredWorld` instead +//~v ERROR: parameter takes `&mut DeferredWorld` instead of a re-borrowed `DeferredWorld` +fn mutable_reference_return<'a>(_param: &'a mut DeferredWorld) -> usize { + 123 +} + +// OK: Lint does not apply when return type relies on reference lifetime +fn mutable_reference_bounded_return<'a>(param: &'a mut DeferredWorld) -> Commands<'a, 'a> { + param.commands() +} + +// OK: Lint does not apply when return type relies on reference lifetime +fn mutable_reference_bounded_return_complex<'a>( + param: &'a mut DeferredWorld, +) -> Vec<(usize, Commands<'a, 'a>)> { + vec![(1, param.commands())] +} + +fn main() { + fn some_system(mut param: DeferredWorld) { + immutable_reference(¶m); + mutable_reference(&mut param); + _ = mutable_reference_return(&mut param); + _ = mutable_reference_bounded_return(&mut param); + _ = mutable_reference_bounded_return_complex(&mut param); + } + + App::new().add_systems(Update, some_system).run(); +} diff --git a/bevy_lint/tests/ui/borrowed_reborrowable/deferred_world.stderr b/bevy_lint/tests/ui/borrowed_reborrowable/deferred_world.stderr new file mode 100644 index 0000000..209b35c --- /dev/null +++ b/bevy_lint/tests/ui/borrowed_reborrowable/deferred_world.stderr @@ -0,0 +1,20 @@ +error: parameter takes `&mut DeferredWorld` instead of a re-borrowed `DeferredWorld` + --> tests/ui/borrowed_reborrowable/deferred_world.rs:17:22 + | +17 | fn mutable_reference(_param: &mut DeferredWorld) { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `DeferredWorld` instead: `mut _param: bevy::bevy_ecs::world::DeferredWorld<'_>` + | +note: the lint level is defined here + --> tests/ui/borrowed_reborrowable/deferred_world.rs:5:9 + | +5 | #![deny(bevy::borrowed_reborrowable)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: parameter takes `&mut DeferredWorld` instead of a re-borrowed `DeferredWorld` + --> tests/ui/borrowed_reborrowable/deferred_world.rs:23:33 + | +23 | fn mutable_reference_return<'a>(_param: &'a mut DeferredWorld) -> usize { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `DeferredWorld` instead: `mut _param: bevy::bevy_ecs::world::DeferredWorld<'_>` + +error: aborting due to 2 previous errors + diff --git a/bevy_lint/tests/ui/borrowed_reborrowable/entity_commands.rs b/bevy_lint/tests/ui/borrowed_reborrowable/entity_commands.rs new file mode 100644 index 0000000..b9229a1 --- /dev/null +++ b/bevy_lint/tests/ui/borrowed_reborrowable/entity_commands.rs @@ -0,0 +1,50 @@ +//! This tests the `borrowed_reborrowable` lint, specifically when triggered on the `EntityCommands` type. + +#![feature(register_tool)] +#![register_tool(bevy)] +#![deny(bevy::borrowed_reborrowable)] + +use bevy::prelude::*; +use bevy::ecs::system::EntityCommands; + +// OK: Lint does not apply to immutable references +fn immutable_reference(_param: &EntityCommands) { + // ... +} + +//~| HELP: use `EntityCommands` instead +//~v ERROR: parameter takes `&mut EntityCommands` instead of a re-borrowed `EntityCommands` +fn mutable_reference(_param: &mut EntityCommands) { + // ... +} + +//~| HELP: use `EntityCommands` instead +//~v ERROR: parameter takes `&mut EntityCommands` instead of a re-borrowed `EntityCommands` +fn mutable_reference_return<'a>(_param: &'a mut EntityCommands) -> usize { + 123 +} + +// OK: Lint does not apply when return type relies on reference lifetime +fn mutable_reference_bounded_return<'a>(param: &'a mut EntityCommands) -> Commands<'a, 'a> { + param.commands() +} + +// OK: Lint does not apply when return type relies on reference lifetime +fn mutable_reference_bounded_return_complex<'a>( + param: &'a mut EntityCommands, +) -> Vec<(usize, Commands<'a, 'a>)> { + vec![(1, param.commands())] +} + +fn main() { + fn some_system(mut param: Commands) { + let mut param = param.spawn_empty(); + immutable_reference(¶m); + mutable_reference(&mut param); + _ = mutable_reference_return(&mut param); + _ = mutable_reference_bounded_return(&mut param); + _ = mutable_reference_bounded_return_complex(&mut param); + } + + App::new().add_systems(Update, some_system).run(); +} diff --git a/bevy_lint/tests/ui/borrowed_reborrowable/entity_commands.stderr b/bevy_lint/tests/ui/borrowed_reborrowable/entity_commands.stderr new file mode 100644 index 0000000..cc3ce96 --- /dev/null +++ b/bevy_lint/tests/ui/borrowed_reborrowable/entity_commands.stderr @@ -0,0 +1,20 @@ +error: parameter takes `&mut EntityCommands` instead of a re-borrowed `EntityCommands` + --> tests/ui/borrowed_reborrowable/entity_commands.rs:17:22 + | +17 | fn mutable_reference(_param: &mut EntityCommands) { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `EntityCommands` instead: `mut _param: bevy::bevy_ecs::system::EntityCommands<'_>` + | +note: the lint level is defined here + --> tests/ui/borrowed_reborrowable/entity_commands.rs:5:9 + | +5 | #![deny(bevy::borrowed_reborrowable)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: parameter takes `&mut EntityCommands` instead of a re-borrowed `EntityCommands` + --> tests/ui/borrowed_reborrowable/entity_commands.rs:23:33 + | +23 | fn mutable_reference_return<'a>(_param: &'a mut EntityCommands) -> usize { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `EntityCommands` instead: `mut _param: bevy::bevy_ecs::system::EntityCommands<'_>` + +error: aborting due to 2 previous errors + diff --git a/bevy_lint/tests/ui/borrowed_reborrowable/entity_mut copy.stderr b/bevy_lint/tests/ui/borrowed_reborrowable/entity_mut copy.stderr new file mode 100644 index 0000000..59e5b8d --- /dev/null +++ b/bevy_lint/tests/ui/borrowed_reborrowable/entity_mut copy.stderr @@ -0,0 +1,20 @@ +error: parameter takes `&mut EntityMut` instead of a re-borrowed `EntityMut` + --> tests/ui/borrowed_reborrowable/entity_mut.rs:17:22 + | +17 | fn mutable_reference(_param: &mut EntityMut) { + | ^^^^^^^^^^^^^^^^^^^^^^ help: use `EntityMut` instead: `mut _param: bevy::prelude::EntityMut<'_>` + | +note: the lint level is defined here + --> tests/ui/borrowed_reborrowable/entity_mut.rs:5:9 + | +5 | #![deny(bevy::borrowed_reborrowable)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: parameter takes `&mut EntityMut` instead of a re-borrowed `EntityMut` + --> tests/ui/borrowed_reborrowable/entity_mut.rs:23:33 + | +23 | fn mutable_reference_return<'a>(_param: &'a mut EntityMut) -> usize { + | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `EntityMut` instead: `mut _param: bevy::prelude::EntityMut<'_>` + +error: aborting due to 2 previous errors + diff --git a/bevy_lint/tests/ui/borrowed_reborrowable/entity_mut.rs b/bevy_lint/tests/ui/borrowed_reborrowable/entity_mut.rs new file mode 100644 index 0000000..9d525cb --- /dev/null +++ b/bevy_lint/tests/ui/borrowed_reborrowable/entity_mut.rs @@ -0,0 +1,50 @@ +//! This tests the `borrowed_reborrowable` lint, specifically when triggered on the `EntityMut` type. + +#![feature(register_tool)] +#![register_tool(bevy)] +#![deny(bevy::borrowed_reborrowable)] + +use bevy::ecs::prelude::{EntityMut, EntityRef}; +use bevy::prelude::*; + +// OK: Lint does not apply to immutable references +fn immutable_reference(_param: &EntityMut) { + // ... +} + +//~| HELP: use `EntityMut` instead +//~v ERROR: parameter takes `&mut EntityMut` instead of a re-borrowed `EntityMut` +fn mutable_reference(_param: &mut EntityMut) { + // ... +} + +//~| HELP: use `EntityMut` instead +//~v ERROR: parameter takes `&mut EntityMut` instead of a re-borrowed `EntityMut` +fn mutable_reference_return<'a>(_param: &'a mut EntityMut) -> usize { + 123 +} + +// OK: Lint does not apply when return type relies on reference lifetime +fn mutable_reference_bounded_return<'a>(param: &'a mut EntityMut) -> EntityRef<'a> { + param.as_readonly() +} + +// OK: Lint does not apply when return type relies on reference lifetime +fn mutable_reference_bounded_return_complex<'a>( + param: &'a mut EntityMut, +) -> Vec<(usize, EntityRef<'a>)> { + vec![(1, param.as_readonly())] +} + +fn main() { + fn some_system(world: &mut World) { + let mut param: EntityMut = world.spawn_empty().into(); + immutable_reference(¶m); + mutable_reference(&mut param); + _ = mutable_reference_return(&mut param); + _ = mutable_reference_bounded_return(&mut param); + _ = mutable_reference_bounded_return_complex(&mut param); + } + + App::new().add_systems(Update, some_system).run(); +} diff --git a/bevy_lint/tests/ui/borrowed_reborrowable/entity_mut.stderr b/bevy_lint/tests/ui/borrowed_reborrowable/entity_mut.stderr new file mode 100644 index 0000000..59e5b8d --- /dev/null +++ b/bevy_lint/tests/ui/borrowed_reborrowable/entity_mut.stderr @@ -0,0 +1,20 @@ +error: parameter takes `&mut EntityMut` instead of a re-borrowed `EntityMut` + --> tests/ui/borrowed_reborrowable/entity_mut.rs:17:22 + | +17 | fn mutable_reference(_param: &mut EntityMut) { + | ^^^^^^^^^^^^^^^^^^^^^^ help: use `EntityMut` instead: `mut _param: bevy::prelude::EntityMut<'_>` + | +note: the lint level is defined here + --> tests/ui/borrowed_reborrowable/entity_mut.rs:5:9 + | +5 | #![deny(bevy::borrowed_reborrowable)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: parameter takes `&mut EntityMut` instead of a re-borrowed `EntityMut` + --> tests/ui/borrowed_reborrowable/entity_mut.rs:23:33 + | +23 | fn mutable_reference_return<'a>(_param: &'a mut EntityMut) -> usize { + | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `EntityMut` instead: `mut _param: bevy::prelude::EntityMut<'_>` + +error: aborting due to 2 previous errors + diff --git a/bevy_lint/tests/ui/borrowed_reborrowable/filtered_entity_mut.rs b/bevy_lint/tests/ui/borrowed_reborrowable/filtered_entity_mut.rs new file mode 100644 index 0000000..93eb351 --- /dev/null +++ b/bevy_lint/tests/ui/borrowed_reborrowable/filtered_entity_mut.rs @@ -0,0 +1,50 @@ +//! This tests the `borrowed_reborrowable` lint, specifically when triggered on the `FilteredEntityMut` type. + +#![feature(register_tool)] +#![register_tool(bevy)] +#![deny(bevy::borrowed_reborrowable)] + +use bevy::ecs::world::{FilteredEntityMut, FilteredEntityRef}; +use bevy::prelude::*; + +// OK: Lint does not apply to immutable references +fn immutable_reference(_param: &FilteredEntityMut) { + // ... +} + +//~| HELP: use `FilteredEntityMut` instead +//~v ERROR: parameter takes `&mut FilteredEntityMut` instead of a re-borrowed `FilteredEntityMut` +fn mutable_reference(_param: &mut FilteredEntityMut) { + // ... +} + +//~| HELP: use `FilteredEntityMut` instead +//~v ERROR: parameter takes `&mut FilteredEntityMut` instead of a re-borrowed `FilteredEntityMut` +fn mutable_reference_return<'a>(_param: &'a mut FilteredEntityMut) -> usize { + 123 +} + +// OK: Lint does not apply when return type relies on reference lifetime +fn mutable_reference_bounded_return<'a>(param: &'a mut FilteredEntityMut) -> FilteredEntityRef<'a> { + param.as_readonly() +} + +// OK: Lint does not apply when return type relies on reference lifetime +fn mutable_reference_bounded_return_complex<'a>( + param: &'a mut FilteredEntityMut, +) -> Vec<(usize, FilteredEntityRef<'a>)> { + vec![(1, param.as_readonly())] +} + +fn main() { + fn some_system(world: &mut World) { + let mut param: FilteredEntityMut = world.spawn_empty().into(); + immutable_reference(¶m); + mutable_reference(&mut param); + _ = mutable_reference_return(&mut param); + _ = mutable_reference_bounded_return(&mut param); + _ = mutable_reference_bounded_return_complex(&mut param); + } + + App::new().add_systems(Update, some_system).run(); +} diff --git a/bevy_lint/tests/ui/borrowed_reborrowable/filtered_entity_mut.stderr b/bevy_lint/tests/ui/borrowed_reborrowable/filtered_entity_mut.stderr new file mode 100644 index 0000000..f1c2ac8 --- /dev/null +++ b/bevy_lint/tests/ui/borrowed_reborrowable/filtered_entity_mut.stderr @@ -0,0 +1,20 @@ +error: parameter takes `&mut FilteredEntityMut` instead of a re-borrowed `FilteredEntityMut` + --> tests/ui/borrowed_reborrowable/filtered_entity_mut.rs:17:22 + | +17 | fn mutable_reference(_param: &mut FilteredEntityMut) { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `FilteredEntityMut` instead: `mut _param: bevy::bevy_ecs::world::FilteredEntityMut<'_>` + | +note: the lint level is defined here + --> tests/ui/borrowed_reborrowable/filtered_entity_mut.rs:5:9 + | +5 | #![deny(bevy::borrowed_reborrowable)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: parameter takes `&mut FilteredEntityMut` instead of a re-borrowed `FilteredEntityMut` + --> tests/ui/borrowed_reborrowable/filtered_entity_mut.rs:23:33 + | +23 | fn mutable_reference_return<'a>(_param: &'a mut FilteredEntityMut) -> usize { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `FilteredEntityMut` instead: `mut _param: bevy::bevy_ecs::world::FilteredEntityMut<'_>` + +error: aborting due to 2 previous errors + diff --git a/bevy_lint/tests/ui/borrowed_reborrowable/mut_typed.rs b/bevy_lint/tests/ui/borrowed_reborrowable/mut_typed.rs new file mode 100644 index 0000000..83866fc --- /dev/null +++ b/bevy_lint/tests/ui/borrowed_reborrowable/mut_typed.rs @@ -0,0 +1,50 @@ +//! This tests the `borrowed_reborrowable` lint, specifically when triggered on the `Mut` type. + +#![feature(register_tool)] +#![register_tool(bevy)] +#![deny(bevy::borrowed_reborrowable)] + +use bevy::ecs::prelude::Mut; +use bevy::prelude::*; + +// OK: Lint does not apply to immutable references +fn immutable_reference(_param: &Mut) { + // ... +} + +//~| HELP: use `Mut` instead +//~v ERROR: parameter takes `&mut Mut` instead of a re-borrowed `Mut` +fn mutable_reference(_param: &mut Mut) { + // ... +} + +//~| HELP: use `Mut` instead +//~v ERROR: parameter takes `&mut Mut` instead of a re-borrowed `Mut` +fn mutable_reference_return<'a>(_param: &'a mut Mut) -> usize { + 123 +} + +// OK: Lint does not apply when return type relies on reference lifetime +fn mutable_reference_bounded_return<'a>(param: &'a mut Mut) -> &'a mut Name { + param.as_mut() +} + +// OK: Lint does not apply when return type relies on reference lifetime +fn mutable_reference_bounded_return_complex<'a>( + param: &'a mut Mut, +) -> Vec<(usize, &'a mut Name)> { + vec![(1, param.as_mut())] +} + +fn main() { + fn some_system(world: &mut World) { + let mut param = world.spawn(Name::new("test")).into_mut::().unwrap(); + immutable_reference(¶m); + mutable_reference(&mut param); + _ = mutable_reference_return(&mut param); + _ = mutable_reference_bounded_return(&mut param); + _ = mutable_reference_bounded_return_complex(&mut param); + } + + App::new().add_systems(Update, some_system).run(); +} diff --git a/bevy_lint/tests/ui/borrowed_reborrowable/mut_typed.stderr b/bevy_lint/tests/ui/borrowed_reborrowable/mut_typed.stderr new file mode 100644 index 0000000..6f13987 --- /dev/null +++ b/bevy_lint/tests/ui/borrowed_reborrowable/mut_typed.stderr @@ -0,0 +1,20 @@ +error: parameter takes `&mut Mut` instead of a re-borrowed `Mut` + --> tests/ui/borrowed_reborrowable/mut_typed.rs:17:22 + | +17 | fn mutable_reference(_param: &mut Mut) { + | ^^^^^^^^^^^^^^^^^^^^^^ help: use `Mut` instead: `mut _param: bevy::prelude::Mut<'_, bevy::prelude::Name>` + | +note: the lint level is defined here + --> tests/ui/borrowed_reborrowable/mut_typed.rs:5:9 + | +5 | #![deny(bevy::borrowed_reborrowable)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: parameter takes `&mut Mut` instead of a re-borrowed `Mut` + --> tests/ui/borrowed_reborrowable/mut_typed.rs:23:33 + | +23 | fn mutable_reference_return<'a>(_param: &'a mut Mut) -> usize { + | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `Mut` instead: `mut _param: bevy::prelude::Mut<'_, bevy::prelude::Name>` + +error: aborting due to 2 previous errors + diff --git a/bevy_lint/tests/ui/borrowed_reborrowable/mut_untyped.rs b/bevy_lint/tests/ui/borrowed_reborrowable/mut_untyped.rs new file mode 100644 index 0000000..38823a7 --- /dev/null +++ b/bevy_lint/tests/ui/borrowed_reborrowable/mut_untyped.rs @@ -0,0 +1,55 @@ +//! This tests the `borrowed_reborrowable` lint, specifically when triggered on the `MutUntyped` type. + +#![feature(register_tool)] +#![register_tool(bevy)] +#![deny(bevy::borrowed_reborrowable)] + +use bevy::ecs::change_detection::MutUntyped; +use bevy::ecs::ptr::PtrMut; +use bevy::prelude::*; + +// OK: Lint does not apply to immutable references +fn immutable_reference(_param: &MutUntyped) { + // ... +} + +//~| HELP: use `MutUntyped` instead +//~v ERROR: parameter takes `&mut MutUntyped` instead of a re-borrowed `MutUntyped` +fn mutable_reference(_param: &mut MutUntyped) { + // ... +} + +//~| HELP: use `MutUntyped` instead +//~v ERROR: parameter takes `&mut MutUntyped` instead of a re-borrowed `MutUntyped` +fn mutable_reference_return<'a>(_param: &'a mut MutUntyped) -> usize { + 123 +} + +// OK: Lint does not apply when return type relies on reference lifetime +fn mutable_reference_bounded_return<'a>(param: &'a mut MutUntyped) -> PtrMut<'a> { + param.as_mut() +} + +// OK: Lint does not apply when return type relies on reference lifetime +fn mutable_reference_bounded_return_complex<'a>( + param: &'a mut MutUntyped, +) -> Vec<(usize, PtrMut<'a>)> { + vec![(1, param.as_mut())] +} + +fn main() { + fn some_system(world: &mut World) { + let mut param: MutUntyped = world + .spawn(Name::new("test")) + .into_mut::() + .unwrap() + .into(); + immutable_reference(¶m); + mutable_reference(&mut param); + _ = mutable_reference_return(&mut param); + _ = mutable_reference_bounded_return(&mut param); + _ = mutable_reference_bounded_return_complex(&mut param); + } + + App::new().add_systems(Update, some_system).run(); +} diff --git a/bevy_lint/tests/ui/borrowed_reborrowable/mut_untyped.stderr b/bevy_lint/tests/ui/borrowed_reborrowable/mut_untyped.stderr new file mode 100644 index 0000000..871975d --- /dev/null +++ b/bevy_lint/tests/ui/borrowed_reborrowable/mut_untyped.stderr @@ -0,0 +1,20 @@ +error: parameter takes `&mut MutUntyped` instead of a re-borrowed `MutUntyped` + --> tests/ui/borrowed_reborrowable/mut_untyped.rs:18:22 + | +18 | fn mutable_reference(_param: &mut MutUntyped) { + | ^^^^^^^^^^^^^^^^^^^^^^^ help: use `MutUntyped` instead: `mut _param: bevy::bevy_ecs::change_detection::MutUntyped<'_>` + | +note: the lint level is defined here + --> tests/ui/borrowed_reborrowable/mut_untyped.rs:5:9 + | +5 | #![deny(bevy::borrowed_reborrowable)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: parameter takes `&mut MutUntyped` instead of a re-borrowed `MutUntyped` + --> tests/ui/borrowed_reborrowable/mut_untyped.rs:24:33 + | +24 | fn mutable_reference_return<'a>(_param: &'a mut MutUntyped) -> usize { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `MutUntyped` instead: `mut _param: bevy::bevy_ecs::change_detection::MutUntyped<'_>` + +error: aborting due to 2 previous errors + diff --git a/bevy_lint/tests/ui/borrowed_reborrowable/non_send_mut.rs b/bevy_lint/tests/ui/borrowed_reborrowable/non_send_mut.rs new file mode 100644 index 0000000..74ff95a --- /dev/null +++ b/bevy_lint/tests/ui/borrowed_reborrowable/non_send_mut.rs @@ -0,0 +1,51 @@ +//! This tests the `borrowed_reborrowable` lint, specifically when triggered on the `NonSendMut` type. + +#![feature(register_tool)] +#![register_tool(bevy)] +#![deny(bevy::borrowed_reborrowable)] + +use bevy::prelude::*; + +#[derive(Resource)] +struct MyResource; + +// OK: Lint does not apply to immutable references +fn immutable_reference(_param: &NonSendMut) { + // ... +} + +//~| HELP: use `NonSendMut` instead +//~v ERROR: parameter takes `&mut NonSendMut` instead of a re-borrowed `NonSendMut` +fn mutable_reference(_param: &mut NonSendMut) { + // ... +} + +//~| HELP: use `NonSendMut` instead +//~v ERROR: parameter takes `&mut NonSendMut` instead of a re-borrowed `NonSendMut` +fn mutable_reference_return<'a>(_param: &'a mut NonSendMut) -> usize { + 123 +} + +// OK: Lint does not apply when return type relies on reference lifetime +fn mutable_reference_bounded_return<'a>(param: &'a mut NonSendMut) -> &'a MyResource { + &*param +} + +// OK: Lint does not apply when return type relies on reference lifetime +fn mutable_reference_bounded_return_complex<'a>( + param: &'a mut NonSendMut, +) -> Vec<(usize, &'a MyResource)> { + vec![(1, &*param)] +} + +fn main() { + fn some_system(mut param: NonSendMut) { + immutable_reference(¶m); + mutable_reference(&mut param); + _ = mutable_reference_return(&mut param); + _ = mutable_reference_bounded_return(&mut param); + _ = mutable_reference_bounded_return_complex(&mut param); + } + + App::new().add_systems(Update, some_system).run(); +} diff --git a/bevy_lint/tests/ui/borrowed_reborrowable/non_send_mut.stderr b/bevy_lint/tests/ui/borrowed_reborrowable/non_send_mut.stderr new file mode 100644 index 0000000..0a79bf4 --- /dev/null +++ b/bevy_lint/tests/ui/borrowed_reborrowable/non_send_mut.stderr @@ -0,0 +1,20 @@ +error: parameter takes `&mut NonSendMut` instead of a re-borrowed `NonSendMut` + --> tests/ui/borrowed_reborrowable/non_send_mut.rs:19:22 + | +19 | fn mutable_reference(_param: &mut NonSendMut) { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `NonSendMut` instead: `mut _param: bevy::prelude::NonSendMut<'_, MyResource>` + | +note: the lint level is defined here + --> tests/ui/borrowed_reborrowable/non_send_mut.rs:5:9 + | +5 | #![deny(bevy::borrowed_reborrowable)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: parameter takes `&mut NonSendMut` instead of a re-borrowed `NonSendMut` + --> tests/ui/borrowed_reborrowable/non_send_mut.rs:25:33 + | +25 | fn mutable_reference_return<'a>(_param: &'a mut NonSendMut) -> usize { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `NonSendMut` instead: `mut _param: bevy::prelude::NonSendMut<'_, MyResource>` + +error: aborting due to 2 previous errors + diff --git a/bevy_lint/tests/ui/borrowed_reborrowable/ptr_mut.rs b/bevy_lint/tests/ui/borrowed_reborrowable/ptr_mut.rs new file mode 100644 index 0000000..001e308 --- /dev/null +++ b/bevy_lint/tests/ui/borrowed_reborrowable/ptr_mut.rs @@ -0,0 +1,48 @@ +//! This tests the `borrowed_reborrowable` lint, specifically when triggered on the `PtrMut` type. + +#![feature(register_tool)] +#![register_tool(bevy)] +#![deny(bevy::borrowed_reborrowable)] + +use bevy::ecs::ptr::{Ptr, PtrMut}; +use bevy::prelude::*; + +// OK: Lint does not apply to immutable references +fn immutable_reference(_param: &PtrMut) { + // ... +} + +//~| HELP: use `PtrMut` instead +//~v ERROR: parameter takes `&mut PtrMut` instead of a re-borrowed `PtrMut` +fn mutable_reference(_param: &mut PtrMut) { + // ... +} + +//~| HELP: use `PtrMut` instead +//~v ERROR: parameter takes `&mut PtrMut` instead of a re-borrowed `PtrMut` +fn mutable_reference_return<'a>(_param: &'a mut PtrMut) -> usize { + 123 +} + +// OK: Lint does not apply when return type relies on reference lifetime +fn mutable_reference_bounded_return<'a>(param: &'a mut PtrMut) -> Ptr<'a> { + param.as_ref() +} + +// OK: Lint does not apply when return type relies on reference lifetime +fn mutable_reference_bounded_return_complex<'a>(param: &'a mut PtrMut) -> Vec<(usize, Ptr<'a>)> { + vec![(1, param.as_ref())] +} + +fn main() { + fn some_system(world: &mut World) { + let mut param: PtrMut = world.into(); + immutable_reference(¶m); + mutable_reference(&mut param); + _ = mutable_reference_return(&mut param); + _ = mutable_reference_bounded_return(&mut param); + _ = mutable_reference_bounded_return_complex(&mut param); + } + + App::new().add_systems(Update, some_system).run(); +} diff --git a/bevy_lint/tests/ui/borrowed_reborrowable/ptr_mut.stderr b/bevy_lint/tests/ui/borrowed_reborrowable/ptr_mut.stderr new file mode 100644 index 0000000..979b7f9 --- /dev/null +++ b/bevy_lint/tests/ui/borrowed_reborrowable/ptr_mut.stderr @@ -0,0 +1,20 @@ +error: parameter takes `&mut PtrMut` instead of a re-borrowed `PtrMut` + --> tests/ui/borrowed_reborrowable/ptr_mut.rs:17:22 + | +17 | fn mutable_reference(_param: &mut PtrMut) { + | ^^^^^^^^^^^^^^^^^^^ help: use `PtrMut` instead: `mut _param: bevy::bevy_ptr::PtrMut<'_>` + | +note: the lint level is defined here + --> tests/ui/borrowed_reborrowable/ptr_mut.rs:5:9 + | +5 | #![deny(bevy::borrowed_reborrowable)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: parameter takes `&mut PtrMut` instead of a re-borrowed `PtrMut` + --> tests/ui/borrowed_reborrowable/ptr_mut.rs:23:33 + | +23 | fn mutable_reference_return<'a>(_param: &'a mut PtrMut) -> usize { + | ^^^^^^^^^^^^^^^^^^^^^^ help: use `PtrMut` instead: `mut _param: bevy::bevy_ptr::PtrMut<'_>` + +error: aborting due to 2 previous errors + diff --git a/bevy_lint/tests/ui/borrowed_reborrowable/query.rs b/bevy_lint/tests/ui/borrowed_reborrowable/query.rs new file mode 100644 index 0000000..8509dec --- /dev/null +++ b/bevy_lint/tests/ui/borrowed_reborrowable/query.rs @@ -0,0 +1,49 @@ +//! This tests the `borrowed_reborrowable` lint, specifically when triggered on the `Query` type. + +#![feature(register_tool)] +#![register_tool(bevy)] +#![deny(bevy::borrowed_reborrowable)] + +use bevy::ecs::system::QueryLens; +use bevy::prelude::*; + +// OK: Lint does not apply to immutable references +fn immutable_reference(_query: &Query) { + // ... +} + +//~| HELP: use `Query` instead +//~v ERROR: parameter takes `&mut Query` instead of a re-borrowed `Query` +fn mutable_reference(query: &mut Query) { + query.iter_mut(); +} + +//~| HELP: use `Query` instead +//~v ERROR: parameter takes `&mut Query` instead of a re-borrowed `Query` +fn mutable_reference_return<'a>(_query: &'a mut Query) -> usize { + 123 +} + +// OK: Lint does not apply when return type relies on reference lifetime +fn mutable_reference_bounded_return<'a>(query: &'a mut Query) -> QueryLens<'a, Entity> { + query.as_query_lens() +} + +// OK: Lint does not apply when return type relies on reference lifetime +fn mutable_reference_bounded_return_complex<'a>( + query: &'a mut Query, +) -> Vec<(usize, QueryLens<'a, Entity>)> { + vec![(1, query.as_query_lens())] +} + +fn main() { + fn some_system(mut query: Query) { + immutable_reference(&query); + mutable_reference(&mut query); + _ = mutable_reference_return(&mut query); + _ = mutable_reference_bounded_return(&mut query); + _ = mutable_reference_bounded_return_complex(&mut query); + } + + App::new().add_systems(Update, some_system).run(); +} diff --git a/bevy_lint/tests/ui/borrowed_reborrowable/query.stderr b/bevy_lint/tests/ui/borrowed_reborrowable/query.stderr new file mode 100644 index 0000000..a064dba --- /dev/null +++ b/bevy_lint/tests/ui/borrowed_reborrowable/query.stderr @@ -0,0 +1,20 @@ +error: parameter takes `&mut Query` instead of a re-borrowed `Query` + --> tests/ui/borrowed_reborrowable/query.rs:17:22 + | +17 | fn mutable_reference(query: &mut Query) { + | ^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `Query` instead: `mut query: bevy::prelude::Query<'_, '_, bevy::prelude::Entity>` + | +note: the lint level is defined here + --> tests/ui/borrowed_reborrowable/query.rs:5:9 + | +5 | #![deny(bevy::borrowed_reborrowable)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: parameter takes `&mut Query` instead of a re-borrowed `Query` + --> tests/ui/borrowed_reborrowable/query.rs:23:33 + | +23 | fn mutable_reference_return<'a>(_query: &'a mut Query) -> usize { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `Query` instead: `mut _query: bevy::prelude::Query<'_, '_, bevy::prelude::Entity>` + +error: aborting due to 2 previous errors + diff --git a/bevy_lint/tests/ui/borrowed_reborrowable/resource.rs b/bevy_lint/tests/ui/borrowed_reborrowable/resource.rs new file mode 100644 index 0000000..fd96176 --- /dev/null +++ b/bevy_lint/tests/ui/borrowed_reborrowable/resource.rs @@ -0,0 +1,51 @@ +//! This tests the `borrowed_reborrowable` lint, specifically when triggered on the `ResMut` type. + +#![feature(register_tool)] +#![register_tool(bevy)] +#![deny(bevy::borrowed_reborrowable)] + +use bevy::prelude::*; + +#[derive(Resource)] +struct Data(String); + +// OK: Lint does not apply to immutable references +fn immutable_reference(_res: &ResMut) { + // ... +} + +//~| HELP: use `ResMut` instead +//~v ERROR: parameter takes `&mut ResMut` instead of a re-borrowed `ResMut` +fn mutable_reference(_res: &mut ResMut) { + // ... +} + +//~| HELP: use `ResMut` instead +//~v ERROR: parameter takes `&mut ResMut` instead of a re-borrowed `ResMut` +fn mutable_reference_return<'a>(_res: &'a mut ResMut) -> usize { + 123 +} + +// OK: Lint does not apply when return type relies on reference lifetime +fn mutable_reference_bounded_return<'a>(res: &'a mut ResMut) -> &'a mut String { + &mut res.0 +} + +// OK: Lint does not apply when return type relies on reference lifetime +fn mutable_reference_bounded_return_complex<'a>( + res: &'a mut ResMut, +) -> Vec<(usize, &'a mut String)> { + vec![(1, &mut res.0)] +} + +fn main() { + fn some_system(mut res: ResMut) { + immutable_reference(&res); + mutable_reference(&mut res); + _ = mutable_reference_return(&mut res); + _ = mutable_reference_bounded_return(&mut res); + _ = mutable_reference_bounded_return_complex(&mut res); + } + + App::new().add_systems(Update, some_system).run(); +} diff --git a/bevy_lint/tests/ui/borrowed_reborrowable/resource.stderr b/bevy_lint/tests/ui/borrowed_reborrowable/resource.stderr new file mode 100644 index 0000000..285433f --- /dev/null +++ b/bevy_lint/tests/ui/borrowed_reborrowable/resource.stderr @@ -0,0 +1,20 @@ +error: parameter takes `&mut ResMut` instead of a re-borrowed `ResMut` + --> tests/ui/borrowed_reborrowable/resource.rs:19:22 + | +19 | fn mutable_reference(_res: &mut ResMut) { + | ^^^^^^^^^^^^^^^^^^^^^^^ help: use `ResMut` instead: `mut _res: bevy::prelude::ResMut<'_, Data>` + | +note: the lint level is defined here + --> tests/ui/borrowed_reborrowable/resource.rs:5:9 + | +5 | #![deny(bevy::borrowed_reborrowable)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: parameter takes `&mut ResMut` instead of a re-borrowed `ResMut` + --> tests/ui/borrowed_reborrowable/resource.rs:25:33 + | +25 | fn mutable_reference_return<'a>(_res: &'a mut ResMut) -> usize { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use `ResMut` instead: `mut _res: bevy::prelude::ResMut<'_, Data>` + +error: aborting due to 2 previous errors + diff --git a/bevy_lint/tests/ui/borrowed_reborrowable/self_param.rs b/bevy_lint/tests/ui/borrowed_reborrowable/self_param.rs new file mode 100644 index 0000000..d747784 --- /dev/null +++ b/bevy_lint/tests/ui/borrowed_reborrowable/self_param.rs @@ -0,0 +1,21 @@ +//! This tests the `borrowed_reborrowable` lint, specifically when triggered on a `self` parameter. +//! +//! The lint should _not_ match against a `self` parameter as it may be impossible to change the +//! method's signature (i.e. for trait methods). + +//@check-pass + +#![feature(register_tool)] +#![register_tool(bevy)] +#![deny(bevy::borrowed_reborrowable)] + +use bevy::prelude::*; + +#[allow(dead_code)] +trait MyTrait { + fn do_thing(&mut self); +} + +impl<'w, 's> MyTrait for Commands<'w, 's> { + fn do_thing(&mut self) {} +}