Skip to content
Open
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
213 changes: 213 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,213 @@
//! Checks for types from `std` that have an equivalent in `bevy_platform`.
//!
//! # Motivation
//!
//! `bevy_platform` helps with platform compatibility support by providing drop in replacements for
//! the following types:
//!
//! - Arc,
//! - Barrier,
//! - BarrierWaitResult,
//! - DefaultHasher,
//! - HashMap,
//! - HashSet,
//! - Instant,
//! - LazyLock,
//! - LockResult,
//! - Mutex,
//! - MutexGuard,
//! - Once,
//! - OnceLock,
//! - OnceState,
//! - PoisonError,
//! - RandomState,
//! - RwLock,
//! - RwLockReadGuard,
//! - RwLockWriteGuard,
//! - SyncCell,
//! - SyncUnsafeCell,
//! - TryLockError,
//! - TryLockResult
//!
//!
//! # Known Issues
//!
//! This lint does not currently support checking partial imported definitions. For example:
//!
//! ```
//! use std::time;
//!
//! let now = time::Instant::now();
//! ```
//!
//! Will not emit a lint.
//!
//! # Example
//!
//! ```
//! use std::time::Instant;
//! let now = Instant::now();
//! ```
//!
//! Use instead:
//!
//! ```
//! use bevy::platform::time::Instant;
//! let now = Instant::now();
//! ```

use clippy_utils::{diagnostics::span_lint_and_sugg, is_from_proc_macro, source::snippet};
use rustc_errors::Applicability;
use rustc_hir::{
HirId, Path, PathSegment,
def::{DefKind, Res},
};
use rustc_lint::{LateContext, LateLintPass};

use crate::{
declare_bevy_lint, declare_bevy_lint_pass, sym, utils::hir_parse::generic_args_snippet,
};

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_path(&mut self, cx: &LateContext<'tcx>, path: &Path<'tcx>, _: HirId) {
// Skip Resolutions that are not Structs for example: `use std::time`.
if let Res::Def(DefKind::Struct, def_id) = path.res
// Retrieve the first path segment, this could look like: `bevy`, `std`, `serde`.
&& let Some(first_segment) = get_first_segment(path)
// Skip if this span originates from an external macro.
// Or likely originates from a proc_macro, note this should be called after
// `in_external_macro`.
&& !path.span.in_external_macro(cx.tcx.sess.source_map())
&& !is_from_proc_macro(cx, &first_segment.ident)
// Skip if this Definition is not originating from `std`.
&& first_segment.ident.name == sym::std
// Get the def_id of the crate from first segment.
&& let Res::Def(DefKind::Mod,crate_def_id) = first_segment.res
// If the first segment is not the crate root, then this type was checked when
// importing.
&& crate_def_id.is_crate_root()
// Get potential generic arguments.
&& let Some(generic_args) = path.segments.last().map(|s| generic_args_snippet(cx, s))
{
// Get the Ty of this Definition.
let ty = cx.tcx.type_of(def_id).skip_binder();
// Check if an alternative exists in `bevy_platform`.
if let Some(bevy_platform_alternative) = BevyPlatformType::try_from_ty(cx, ty) {
span_lint_and_sugg(
cx,
BEVY_PLATFORM_ALTERNATIVE_EXISTS,
path.span,
BEVY_PLATFORM_ALTERNATIVE_EXISTS.desc,
format!(
"the type `{}` can be replaced with the `no_std` compatible type {}{}",
snippet(cx.tcx.sess, path.span, ""),
bevy_platform_alternative.full_path(),
generic_args,
),
format!("{}{}", bevy_platform_alternative.full_path(), generic_args),
Applicability::MachineApplicable,
);
}
}
}
}

/// Returns the first named segment of a [`Path`].
///
/// If this is a global path (such as `::std::fmt::Debug`), then the segment after [`kw::PathRoot`]
/// is returned.
fn get_first_segment<'tcx>(path: &Path<'tcx>) -> Option<&'tcx PathSegment<'tcx>> {
match path.segments {
// A global path will have PathRoot as the first segment. In this case, return the segment
// after.
[x, y, ..] if x.ident.name == rustc_span::symbol::kw::PathRoot => Some(y),
[x, ..] => Some(x),
_ => None,
}
}

/// 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! {
Arc("sync") => ARC,
Barrier("sync") => BARRIER,
BarrierWaitResult("sync") => BARRIERWAITRESULT,
DefaultHasher("hash") => DEFAULTHASHER,
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,
RandomState("hash") => RANDOMSTATE,
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));
}
}
32 changes: 32 additions & 0 deletions bevy_lint/src/paths.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,3 +86,35 @@ 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 ARC: PathLookup = type_path!(std::sync::Arc);
pub static BARRIER: PathLookup = type_path!(std::sync::Barrier);
pub static BARRIERWAITRESULT: PathLookup = type_path!(std::sync::BarrierWaitResult);
pub static DEFAULTHASHER: PathLookup = type_path!(std::hash::DefaultHasher);
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 RANDOMSTATE: PathLookup = type_path!(std::hash::RandomState);
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);
}
29 changes: 24 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::{
Arc, HashMap, HashSet, Instant, Mutex, MutexGuard, RwLock, RwLockReadGuard, RwLockWriteGuard,
SyncUnsafeCell, bevy_ecs, bundle, hash, 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,49 +117,62 @@ 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,
DefaultHasher,
Deferred,
deferred_world,
DeferredWorld,
entity_ref,
EntityCommands,
EntityMut,
event,
Event,
Events,
Exclusive,
FilteredEntityMut,
FixedUpdate,
init_resource,
insert_resource,
iter_current_update_messages,
LazyLock,
LockResult,
main_schedule,
Message,
Messages,
Mut,
MutUntyped,
NonSendMut,
Once,
OnceLock,
OnceState,
PartialReflect,
Plugin,
PoisonError,
PtrMut,
query,
Query,
RandomState,
Reflect,
related_methods,
RelatedSpawner,
Expand All @@ -169,9 +185,12 @@ declare_bevy_symbols! {
schedule,
set,
spawn,
system_param,
system,
system_param,
SystemSet,
time,
TryLockError,
TryLockResult,
Update,
With,
world,
Expand Down
Loading