Skip to content
Merged
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
4 changes: 2 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion bevy_lint/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ harness = false
# Contains a series of useful utilities when writing lints. The version is chosen to work with the
# currently pinned nightly Rust version. When the Rust version changes, this too needs to be
# updated!
clippy_utils = "=0.1.88"
clippy_utils = "=0.1.89"

# Easy error propagation and contexts.
anyhow = "1.0.86"
Expand Down
4 changes: 2 additions & 2 deletions bevy_lint/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ see <https://linebender.org/blog/doc-include/>.
You can install the toolchain required for the latest release with:

```sh
rustup toolchain install nightly-2025-04-03 \
rustup toolchain install nightly-2025-05-14 \
--component rustc-dev \
--component llvm-tools-preview
```
Expand All @@ -44,7 +44,7 @@ If you are installing a different version of the linter, you may need to install
Once you have the toolchain installed, you can compile and install `bevy_lint` through `cargo`:

```sh
rustup run nightly-2025-04-03 cargo install \
rustup run nightly-2025-05-14 cargo install \
--git https://github.com/TheBevyFlock/bevy_cli.git \
--tag lint-v0.3.0 \
--locked \
Expand Down
4 changes: 2 additions & 2 deletions bevy_lint/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,15 @@ runs:
uses: dtolnay/rust-toolchain@master
with:
# This must be kept in sync with `rust-toolchain.toml`.
toolchain: nightly-2025-04-03
toolchain: nightly-2025-05-14
components: rustc-dev, llvm-tools-preview

- name: Install `bevy_lint`
shell: bash
run: |
# The toolchain must be kept in sync with `rust-toolchain.toml`. The `--branch main` should
# be swapped with `--tag lint-vX.Y.Z` for releases.
rustup run nightly-2025-04-03 cargo install \
rustup run nightly-2025-05-14 cargo install \
--git https://github.com/TheBevyFlock/bevy_cli.git \
--branch main \
--locked \
Expand Down
9 changes: 9 additions & 0 deletions bevy_lint/src/callback.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,15 @@ impl Callbacks for BevyLintCallback {
}
}
}));

// There shouldn't be any existing extra symbols, as we should be the only callback
// overriding them.
debug_assert!(config.extra_symbols.is_empty());

// Give the compiler a list of extra `Symbol`s to intern ahead of time. This helps us avoid
// calling `Symbol::intern()` while linting. See the `sym` module for a more detailed
// explanation.
config.extra_symbols = crate::sym::extra_symbols();
}
}

Expand Down
4 changes: 4 additions & 0 deletions bevy_lint/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
#![feature(rustc_private)]
// Allows chaining `if let` multiple times using `&&`.
#![feature(let_chains)]
// Used to access the index of repeating macro input in `declare_bevy_symbols!`.
#![feature(macro_metavar_expr)]
Comment on lines +19 to +20
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The RFC for this feature is really interesting! It lets us do this kind of thing:

#[macro_export]
macro_rules! vec {
    ( $( $x:expr ),* ) => {
        {
            // `${count(x)}` counts the amount of times `$x` is specified, and evaluates to that number!
            // We can't do this in stable Rust without proc-macros, which can be quite slow.
            let mut temp_vec = Vec::with_capacity(${count(x)});
            $(
                temp_vec.push($x);
            )*
            temp_vec
        }
    };
}

In the linter I use ${index()} in declare_bevy_symbols!. Check out that for more details.

// Warn on internal `rustc` lints that check for poor usage of internal compiler APIs. Note that
// you also need to pass `-Z unstable-options` to `rustc` for this to be enabled:
// `RUSTFLAGS="-Zunstable-options" cargo check`
Expand All @@ -40,12 +42,14 @@ extern crate rustc_lint_defs;
extern crate rustc_middle;
extern crate rustc_session;
extern crate rustc_span;
extern crate rustc_type_ir;

mod callback;
mod config;
mod lint;
pub mod lints;
mod paths;
mod sym;
mod utils;

pub use self::callback::BevyLintCallback;
25 changes: 1 addition & 24 deletions bevy_lint/src/lint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -148,13 +148,6 @@ macro_rules! declare_bevy_lint {
/// declare_bevy_lint_pass! {
/// // Declares which lints are emitted by this lint pass.
/// pub LintPassName => [LINT_NAME],
///
/// // The following are optional fields, and may be omitted.
/// //
/// // Declares fields of the lint pass that are set when `LintPassName::default()` is called.
/// @default = {
/// component: Symbol = Symbol::intern("component"),
/// },
/// }
/// ```
#[macro_export]
Expand All @@ -163,25 +156,9 @@ macro_rules! declare_bevy_lint_pass {
(
$(#[$attr:meta])*
$vis:vis $name:ident => [$($lint:expr),* $(,)?],

$(
@default = {
$($default_field:ident: $default_ty:ty = $default_value:expr),* $(,)?
},
)?
Comment on lines -167 to -171
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We no longer need @default, as its primary purpose was to intern symbols we used. We now have the sym module for that!

) => {
$(#[$attr])*
$vis struct $name {
$($($default_field: $default_ty),*)?
}

impl ::std::default::Default for $name {
fn default() -> Self {
Self {
$($($default_field: $default_value),*)?
}
}
}
Comment on lines -178 to -184
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We no longer implement Default for lint passes, as they are unit structs with no fields.

$vis struct $name;

::rustc_lint_defs::impl_lint_pass!($name => [$($lint),*]);
};
Expand Down
7 changes: 1 addition & 6 deletions bevy_lint/src/lints/cargo.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,14 @@
//! Lints that check over `Cargo.toml` instead of your code.

use cargo_metadata::MetadataCommand;
use clippy_utils::sym;
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{config::Input, utils::was_invoked_from_cargo};
use rustc_span::Symbol;

use super::nursery::duplicate_bevy_dependencies::DUPLICATE_BEVY_DEPENDENCIES;
use crate::declare_bevy_lint_pass;

declare_bevy_lint_pass! {
pub(crate) Cargo => [DUPLICATE_BEVY_DEPENDENCIES],
@default = {
bevy: Symbol = sym!(bevy),
},
}

impl LateLintPass<'_> for Cargo {
Expand All @@ -36,7 +31,7 @@ impl LateLintPass<'_> for Cargo {
.exec()
{
Ok(metadata) => {
super::nursery::duplicate_bevy_dependencies::check(cx, &metadata, self.bevy);
super::nursery::duplicate_bevy_dependencies::check(cx, &metadata);
}
Err(e) => {
cx.tcx
Expand Down
2 changes: 1 addition & 1 deletion bevy_lint/src/lints/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,5 +51,5 @@ pub(crate) fn register(store: &mut LintStore) {

// The Cargo lint pass is not associated with a single lint group, so we register it
// separately.
store.register_late_pass(|_| Box::new(cargo::Cargo::default()));
store.register_late_pass(|_| Box::new(cargo::Cargo));
}
21 changes: 9 additions & 12 deletions bevy_lint/src/lints/nursery/camera_modification_in_fixed_update.rs
Original file line number Diff line number Diff line change
Expand Up @@ -71,13 +71,12 @@
//! [physics in fixed timestep example](https://bevy.org/examples/movement/physics-in-fixed-timestep/).
//! ```

use clippy_utils::{diagnostics::span_lint_and_help, sym, ty::match_type};
use clippy_utils::diagnostics::span_lint_and_help;
use rustc_hir::{ExprKind, QPath, def::Res};
use rustc_lint::{LateContext, LateLintPass};
use rustc_middle::ty::{Adt, GenericArgKind, TyKind};
use rustc_span::Symbol;

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

declare_bevy_lint! {
pub(crate) CAMERA_MODIFICATION_IN_FIXED_UPDATE,
Expand All @@ -87,9 +86,6 @@ declare_bevy_lint! {

declare_bevy_lint_pass! {
pub(crate) CameraModificationInFixedUpdate => [CAMERA_MODIFICATION_IN_FIXED_UPDATE],
@default = {
add_systems: Symbol = sym!(add_systems),
},
}

impl<'tcx> LateLintPass<'tcx> for CameraModificationInFixedUpdate {
Expand All @@ -111,8 +107,8 @@ impl<'tcx> LateLintPass<'tcx> for CameraModificationInFixedUpdate {
let receiver_ty = cx.typeck_results().expr_ty(receiver).peel_refs();

// Match calls to `App::add_systems(schedule, systems)`
if !match_type(cx, receiver_ty, &crate::paths::APP)
|| method_path.ident.name != self.add_systems
if !crate::paths::APP.matches_ty(cx, receiver_ty)
|| method_path.ident.name != sym::add_systems
{
return;
}
Expand All @@ -124,7 +120,7 @@ impl<'tcx> LateLintPass<'tcx> for CameraModificationInFixedUpdate {
let schedule_ty = cx.typeck_results().expr_ty(schedule).peel_refs();

// Skip if the schedule is not `FixedUpdate`
if !match_type(cx, schedule_ty, &crate::paths::FIXED_UPDATE) {
if !crate::paths::FIXED_UPDATE.matches_ty(cx, schedule_ty) {
return;
}

Expand All @@ -149,7 +145,8 @@ impl<'tcx> LateLintPass<'tcx> for CameraModificationInFixedUpdate {

// Check if the parameter is a `Query`
let adt_ty = cx.tcx.type_of(adt_def_id.did()).skip_binder();
if !match_type(cx, adt_ty, &crate::paths::QUERY) {

if !crate::paths::QUERY.matches_ty(cx, adt_ty) {
continue;
}

Expand Down Expand Up @@ -192,7 +189,7 @@ impl<'tcx> LateLintPass<'tcx> for CameraModificationInFixedUpdate {
// Check for `With<Camera>` filter on a mutable query
for query_filter in query_filters {
// Check if the `With` `QueryFilter` was used.
if match_type(cx, query_filter, &crate::paths::WITH)
if crate::paths::WITH.matches_ty(cx, query_filter)
// Get the generic argument of the Filter
&& let TyKind::Adt(_, with_args) = query_filter.kind()
// There can only be exactly one argument
Expand All @@ -201,7 +198,7 @@ impl<'tcx> LateLintPass<'tcx> for CameraModificationInFixedUpdate {
&& let GenericArgKind::Type(filter_component_ty) =
filter_component_arg.unpack()
// Check if Filter is of type `Camera`
&& match_type(cx, filter_component_ty, &crate::paths::CAMERA)
&& crate::paths::CAMERA.matches_ty(cx, filter_component_ty)
// Emit lint if any `Camera` component is mutably borrowed
&& query_data_mutability.iter().any(|mutability|match mutability {
rustc_ast::Mutability::Not => false,
Expand Down
37 changes: 17 additions & 20 deletions bevy_lint/src/lints/nursery/duplicate_bevy_dependencies.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,15 +68,15 @@ use cargo_metadata::{
};
use clippy_utils::{
diagnostics::{span_lint, span_lint_and_then},
find_crates,
paths::find_crates,
};
use rustc_hir::def_id::LOCAL_CRATE;
use rustc_lint::LateContext;
use rustc_span::{BytePos, Pos, SourceFile, Span, Symbol, SyntaxContext};
use rustc_span::{BytePos, Pos, SourceFile, Span, SyntaxContext};
use serde::Deserialize;
use toml::Spanned;

use crate::declare_bevy_lint;
use crate::{declare_bevy_lint, sym};

declare_bevy_lint! {
pub(crate) DUPLICATE_BEVY_DEPENDENCIES,
Expand All @@ -99,9 +99,9 @@ fn toml_span(range: Range<usize>, file: &SourceFile) -> Span {
)
}

pub(crate) fn check(cx: &LateContext<'_>, metadata: &Metadata, bevy_symbol: Symbol) {
pub(crate) fn check(cx: &LateContext<'_>, metadata: &Metadata) {
// no reason to continue the check if there is only one instance of `bevy` required
if find_crates(cx.tcx, bevy_symbol).len() == 1 {
if find_crates(cx.tcx, sym::bevy).len() == 1 {
return;
}

Expand Down Expand Up @@ -205,21 +205,18 @@ fn minimal_lint(
return node.id.repr.split('#').nth(1).map(|version| vec![version]);
}
// Extract versions from external crates
if let Some((id, _)) = node.id.repr.split_once('@') {
if bevy_dependents
if let Some((id, _)) = node.id.repr.split_once('@')
&& bevy_dependents
.keys()
.any(|crate_name| id.ends_with(crate_name))
{
return Some(
node.dependencies
.iter()
.filter_map(|dep| dep.repr.split_once('@'))
.filter_map(|(name, version)| {
(name.contains("bevy")).then_some(version)
})
.collect(),
);
}
{
return Some(
node.dependencies
.iter()
.filter_map(|dep| dep.repr.split_once('@'))
.filter_map(|(name, version)| (name.contains("bevy")).then_some(version))
.collect(),
);
}

None
Expand All @@ -245,8 +242,8 @@ fn minimal_lint(
/// 1. A toml-string `<crate> = <version>`
/// 2. A toml-table `<crate> = { version = <version> , ... }`
///
/// Cargo supports specifying version ranges,
/// but [`Version::from_str`] can only parse exact versions and not ranges.
/// Cargo supports specifying version ranges, but [`parse_version()`] can only parse exact versions
/// and not ranges.
fn get_version_from_toml(table: &toml::Value) -> anyhow::Result<Version> {
match table {
toml::Value::String(version) => parse_version(version),
Expand Down
6 changes: 2 additions & 4 deletions bevy_lint/src/lints/nursery/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,9 @@ impl LintGroup for Nursery {

fn register_passes(store: &mut LintStore) {
store.register_late_pass(|_| {
Box::new(
camera_modification_in_fixed_update::CameraModificationInFixedUpdate::default(),
)
Box::new(camera_modification_in_fixed_update::CameraModificationInFixedUpdate)
});
// `duplicate_bevy_dependencies` is a Cargo lint, so it does not have its own pass.
store.register_late_pass(|_| Box::new(zst_query::ZstQuery::default()));
store.register_late_pass(|_| Box::new(zst_query::ZstQuery));
}
}
12 changes: 2 additions & 10 deletions bevy_lint/src/lints/nursery/zst_query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,7 @@
//! # assert_eq!(std::mem::size_of::<Player>(), 0);
//! ```

use clippy_utils::{
diagnostics::span_lint_and_help,
ty::{is_normalizable, match_type, ty_from_hir_ty},
};
use clippy_utils::{diagnostics::span_lint_and_help, ty::ty_from_hir_ty};
use rustc_abi::Size;
use rustc_hir::AmbigArg;
use rustc_lint::{LateContext, LateLintPass};
Expand Down Expand Up @@ -124,7 +121,7 @@ enum QueryKind {

impl QueryKind {
fn try_from_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<Self> {
if match_type(cx, ty, &crate::paths::QUERY) {
if crate::paths::QUERY.matches_ty(cx, ty) {
Some(Self::Query)
} else {
None
Expand Down Expand Up @@ -156,11 +153,6 @@ impl QueryKind {
/// - `Some(false)` if the type is most likely not a ZST
/// - `None` if we cannot determine the size (e.g., type is not normalizable)
fn is_zero_sized<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option<bool> {
// `cx.layout_of()` panics if the type is not normalizable.
if !is_normalizable(cx, cx.param_env, ty) {
return None;
}

// Note: we don't use `approx_ty_size` from `clippy_utils` here
// because it will return `0` as the default value if the type is not
// normalizable, which will put us at risk of emitting more false positives.
Expand Down
Loading
Loading