Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
132 changes: 132 additions & 0 deletions bevy_lint/src/lints/style/bevy_platform_alternative_exists.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
use clippy_utils::{diagnostics::span_lint_and_sugg, source::snippet, ty::ty_from_hir_ty};
use rustc_errors::Applicability;
use rustc_hir::{QPath, TyKind};
use rustc_lint::{LateContext, LateLintPass};

use crate::{declare_bevy_lint, declare_bevy_lint_pass};

declare_bevy_lint! {
pub(crate) BEVY_PLATFORM_ALTERNATIVE_EXISTS,
super::Style,
"Used type from the `std` that has an existing alternative from `bevy_platform`",
}

declare_bevy_lint_pass! {
pub(crate) BevyPlatformAlternativeExists => [BEVY_PLATFORM_ALTERNATIVE_EXISTS],
}

impl<'tcx> LateLintPass<'tcx> for BevyPlatformAlternativeExists {
fn check_ty(
&mut self,
cx: &LateContext<'tcx>,
hir_ty: &'tcx rustc_hir::Ty<'tcx, rustc_hir::AmbigArg>,
) {
if hir_ty.span.in_external_macro(cx.tcx.sess.source_map()) {
return;
}

let as_unambig_ty = hir_ty.as_unambig_ty();

// lower the [`hir::Ty`] to a [`rustc_middle::ty::Ty`]
let ty = ty_from_hir_ty(cx, as_unambig_ty);

// Get the path to the type definition.
let TyKind::Path(QPath::Resolved(_, path)) = &as_unambig_ty.kind else {
return;
};

// if for the given `ty` an alternative from `bevy_platform` exists.
if let Some(bevy_platform_alternative) = BevyPlatformType::try_from_ty(cx, ty)
// Only emit a lint if the first segment of this path is `std` thus the type originates
// from the standart library. This prevents linting for `bevy::platform` types that are just a reexport of the `std`.
&& path.segments.first().is_some_and(|segment| segment.ident.name.as_str().starts_with("std"))
{
span_lint_and_sugg(
cx,
BEVY_PLATFORM_ALTERNATIVE_EXISTS,
hir_ty.span,
BEVY_PLATFORM_ALTERNATIVE_EXISTS.desc,
format!(
"the type `{}` can be replaced with the `no_std` compatible type {}",
snippet(cx.tcx.sess, hir_ty.span, ""),
bevy_platform_alternative.full_path()
),
bevy_platform_alternative.full_path().to_string(),
Applicability::MachineApplicable,
);
}
}
}

/// Creates an enum containing all the types form `bevy_platform` as variants.
///
/// # Example
///
/// ```ignore
/// declare_bevy_platform_types! {
/// // The variant name => [`PathLookup`] that matches the equivalent type in the std.
/// CustomType => CUSTOMTYPE,
/// // Optional the module path can be passed too, default is `bevy::platform::<variant_name>`.
/// // If an additional module path is added, it will result in: `bevy::platform::custom::thread::CustomType`.
/// CustomType("custom::thread") => BARRIER,
/// ```
macro_rules! declare_bevy_platform_types {
(
$(
$variant:ident $(($mod_path:expr))? => $path:ident
)
,+$(,)?
) => {
#[derive(Copy, Clone)]
pub enum BevyPlatformType {
$(
$variant,
)+
}

impl BevyPlatformType{
/// Try to create a [`BevyPlatformType`] from the given [`Ty`].
pub fn try_from_ty(cx: &LateContext<'_>, ty: rustc_middle::ty::Ty<'_>) -> Option<Self> {
use crate::paths::bevy_platform_types::*;
$(
if $path.matches_ty(cx, ty) {
Some(Self::$variant)
} else
)+
{
None
}
}

///Returns a string identifying this [`BevyPlatformType`]. This string is suitable for user output.
pub const fn full_path(&self) -> &'static str {
match self {
$(Self::$variant => concat!("bevy::platform", $("::", $mod_path,)? "::" , stringify!($variant)),)+
}
}
}
};
}

declare_bevy_platform_types! {
Barrier("sync") => BARRIER,
BarrierWaitResult("sync") => BARRIERWAITRESULT,
HashMap("collections") => HASHMAP,
HashSet("collections") => HASHSET,
Instant("time") => INSTANT,
LazyLock("sync") => LAZYLOCK,
LockResult("sync") => LOCKRESULT,
Mutex("sync") => MUTEX,
MutexGuard("sync") => MUTEXGUARD,
Once("sync")=> ONCE,
OnceLock("sync") => ONCELOCK,
OnceState("sync") => ONCESTATE,
PoisonError("sync") => POISONERROR,
RwLock("sync") => RWLOCK,
RwLockReadGuard("sync") => RWLOCKREADGUARD,
RwLockWriteGuard("sync") => RWLOCKWRITEGUARD,
SyncCell("sync") => SYNCCELL,
SyncUnsafeCell("cell") => SYNCUNSAFECELL,
TryLockError("sync") => TRYLOCKERROR,
TryLockResult("sync") => TRYLOCKRESULT,
}
9 changes: 8 additions & 1 deletion bevy_lint/src/lints/style/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,23 @@ use rustc_lint::{Level, Lint, LintStore};

use crate::lint::LintGroup;

pub mod bevy_platform_alternative_exists;
pub mod unconventional_naming;

pub(crate) struct Style;

impl LintGroup for Style {
const NAME: &str = "bevy::style";
const LEVEL: Level = Level::Warn;
const LINTS: &[&Lint] = &[unconventional_naming::UNCONVENTIONAL_NAMING];
const LINTS: &[&Lint] = &[
bevy_platform_alternative_exists::BEVY_PLATFORM_ALTERNATIVE_EXISTS,
unconventional_naming::UNCONVENTIONAL_NAMING,
];

fn register_passes(store: &mut LintStore) {
store.register_late_pass(|_| {
Box::new(bevy_platform_alternative_exists::BevyPlatformAlternativeExists)
});
store.register_late_pass(|_| Box::new(unconventional_naming::UnconventionalNaming));
}
}
29 changes: 29 additions & 0 deletions bevy_lint/src/paths.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,3 +86,32 @@ pub static UPDATE: PathLookup = type_path!(bevy_app::main_schedule::Update);
pub static WITH: PathLookup = type_path!(bevy_ecs::query::filter::With);
/// <https://github.com/bevyengine/bevy/blob/v0.17.0-rc.1/crates/bevy_ecs/src/world/mod.rs#L90>
pub static WORLD: PathLookup = type_path!(bevy_ecs::world::World);

// All the paths that represent the `bevy_platform` types.
// Keep the following list alphabetically sorted :) in neovim, use `:sort/\vpub static \w+/ ri`
pub mod bevy_platform_types {
use clippy_utils::paths::{PathLookup, PathNS};

use crate::sym;

pub static BARRIER: PathLookup = type_path!(std::sync::Barrier);
pub static BARRIERWAITRESULT: PathLookup = type_path!(std::sync::BarrierWaitResult);
pub static HASHMAP: PathLookup = type_path!(std::collections::HashMap);
pub static HASHSET: PathLookup = type_path!(std::collections::HashSet);
pub static INSTANT: PathLookup = type_path!(std::time::Instant);
pub static LAZYLOCK: PathLookup = type_path!(std::sync::LazyLock);
pub static LOCKRESULT: PathLookup = type_path!(std::sync::LockResult);
pub static MUTEX: PathLookup = type_path!(std::sync::Mutex);
pub static MUTEXGUARD: PathLookup = type_path!(std::sync::MutexGuard);
pub static ONCE: PathLookup = type_path!(std::sync::Once);
pub static ONCELOCK: PathLookup = type_path!(std::sync::OnceLock);
pub static ONCESTATE: PathLookup = type_path!(std::sync::OnceState);
pub static POISONERROR: PathLookup = type_path!(std::sync::PoisonError);
pub static RWLOCK: PathLookup = type_path!(std::sync::RwLock);
pub static RWLOCKREADGUARD: PathLookup = type_path!(std::sync::RwLockReadGuard);
pub static RWLOCKWRITEGUARD: PathLookup = type_path!(std::sync::RwLockWriteGuard);
pub static SYNCCELL: PathLookup = type_path!(std::sync::Exclusive);
pub static SYNCUNSAFECELL: PathLookup = type_path!(std::cell::SyncUnsafeCell);
pub static TRYLOCKERROR: PathLookup = type_path!(std::sync::TryLockError);
pub static TRYLOCKRESULT: PathLookup = type_path!(std::sync::TryLockResult);
}
27 changes: 22 additions & 5 deletions bevy_lint/src/sym.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,10 @@
use clippy_utils::sym::EXTRA_SYMBOLS as CLIPPY_SYMBOLS;
/// These are symbols that we use but are already interned by either the compiler or Clippy.
pub use clippy_utils::sym::filter;
pub use rustc_span::sym::{bevy_ecs, bundle, message, plugin, reflect};
pub use rustc_span::sym::{
HashMap, HashSet, Instant, Mutex, MutexGuard, RwLock, RwLockReadGuard, RwLockWriteGuard,
SyncUnsafeCell, bevy_ecs, bundle, message, plugin, reflect, std, sync,
};
use rustc_span::{Symbol, symbol::PREDEFINED_SYMBOLS_COUNT};

/// The starting offset used for the first Bevy-specific symbol.
Expand Down Expand Up @@ -114,38 +117,44 @@ macro_rules! declare_bevy_symbols {

// Before adding a new symbol here, check that it doesn't exist yet in `rustc_span::sym` or
// `clippy_utils::sym`. Having duplicate symbols will cause the compiler to ICE! Also please keep
// this list alphabetically sorted :)
// this list alphabetically sorted :) (use `:sort i` in nvim)
declare_bevy_symbols! {
add_systems,
app,
App,
Barrier,
BarrierWaitResult,
bevy,
bevy_app,
bevy_camera,
bevy_ptr,
bevy_reflect,
bevy,
Bundle,
camera,
Camera,
cell,
change_detection,
collections,
commands,
Commands,
component,
Component,
deferred_world,
Deferred,
deferred_world,
DeferredWorld,
entity_ref,
EntityCommands,
EntityMut,
event,
Event,
Events,
Exclusive,
FilteredEntityMut,
FixedUpdate,
init_resource,
insert_resource,
iter_current_update_messages,
LazyLock,
main_schedule,
Message,
Messages,
Expand All @@ -169,13 +178,21 @@ declare_bevy_symbols! {
schedule,
set,
spawn,
system_param,
system,
system_param,
SystemSet,
time,
Update,
With,
world,
World,
Once,
OnceLock,
OnceState,
LockResult,
PoisonError,
TryLockError,
TryLockResult,
}

/// Returns a list of strings that should be supplied to
Expand Down
28 changes: 28 additions & 0 deletions bevy_lint/tests/ui/bevy_platform_alternative_exists/main.fixed
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#![feature(register_tool)]
#![register_tool(bevy)]
#![deny(bevy::bevy_platform_alternative_exists)]

#[allow(dead_code)]
struct Player {
attack_cd: bevy::platform::time::Instant,
//~^ ERROR: Used type from the `std` that has an existing alternative from `bevy_platform`
//~| HELP: the type `std::time::Instant` can be replaced with the `no_std` compatible
// type bevy::platform::time::Instant
}

fn main() {
let mut hash_map = bevy::platform::collections::HashMap::new();
//~^ ERROR: Used type from the `std` that has an existing alternative from `bevy_platform`
//~| HELP: the type `std::collections::HashMap` can be replaced with the `no_std` compatible
// type bevy::platform::collections::HashMap
hash_map.insert("foo", "bar");

let _time = bevy::platform::time::Instant::now();
//~^ ERROR: Used type from the `std` that has an existing alternative from `bevy_platform`
//~| HELP: the type `std::time::Instant` can be replaced with the `no_std` compatible
// type bevy::platform::time::Instant

// This should be fine even tho it will result in a `std::time::Instant` after full path
// resolution.
let _bevy_time = bevy::platform::time::Instant::now();
}
30 changes: 30 additions & 0 deletions bevy_lint/tests/ui/bevy_platform_alternative_exists/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#![feature(register_tool)]
#![register_tool(bevy)]
#![deny(bevy::bevy_platform_alternative_exists)]

use std::time;

#[allow(dead_code)]
struct Player {
attack_cd: time::Instant,
//~^ ERROR: Used type from the `std` that has an existing alternative from `bevy_platform`
//~| HELP: the type `std::time::Instant` can be replaced with the `no_std` compatible
// type bevy::platform::time::Instant
}

fn main() {
let mut hash_map = std::collections::HashMap::new();
//~^ ERROR: Used type from the `std` that has an existing alternative from `bevy_platform`
//~| HELP: the type `std::collections::HashMap` can be replaced with the `no_std` compatible
// type bevy::platform::collections::HashMap
hash_map.insert("foo", "bar");

let _time = std::time::Instant::now();
//~^ ERROR: Used type from the `std` that has an existing alternative from `bevy_platform`
//~| HELP: the type `std::time::Instant` can be replaced with the `no_std` compatible
// type bevy::platform::time::Instant

// This should be fine even tho it will result in a `std::time::Instant` after full path
// resolution.
let _bevy_time = bevy::platform::time::Instant::now();
}
31 changes: 31 additions & 0 deletions bevy_lint/tests/ui/bevy_platform_alternative_exists/main.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
error: Used type from the `std` that has an existing alternative from `bevy_platform`
--> tests/ui/bevy_platform_alternative_exists/main.rs:16:24
|
16 | let mut hash_map = std::collections::HashMap::new();
| ^^^^^^^^^^^^^^^^^^^^^^^^^
|
note: the lint level is defined here
--> tests/ui/bevy_platform_alternative_exists/main.rs:3:9
|
3 | #![deny(bevy::bevy_platform_alternative_exists)]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
help: the type `std::collections::HashMap` can be replaced with the `no_std` compatible type bevy::platform::collections::HashMap
|
16 - let mut hash_map = std::collections::HashMap::new();
16 + let mut hash_map = bevy::platform::collections::HashMap::new();
|

error: Used type from the `std` that has an existing alternative from `bevy_platform`
--> tests/ui/bevy_platform_alternative_exists/main.rs:22:17
|
22 | let _time = std::time::Instant::now();
| ^^^^^^^^^^^^^^^^^^
|
help: the type `std::time::Instant` can be replaced with the `no_std` compatible type bevy::platform::time::Instant
|
22 - let _time = std::time::Instant::now();
22 + let _time = bevy::platform::time::Instant::now();
|

error: aborting due to 2 previous errors

Loading