diff --git a/Cargo.lock b/Cargo.lock index b90802eb..84193be7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1565,9 +1565,9 @@ checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" [[package]] name = "clippy_utils" -version = "0.1.88" +version = "0.1.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07a197dfde816f2732f1e1f43df2e4b93a8eead5f26d4deb3bc54b1fb6412ebf" +checksum = "7e61f20a7d249072978da440bc2481c1ee0b5cc0bebff9352f51c17e8add1a3f" dependencies = [ "arrayvec", "itertools 0.12.1", diff --git a/bevy_lint/Cargo.toml b/bevy_lint/Cargo.toml index e4824a31..78cd7c73 100644 --- a/bevy_lint/Cargo.toml +++ b/bevy_lint/Cargo.toml @@ -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" diff --git a/bevy_lint/README.md b/bevy_lint/README.md index 1242dc1e..18460f41 100644 --- a/bevy_lint/README.md +++ b/bevy_lint/README.md @@ -32,7 +32,7 @@ see . 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 ``` @@ -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 \ diff --git a/bevy_lint/action.yml b/bevy_lint/action.yml index 5a1f4043..335a44f7 100644 --- a/bevy_lint/action.yml +++ b/bevy_lint/action.yml @@ -16,7 +16,7 @@ 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` @@ -24,7 +24,7 @@ runs: 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 \ diff --git a/bevy_lint/src/callback.rs b/bevy_lint/src/callback.rs index aadc421a..f3591006 100644 --- a/bevy_lint/src/callback.rs +++ b/bevy_lint/src/callback.rs @@ -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(); } } diff --git a/bevy_lint/src/lib.rs b/bevy_lint/src/lib.rs index fd9cb0be..dfb2995a 100644 --- a/bevy_lint/src/lib.rs +++ b/bevy_lint/src/lib.rs @@ -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)] // 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` @@ -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; diff --git a/bevy_lint/src/lint.rs b/bevy_lint/src/lint.rs index 79bac4f7..b6f1b994 100644 --- a/bevy_lint/src/lint.rs +++ b/bevy_lint/src/lint.rs @@ -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] @@ -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),* $(,)? - }, - )? ) => { $(#[$attr])* - $vis struct $name { - $($($default_field: $default_ty),*)? - } - - impl ::std::default::Default for $name { - fn default() -> Self { - Self { - $($($default_field: $default_value),*)? - } - } - } + $vis struct $name; ::rustc_lint_defs::impl_lint_pass!($name => [$($lint),*]); }; diff --git a/bevy_lint/src/lints/cargo.rs b/bevy_lint/src/lints/cargo.rs index 7eb08941..11d0ab7c 100644 --- a/bevy_lint/src/lints/cargo.rs +++ b/bevy_lint/src/lints/cargo.rs @@ -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 { @@ -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 diff --git a/bevy_lint/src/lints/mod.rs b/bevy_lint/src/lints/mod.rs index c866ed07..1759b66a 100644 --- a/bevy_lint/src/lints/mod.rs +++ b/bevy_lint/src/lints/mod.rs @@ -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)); } diff --git a/bevy_lint/src/lints/nursery/camera_modification_in_fixed_update.rs b/bevy_lint/src/lints/nursery/camera_modification_in_fixed_update.rs index 9113cd97..98846270 100644 --- a/bevy_lint/src/lints/nursery/camera_modification_in_fixed_update.rs +++ b/bevy_lint/src/lints/nursery/camera_modification_in_fixed_update.rs @@ -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, @@ -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 { @@ -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; } @@ -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; } @@ -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; } @@ -192,7 +189,7 @@ impl<'tcx> LateLintPass<'tcx> for CameraModificationInFixedUpdate { // Check for `With` 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 @@ -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, diff --git a/bevy_lint/src/lints/nursery/duplicate_bevy_dependencies.rs b/bevy_lint/src/lints/nursery/duplicate_bevy_dependencies.rs index 5a86a9eb..ed13d6b5 100644 --- a/bevy_lint/src/lints/nursery/duplicate_bevy_dependencies.rs +++ b/bevy_lint/src/lints/nursery/duplicate_bevy_dependencies.rs @@ -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, @@ -99,9 +99,9 @@ fn toml_span(range: Range, 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; } @@ -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 @@ -245,8 +242,8 @@ fn minimal_lint( /// 1. A toml-string ` = ` /// 2. A toml-table ` = { 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 { match table { toml::Value::String(version) => parse_version(version), diff --git a/bevy_lint/src/lints/nursery/mod.rs b/bevy_lint/src/lints/nursery/mod.rs index dd2a900f..5cacd8ac 100644 --- a/bevy_lint/src/lints/nursery/mod.rs +++ b/bevy_lint/src/lints/nursery/mod.rs @@ -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)); } } diff --git a/bevy_lint/src/lints/nursery/zst_query.rs b/bevy_lint/src/lints/nursery/zst_query.rs index 22a3259e..21aa360b 100644 --- a/bevy_lint/src/lints/nursery/zst_query.rs +++ b/bevy_lint/src/lints/nursery/zst_query.rs @@ -50,10 +50,7 @@ //! # assert_eq!(std::mem::size_of::(), 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}; @@ -124,7 +121,7 @@ enum QueryKind { impl QueryKind { fn try_from_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option { - if match_type(cx, ty, &crate::paths::QUERY) { + if crate::paths::QUERY.matches_ty(cx, ty) { Some(Self::Query) } else { None @@ -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 { - // `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. diff --git a/bevy_lint/src/lints/pedantic/borrowed_reborrowable.rs b/bevy_lint/src/lints/pedantic/borrowed_reborrowable.rs index fc9cdd33..5230a03f 100644 --- a/bevy_lint/src/lints/pedantic/borrowed_reborrowable.rs +++ b/bevy_lint/src/lints/pedantic/borrowed_reborrowable.rs @@ -100,14 +100,15 @@ use std::ops::ControlFlow; use clippy_utils::{ diagnostics::span_lint_and_sugg, + paths::PathLookup, source::{snippet, snippet_opt}, - ty::match_type, }; use rustc_errors::Applicability; -use rustc_hir::{Body, FnDecl, MutTy, Mutability, intravisit::FnKind}; +use rustc_hir::{Body, FnDecl, MutTy, Mutability, PatKind, intravisit::FnKind}; use rustc_lint::{LateContext, LateLintPass}; -use rustc_middle::ty::{Interner, Ty, TyKind, TypeVisitable, TypeVisitor}; -use rustc_span::{Span, def_id::LocalDefId, symbol::kw}; +use rustc_middle::ty::{Ty, TyKind, TypeVisitable, TypeVisitor}; +use rustc_span::{Span, def_id::LocalDefId, kw}; +use rustc_type_ir::Interner; use crate::{declare_bevy_lint, declare_bevy_lint_pass}; @@ -145,16 +146,9 @@ impl<'tcx> LateLintPass<'tcx> for BorrowedReborrowable { // A list of argument types, used in the actual lint check. let arg_types = fn_sig.inputs().skip_binder(); - // A list of argument names, used to check and skip `&mut self`. - let arg_names = cx.tcx.fn_arg_names(def_id); // A list of argument parameters, used to find the span of arguments. let arg_params = body.params; - debug_assert_eq!( - arg_types.len(), - arg_names.len(), - "there must be the same number of argument types and names" - ); debug_assert_eq!( arg_types.len(), arg_params.len(), @@ -167,14 +161,13 @@ impl<'tcx> LateLintPass<'tcx> for BorrowedReborrowable { continue; }; - // 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_names[arg_index].is_some_and(|ident| ident.name == kw::SelfLower) { + // If the argument is named `self`, skip it. Without this check the lint would be + // emitted for `&mut self` if `self` was reborrowable, which isn't wanted! That would + // just be annoying for engine developers trying to add useful methods to reborrowable + // types. + if let PatKind::Binding(_, _, ident, _) = body.params[arg_index].pat.kind + && ident.name == kw::SelfLower + { continue; } @@ -255,7 +248,7 @@ impl Reborrowable { fn try_from_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option { use crate::paths::*; - const PATH_MAP: &[(&[&str], Reborrowable)] = &[ + static PATH_MAP: &[(&PathLookup, Reborrowable)] = &[ (&COMMANDS, Reborrowable::Commands), (&DEFERRED, Reborrowable::Deferred), (&DEFERRED_WORLD, Reborrowable::DeferredWorld), @@ -271,7 +264,7 @@ impl Reborrowable { ]; for &(path, reborrowable) in PATH_MAP { - if match_type(cx, ty, path) { + if path.matches_ty(cx, ty) { return Some(reborrowable); } } diff --git a/bevy_lint/src/lints/pedantic/main_return_without_appexit.rs b/bevy_lint/src/lints/pedantic/main_return_without_appexit.rs index 38e57b83..c4530678 100644 --- a/bevy_lint/src/lints/pedantic/main_return_without_appexit.rs +++ b/bevy_lint/src/lints/pedantic/main_return_without_appexit.rs @@ -36,15 +36,15 @@ use std::ops::ControlFlow; use clippy_utils::{ - diagnostics::span_lint_hir_and_then, is_entrypoint_fn, is_expr_used_or_unified, sym, - ty::match_type, visitors::for_each_expr, + diagnostics::span_lint_hir_and_then, is_entrypoint_fn, is_expr_used_or_unified, + visitors::for_each_expr, }; use rustc_errors::Applicability; use rustc_hir::{Body, FnDecl, FnRetTy, Ty, TyKind, def_id::LocalDefId, intravisit::FnKind}; use rustc_lint::{LateContext, LateLintPass}; -use rustc_span::{Span, Symbol}; +use rustc_span::Span; -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) MAIN_RETURN_WITHOUT_APPEXIT, @@ -54,9 +54,6 @@ declare_bevy_lint! { declare_bevy_lint_pass! { pub(crate) MainReturnWithoutAppExit => [MAIN_RETURN_WITHOUT_APPEXIT], - @default = { - run: Symbol = sym!(run), - }, } impl<'tcx> LateLintPass<'tcx> for MainReturnWithoutAppExit { @@ -91,7 +88,7 @@ impl<'tcx> LateLintPass<'tcx> for MainReturnWithoutAppExit { receiver, .. }) = MethodCall::try_from(cx, expr) - && method_path.ident.name == self.run + && method_path.ident.name == sym::run && !expr.span.in_external_macro(cx.tcx.sess.source_map()) { // Get the type of `src` for `src.run()`. We peel away all references because @@ -99,7 +96,7 @@ impl<'tcx> LateLintPass<'tcx> for MainReturnWithoutAppExit { let ty = cx.typeck_results().expr_ty(receiver).peel_refs(); // If `src` is a Bevy `App` and the `AppExit` is unused, emit the lint. - if match_type(cx, ty, &crate::paths::APP) + if crate::paths::APP.matches_ty(cx, ty) && !is_expr_used_or_unified(cx.tcx, expr) { span_lint_hir_and_then( diff --git a/bevy_lint/src/lints/pedantic/mod.rs b/bevy_lint/src/lints/pedantic/mod.rs index 53cb34cb..c67a27f8 100644 --- a/bevy_lint/src/lints/pedantic/mod.rs +++ b/bevy_lint/src/lints/pedantic/mod.rs @@ -24,11 +24,9 @@ impl LintGroup for Pedantic { ]; fn register_passes(store: &mut LintStore) { + store.register_late_pass(|_| Box::new(borrowed_reborrowable::BorrowedReborrowable)); store.register_late_pass(|_| { - Box::new(borrowed_reborrowable::BorrowedReborrowable::default()) - }); - store.register_late_pass(|_| { - Box::new(main_return_without_appexit::MainReturnWithoutAppExit::default()) + Box::new(main_return_without_appexit::MainReturnWithoutAppExit) }); } } diff --git a/bevy_lint/src/lints/restriction/missing_reflect.rs b/bevy_lint/src/lints/restriction/missing_reflect.rs index 9a9ca394..77ba348a 100644 --- a/bevy_lint/src/lints/restriction/missing_reflect.rs +++ b/bevy_lint/src/lints/restriction/missing_reflect.rs @@ -55,19 +55,15 @@ //! Code](../../index.html#toggling-lints-in-code). use clippy_utils::{ - def_path_res, diagnostics::span_lint_hir_and_then, - get_trait_def_id, + paths::PathLookup, sugg::DiagExt, ty::{implements_trait, ty_from_hir_ty}, }; use rustc_errors::Applicability; -use rustc_hir::{ - HirId, Item, ItemKind, Node, OwnerId, QPath, TyKind, - def::{DefKind, Res}, -}; +use rustc_hir::{HirId, Item, ItemKind, Node, OwnerId, QPath, TyKind, def::DefKind}; use rustc_lint::{LateContext, LateLintPass}; -use rustc_middle::{span_bug, ty::TyCtxt}; +use rustc_middle::span_bug; use rustc_span::Span; use crate::{declare_bevy_lint, declare_bevy_lint_pass}; @@ -88,36 +84,27 @@ impl<'tcx> LateLintPass<'tcx> for MissingReflect { fn check_crate(&mut self, cx: &LateContext<'tcx>) { // Finds all types that implement `Reflect` in this crate. let reflected: Vec = - TraitType::from_local_crate(cx.tcx, &crate::paths::REFLECT).collect(); + TraitType::from_local_crate(cx, &crate::paths::REFLECT).collect(); // Finds all non-`Reflect` types that implement `Event` in this crate. - let events: Vec = TraitType::from_local_crate(cx.tcx, &crate::paths::EVENT) + let events: Vec = TraitType::from_local_crate(cx, &crate::paths::EVENT) .filter(|trait_type| !reflected.contains(trait_type)) .collect(); // Finds all non-`Reflect` types that implement `Component` and *not* `Event` in this // crate. Because events are also components, we need to deduplicate the two to avoid // emitting multiple diagnostics for the same type. - let components: Vec = - TraitType::from_local_crate(cx.tcx, &crate::paths::COMPONENT) - .filter(|trait_type| { - !(reflected.contains(trait_type) || events.contains(trait_type)) - }) - .collect(); + let components: Vec = TraitType::from_local_crate(cx, &crate::paths::COMPONENT) + .filter(|trait_type| !(reflected.contains(trait_type) || events.contains(trait_type))) + .collect(); // Finds all non-`Reflect` types that implement `Resource` in this crate. - let resources: Vec = - TraitType::from_local_crate(cx.tcx, &crate::paths::RESOURCE) - .filter(|trait_type| !reflected.contains(trait_type)) - .collect(); + let resources: Vec = TraitType::from_local_crate(cx, &crate::paths::RESOURCE) + .filter(|trait_type| !reflected.contains(trait_type)) + .collect(); + + let reflect_trait_def_ids = crate::paths::PARTIAL_REFLECT.get(cx); - // This is an expensive function that is purposefully called outside of the `for` loop. Note - // that this will only return `None` if `PartialReflect` does not exist (e.g. `bevy_reflect` - // is not available.) - let Some(reflect_trait_def_id) = get_trait_def_id(cx.tcx, &crate::paths::PARTIAL_REFLECT) - else { - return; - }; // Emit diagnostics for each of these types. for (checked_trait, trait_name, message_phrase) in [ (events, "Event", "an event"), @@ -174,7 +161,10 @@ impl<'tcx> LateLintPass<'tcx> for MissingReflect { // Check if the field's type implements the `PartialReflect` trait. If it does // not, change the `Applicability` level to `MaybeIncorrect` because `Reflect` // cannot be automatically derived. - if !implements_trait(cx, ty, reflect_trait_def_id, &[]) { + if !reflect_trait_def_ids + .iter() + .any(|&trait_id| implements_trait(cx, ty, trait_id, &[])) + { applicability = Applicability::MaybeIncorrect; break; } @@ -221,26 +211,24 @@ struct TraitType { } impl TraitType { - fn from_local_crate<'tcx>( - tcx: TyCtxt<'tcx>, - trait_path: &[&str], - ) -> impl Iterator + use<'tcx> { + fn from_local_crate<'tcx, 'a>( + cx: &'a LateContext<'tcx>, + trait_path: &'a PathLookup, + ) -> impl Iterator + use<'tcx, 'a> { // Find the `DefId` of the trait. There may be multiple if there are multiple versions of // the same crate. - let trait_def_ids = def_path_res(tcx, trait_path) - .into_iter() - .filter_map(|res| match res { - Res::Def(DefKind::Trait, def_id) => Some(def_id), - _ => None, - }); + let trait_def_ids = trait_path + .get(cx) + .iter() + .filter(|&def_id| cx.tcx.def_kind(def_id) == DefKind::Trait); // Find a map of all trait `impl` items within the current crate. The key is the `DefId` of // the trait, and the value is a `Vec` for all `impl` items. - let all_trait_impls = tcx.all_local_trait_impls(()); + let all_trait_impls = cx.tcx.all_local_trait_impls(()); // Find all `impl` items for the specific trait. let trait_impls = trait_def_ids - .filter_map(|def_id| all_trait_impls.get(&def_id)) + .filter_map(|def_id| all_trait_impls.get(def_id)) .flatten() .copied(); @@ -248,7 +236,7 @@ impl TraitType { // we use `filter_map()` to skip errors. trait_impls.filter_map(move |local_def_id| { // Retrieve the node of the `impl` item from its `DefId`. - let node = tcx.hir_node_by_def_id(local_def_id); + let node = cx.tcx.hir_node_by_def_id(local_def_id); // Verify that it's an `impl` item and not something else. let Node::Item(Item { @@ -285,7 +273,7 @@ impl TraitType { // Find the span where the type was declared. This is guaranteed to be an item, so we // can safely call `expect_item()` without it panicking. - let item_span = tcx.hir_node(hir_id).expect_item().span; + let item_span = cx.tcx.hir_node(hir_id).expect_item().span; Some(TraitType { hir_id, diff --git a/bevy_lint/src/lints/restriction/mod.rs b/bevy_lint/src/lints/restriction/mod.rs index 4e7b4846..bbba33b7 100644 --- a/bevy_lint/src/lints/restriction/mod.rs +++ b/bevy_lint/src/lints/restriction/mod.rs @@ -24,7 +24,7 @@ impl LintGroup for Restriction { ]; fn register_passes(store: &mut LintStore) { - store.register_late_pass(|_| Box::new(missing_reflect::MissingReflect::default())); - store.register_late_pass(|_| Box::new(panicking_methods::PanickingMethods::default())); + store.register_late_pass(|_| Box::new(missing_reflect::MissingReflect)); + store.register_late_pass(|_| Box::new(panicking_methods::PanickingMethods)); } } diff --git a/bevy_lint/src/lints/restriction/panicking_methods.rs b/bevy_lint/src/lints/restriction/panicking_methods.rs index 8b74ae0d..60199b47 100644 --- a/bevy_lint/src/lints/restriction/panicking_methods.rs +++ b/bevy_lint/src/lints/restriction/panicking_methods.rs @@ -51,7 +51,6 @@ use clippy_utils::{ diagnostics::span_lint_and_help, source::{snippet, snippet_opt}, - ty::match_type, }; use rustc_hir::Expr; use rustc_lint::{LateContext, LateLintPass}; @@ -216,9 +215,9 @@ enum PanickingType { impl PanickingType { /// Returns the corresponding variant for the given [`Ty`], if it is supported by this lint. fn try_from_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> Option { - if match_type(cx, ty, &crate::paths::QUERY) { + if crate::paths::QUERY.matches_ty(cx, ty) { Some(Self::Query) - } else if match_type(cx, ty, &crate::paths::WORLD) { + } else if crate::paths::WORLD.matches_ty(cx, ty) { Some(Self::World) } else { None diff --git a/bevy_lint/src/lints/style/mod.rs b/bevy_lint/src/lints/style/mod.rs index 38042922..62728609 100644 --- a/bevy_lint/src/lints/style/mod.rs +++ b/bevy_lint/src/lints/style/mod.rs @@ -18,9 +18,7 @@ impl LintGroup for Style { const LINTS: &[&Lint] = &[unconventional_naming::UNCONVENTIONAL_NAMING]; fn register_passes(store: &mut LintStore) { - store.register_late_pass(|_| { - Box::new(unconventional_naming::UnconventionalNaming::default()) - }); + store.register_late_pass(|_| Box::new(unconventional_naming::UnconventionalNaming)); } fn register_lints(store: &mut LintStore) { diff --git a/bevy_lint/src/lints/suspicious/insert_event_resource.rs b/bevy_lint/src/lints/suspicious/insert_event_resource.rs index f39bd3da..8750f161 100644 --- a/bevy_lint/src/lints/suspicious/insert_event_resource.rs +++ b/bevy_lint/src/lints/suspicious/insert_event_resource.rs @@ -44,17 +44,15 @@ use std::borrow::Cow; use clippy_utils::{ diagnostics::span_lint_and_sugg, source::{snippet, snippet_with_applicability}, - sym, - ty::{match_type, ty_from_hir_ty}, + ty::ty_from_hir_ty, }; use rustc_errors::Applicability; use rustc_hir::{Expr, GenericArg, GenericArgs, Path, PathSegment, QPath}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::{Ty, TyKind}; -use rustc_span::Symbol; use crate::{ - declare_bevy_lint, declare_bevy_lint_pass, + declare_bevy_lint, declare_bevy_lint_pass, sym, utils::hir_parse::{MethodCall, generic_args_snippet, span_args}, }; @@ -66,10 +64,6 @@ declare_bevy_lint! { declare_bevy_lint_pass! { pub(crate) InsertEventResource => [INSERT_EVENT_RESOURCE], - @default = { - insert_resource: Symbol = sym!(insert_resource), - init_resource: Symbol = sym!(init_resource), - }, } const HELP_MESSAGE: &str = "inserting an `Events` resource does not fully setup that event"; @@ -91,17 +85,17 @@ impl<'tcx> LateLintPass<'tcx> for InsertEventResource { .peel_refs(); // If `src` is not a Bevy `App`, exit. - if !match_type(cx, src_ty, &crate::paths::APP) { + if !crate::paths::APP.matches_ty(cx, src_ty) { return; } // If the method is `App::insert_resource()` or `App::init_resource()`, check it with // its corresponding function. match method_call.method_path.ident.name { - symbol if symbol == self.insert_resource => { + symbol if symbol == sym::insert_resource => { check_insert_resource(cx, &method_call); } - symbol if symbol == self.init_resource => { + symbol if symbol == sym::init_resource => { check_init_resource(cx, &method_call); } _ => {} @@ -121,7 +115,7 @@ fn check_insert_resource(cx: &LateContext<'_>, method_call: &MethodCall) { let ty = cx.typeck_results().expr_ty(arg); // If `arg` is `Events`, emit the lint. - if match_type(cx, ty, &crate::paths::EVENTS) { + if crate::paths::EVENTS.matches_ty(cx, ty) { let mut applicability = Applicability::MachineApplicable; let event_ty_snippet = extract_ty_event_snippet(ty, &mut applicability); @@ -199,7 +193,7 @@ fn check_init_resource<'tcx>(cx: &LateContext<'tcx>, method_call: &MethodCall<'t let resource_ty = ty_from_hir_ty(cx, resource_hir_ty.as_unambig_ty()); // If the resource type is `Events`, emit the lint. - if match_type(cx, resource_ty, &crate::paths::EVENTS) { + if crate::paths::EVENTS.matches_ty(cx, resource_ty) { let mut applicability = Applicability::MachineApplicable; let event_ty_snippet = diff --git a/bevy_lint/src/lints/suspicious/insert_unit_bundle.rs b/bevy_lint/src/lints/suspicious/insert_unit_bundle.rs index ce634b9d..f91d14eb 100644 --- a/bevy_lint/src/lints/suspicious/insert_unit_bundle.rs +++ b/bevy_lint/src/lints/suspicious/insert_unit_bundle.rs @@ -50,14 +50,13 @@ //! # bevy::ecs::system::assert_is_system(spawn); //! ``` -use clippy_utils::{diagnostics::span_lint_hir_and_then, sym, ty::match_type}; +use clippy_utils::diagnostics::span_lint_hir_and_then; use rustc_errors::Applicability; use rustc_hir::{Expr, ExprKind}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::{Ty, 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) INSERT_UNIT_BUNDLE, @@ -67,9 +66,6 @@ declare_bevy_lint! { declare_bevy_lint_pass! { pub(crate) InsertUnitBundle => [INSERT_UNIT_BUNDLE], - @default = { - spawn: Symbol = sym!(spawn), - }, } impl<'tcx> LateLintPass<'tcx> for InsertUnitBundle { @@ -91,8 +87,8 @@ impl<'tcx> LateLintPass<'tcx> for InsertUnitBundle { // If the method call was not to `Commands::spawn()` or originates from an external macro, // we skip it. if !(span.in_external_macro(cx.tcx.sess.source_map()) - || match_type(cx, src_ty, &crate::paths::COMMANDS) - && method_path.ident.name == self.spawn) + || crate::paths::COMMANDS.matches_ty(cx, src_ty) + && method_path.ident.name == sym::spawn) { return; } diff --git a/bevy_lint/src/lints/suspicious/iter_current_update_events.rs b/bevy_lint/src/lints/suspicious/iter_current_update_events.rs index 9bab77de..262d7435 100644 --- a/bevy_lint/src/lints/suspicious/iter_current_update_events.rs +++ b/bevy_lint/src/lints/suspicious/iter_current_update_events.rs @@ -38,12 +38,11 @@ //! } //! ``` -use clippy_utils::{diagnostics::span_lint_and_help, ty::match_type}; +use clippy_utils::diagnostics::span_lint_and_help; use rustc_hir::Expr; use rustc_lint::{LateContext, LateLintPass}; -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) ITER_CURRENT_UPDATE_EVENTS, @@ -53,10 +52,6 @@ declare_bevy_lint! { declare_bevy_lint_pass! { pub(crate) IterCurrentUpdateEvents => [ITER_CURRENT_UPDATE_EVENTS], - - @default = { - iter_current_update_events: Symbol = Symbol::intern("iter_current_update_events"), - }, } impl<'tcx> LateLintPass<'tcx> for IterCurrentUpdateEvents { @@ -87,11 +82,11 @@ impl<'tcx> LateLintPass<'tcx> for IterCurrentUpdateEvents { .expr_ty_adjusted(method_call.receiver) .peel_refs(); - if !match_type(cx, src_ty, &crate::paths::EVENTS) { + if !crate::paths::EVENTS.matches_ty(cx, src_ty) { return; } - if method_call.method_path.ident.name == self.iter_current_update_events { + if method_call.method_path.ident.name == sym::iter_current_update_events { span_lint_and_help( cx, ITER_CURRENT_UPDATE_EVENTS, diff --git a/bevy_lint/src/lints/suspicious/mod.rs b/bevy_lint/src/lints/suspicious/mod.rs index 1c344964..9b843351 100644 --- a/bevy_lint/src/lints/suspicious/mod.rs +++ b/bevy_lint/src/lints/suspicious/mod.rs @@ -25,12 +25,8 @@ impl LintGroup for Suspicious { ]; fn register_passes(store: &mut LintStore) { - store.register_late_pass(|_| { - Box::new(insert_event_resource::InsertEventResource::default()) - }); - store.register_late_pass(|_| Box::new(insert_unit_bundle::InsertUnitBundle::default())); - store.register_late_pass(|_| { - Box::new(iter_current_update_events::IterCurrentUpdateEvents::default()) - }); + store.register_late_pass(|_| Box::new(insert_event_resource::InsertEventResource)); + store.register_late_pass(|_| Box::new(insert_unit_bundle::InsertUnitBundle)); + store.register_late_pass(|_| Box::new(iter_current_update_events::IterCurrentUpdateEvents)); } } diff --git a/bevy_lint/src/paths.rs b/bevy_lint/src/paths.rs index 114bb2a7..bc9eada4 100644 --- a/bevy_lint/src/paths.rs +++ b/bevy_lint/src/paths.rs @@ -1,58 +1,74 @@ -//! A collection of hardcoded types and functions that the linter uses. +//! A collection of hardcoded type and function paths that the linter uses. //! //! Since Bevy is a 3rd-party crate, we cannot easily add diagnostic items to it. In lieu of this, //! we hardcode the paths to the items we need here, for easy referencing. -//! -//! Also see: [`match_type()`](clippy_utils::ty::match_type), -//! [`match_def_path()`](clippy_utils::match_def_path). -/// -pub const APP: [&str; 3] = ["bevy_app", "app", "App"]; +use clippy_utils::paths::{PathLookup, PathNS}; + +use crate::sym; + +/// Returns a new [`PathLookup`] in the [type namespace](PathNS::Type) for a given path. +/// +/// `type_path!()` takes a `::`-separated list of identifiers. Each identifier should correspond to +/// a [`Symbol`](rustc_span::Symbol) in [`crate::sym`]. For example, +/// `type_path!(bevy_app::app::App)` creates a [`PathLookup`] in the [`PathNS::Type`] namespace +/// with the path `[sym::bevy_app, sym::app, sym::App]`. +macro_rules! type_path { + ($first:ident $(:: $remaining:ident)*) => { + PathLookup::new(PathNS::Type, &[sym::$first, $(sym::$remaining),*]) + }; +} + +// Keep the following list alphabetically sorted :) + +/// +pub static APP: PathLookup = type_path!(bevy_app::app::App); /// -pub const CAMERA: [&str; 4] = ["bevy_render", "camera", "camera", "Camera"]; -/// -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"]; -/// -pub const EVENT: [&str; 4] = ["bevy_ecs", "event", "base", "Event"]; -/// -pub const EVENTS: [&str; 4] = ["bevy_ecs", "event", "collections", "Events"]; -/// -pub const FILTERED_ENTITY_MUT: [&str; 4] = ["bevy_ecs", "world", "entity_ref", "FilteredEntityMut"]; +pub static CAMERA: PathLookup = type_path!(bevy_render::camera::camera::Camera); +/// +pub static COMMANDS: PathLookup = type_path!(bevy_ecs::system::commands::Commands); +/// +pub static COMPONENT: PathLookup = type_path!(bevy_ecs::component::Component); +/// +pub static DEFERRED_WORLD: PathLookup = type_path!(bevy_ecs::world::deferred_world::DeferredWorld); +/// +pub static DEFERRED: PathLookup = type_path!(bevy_ecs::system::system_param::Deferred); +/// +pub static ENTITY_COMMANDS: PathLookup = type_path!(bevy_ecs::system::commands::EntityCommands); +/// +pub static ENTITY_MUT: PathLookup = type_path!(bevy_ecs::world::entity_ref::EntityMut); +/// +pub static EVENT: PathLookup = type_path!(bevy_ecs::event::base::Event); +/// +pub static EVENTS: PathLookup = type_path!(bevy_ecs::event::collections::Events); +/// +pub static FILTERED_ENTITY_MUT: PathLookup = + type_path!(bevy_ecs::world::entity_ref::FilteredEntityMut); /// -pub const FIXED_UPDATE: [&str; 3] = ["bevy_app", "main_schedule", "FixedUpdate"]; -/// -pub const WITH: [&str; 4] = ["bevy_ecs", "query", "filter", "With"]; -/// -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 static FIXED_UPDATE: PathLookup = type_path!(bevy_app::main_schedule::FixedUpdate); +/// +pub static MUT_UNTYPED: PathLookup = type_path!(bevy_ecs::change_detection::MutUntyped); +/// +pub static MUT: PathLookup = type_path!(bevy_ecs::change_detection::Mut); +/// +pub static NON_SEND_MUT: PathLookup = type_path!(bevy_ecs::change_detection::NonSendMut); /// -pub const PARTIAL_REFLECT: [&str; 3] = ["bevy_reflect", "reflect", "PartialReflect"]; -/// -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 REFLECT: [&str; 3] = ["bevy_reflect", "reflect", "Reflect"]; -/// -pub const RES_MUT: [&str; 3] = ["bevy_ecs", "change_detection", "ResMut"]; -/// -pub const RESOURCE: [&str; 3] = ["bevy_ecs", "resource", "Resource"]; -/// -pub const SYSTEM_SET: [&str; 4] = ["bevy_ecs", "schedule", "set", "SystemSet"]; -/// -pub const WORLD: [&str; 3] = ["bevy_ecs", "world", "World"]; +pub static PARTIAL_REFLECT: PathLookup = type_path!(bevy_reflect::reflect::PartialReflect); +/// +pub static PLUGIN: PathLookup = type_path!(bevy_app::plugin::Plugin); +/// +pub static PTR_MUT: PathLookup = type_path!(bevy_ptr::PtrMut); +/// +pub static QUERY: PathLookup = type_path!(bevy_ecs::system::query::Query); +/// +pub static REFLECT: PathLookup = type_path!(bevy_reflect::reflect::Reflect); +/// +pub static RES_MUT: PathLookup = type_path!(bevy_ecs::change_detection::ResMut); +/// +pub static RESOURCE: PathLookup = type_path!(bevy_ecs::resource::Resource); +/// +pub static SYSTEM_SET: PathLookup = type_path!(bevy_ecs::schedule::set::SystemSet); +/// +pub static WITH: PathLookup = type_path!(bevy_ecs::query::filter::With); +/// +pub static WORLD: PathLookup = type_path!(bevy_ecs::world::World); diff --git a/bevy_lint/src/sym.rs b/bevy_lint/src/sym.rs new file mode 100644 index 00000000..4503ee39 --- /dev/null +++ b/bevy_lint/src/sym.rs @@ -0,0 +1,186 @@ +//! Pre-interned [`Symbol`]s available in `const` contexts. +//! +//! [`Symbol`]s are [interned strings](https://en.wikipedia.org/wiki/String_interning) that are +//! cheap to store and compare (they're secretly [`u32`]s!). This module contains a list of pre- +//! interned symbol constants that are used in the linter. +//! +//! # Symbol Offsets +//! +//! The linter allocates symbols in the following layout: +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//! +//!
IndexNote
0Symbols in rustc_span::sym
1
2
...
PREDEFINED_SYMBOLS_COUNTSymbols in clippy_utils::sym
...
SYMBOL_OFFSETSymbols in bevy_lint::sym
...
+//! +//! Note that the order here is important. [`clippy_utils`] expects its symbols to start at +//! [`PREDEFINED_SYMBOLS_COUNT`], which is why it's before Bevy's symbols. + +#![allow( + non_upper_case_globals, + reason = "Symbol constants are named as-is so it is easy to see what strings they represent." +)] + +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 rustc_span::sym::{bevy_ecs, plugin, reflect}; +use rustc_span::{Symbol, symbol::PREDEFINED_SYMBOLS_COUNT}; + +/// The starting offset used for the first Bevy-specific symbol. +/// +/// This is used instead of [`PREDEFINED_SYMBOLS_COUNT`] because this takes into account Clippy's +/// pre-interned symbols as well. +const SYMBOL_OFFSET: u32 = PREDEFINED_SYMBOLS_COUNT + CLIPPY_SYMBOLS.len() as u32; + +/// A helper used by `declare_bevy_symbols!` to extract its input. +/// +/// ```ignore +/// assert_eq!(extract_value!(Name), "Name"); +/// assert_eq!(extract_value!(Name: "value"), "value"); +/// ``` +macro_rules! extract_value { + ($name:ident) => { + stringify!($name) + }; + ($name:ident: $value:literal) => { + $value + }; +} + +/// Generates the [`Symbol`] constants and [`BEVY_SYMBOLS`] from a list of name-value pairs. +/// +/// # Example +/// +/// ```ignore +/// declare_bevy_symbols! { +/// // Interns the string "Hello, world" available as the constant named `Hello`. +/// Hello: "Hello, world!", +/// // Interns the string "bevy" available as the constant named `bevy`. This is the shorthand! +/// bevy, +/// } +/// ``` +macro_rules! declare_bevy_symbols { + { + $($name:ident $(: $value:literal)?),* $(,)? + } => { + /// A list of strings that are pre-interned at the beginning of linting through + /// [`Config::extra_symbols`](rustc_interface::interface::Config::extra_symbols). + const BEVY_SYMBOLS: &[&str] = &[ + $( + extract_value!($name $(: $value)?) + ),* + ]; + + $( + #[doc = concat!("A pre-interned [`Symbol`] for the string \"", extract_value!($name $(: $value)?), "\".")] + pub const $name: Symbol = Symbol::new(SYMBOL_OFFSET + ${index()}); + )* + }; +} + +// 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 :) +declare_bevy_symbols! { + add_systems, + app, + App, + base, + bevy_app, + bevy_ptr, + bevy_reflect, + bevy_render, + bevy, + camera, + Camera, + change_detection, + collections, + commands, + Commands, + component, + Component, + deferred_world, + Deferred, + DeferredWorld, + entity_ref, + EntityCommands, + EntityMut, + event, + Event, + Events, + filter, + FilteredEntityMut, + FixedUpdate, + init_resource, + insert_resource, + iter_current_update_events, + main_schedule, + Mut, + MutUntyped, + NonSendMut, + PartialReflect, + Plugin, + PtrMut, + query, + Query, + Reflect, + ResMut, + resource, + Resource, + run, + schedule, + set, + spawn, + system_param, + system, + SystemSet, + With, + world, + World, +} + +/// Returns a list of strings that should be supplied to +/// [`Config::extra_symbols`](rustc_interface::interface::Config::extra_symbols). +pub fn extra_symbols() -> Vec<&'static str> { + let mut symbols = Vec::with_capacity(CLIPPY_SYMBOLS.len() + BEVY_SYMBOLS.len()); + + // The Clippy symbols must be before the Bevy symbols, as `clippy_utils` depends on its + // predefined symbols having specific values. + symbols.extend_from_slice(CLIPPY_SYMBOLS); + symbols.extend_from_slice(BEVY_SYMBOLS); + + symbols +} diff --git a/bevy_lint/src/utils/hir_parse.rs b/bevy_lint/src/utils/hir_parse.rs index 972cd800..e38f02cc 100644 --- a/bevy_lint/src/utils/hir_parse.rs +++ b/bevy_lint/src/utils/hir_parse.rs @@ -1,6 +1,6 @@ //! Utility functions for parsing HIR types. -use clippy_utils::{match_def_path, source::snippet_opt}; +use clippy_utils::{paths::PathLookup, source::snippet_opt}; use rustc_hir::{ Expr, ExprKind, GenericArg, GenericArgs, Impl, Node, Path, PathSegment, QPath, Ty, TyKind, def::{DefKind, Res}, @@ -238,7 +238,7 @@ impl<'tcx> MethodCall<'tcx> { // ``` if let Res::Def(DefKind::AssocFn, def_id) = cx.qpath_res(qpath, path.hir_id) { // Retrieve the identifiers for all the arguments to this function. - let inputs = cx.tcx.fn_arg_names(def_id); + let inputs = cx.tcx.fn_arg_idents(def_id); // If the name of the first argument is `self`, then it *must* be a method. // `self` is a reserved keyword, and cannot be used as a general function @@ -303,10 +303,12 @@ impl<'tcx> MethodCall<'tcx> { } /// Checks if the [`Impl`] implements a given trait from Bevy. -pub fn impls_trait(cx: &LateContext, impl_: &Impl, trait_path: &[&str]) -> bool { +pub fn impls_trait(cx: &LateContext, impl_: &Impl, trait_path: &PathLookup) -> bool { impl_.of_trait.is_some_and(|of_trait| { - matches!(of_trait.path.res, Res::Def(_, trait_def_id) - // is the trait being implemented the specified trait from Bevy - if match_def_path(cx, trait_def_id, trait_path)) + matches!( + of_trait.path.res, + // Is the trait being implemented the specified trait from Bevy? + Res::Def(_, trait_def_id) if trait_path.matches(cx, trait_def_id) + ) }) } diff --git a/docs/src/contribute/linter/how-to/types.md b/docs/src/contribute/linter/how-to/types.md index 9027e8d7..804e3a3e 100644 --- a/docs/src/contribute/linter/how-to/types.md +++ b/docs/src/contribute/linter/how-to/types.md @@ -64,28 +64,18 @@ For more information, see [`Adjustment`], [Type coercions], and [Method lookup]. ## Checking for a Specific Type -Often you have a `Ty`, and want to check if it matches a specific hardcoded type, such as Bevy's [`App`]. You can do this with `clippy_utils`'s [`match_type()`] function: +Often you have a `Ty`, and want to check if it matches a specific hardcoded type, such as Bevy's [`App`]. You can check if a type matches a specific path using [`PathLookup::matches_ty()`](https://doc.rust-lang.org/nightly/nightly-rustc/clippy_utils/paths/struct.PathLookup.html#method.matches_ty). `PathLookup`s used by `bevy_lint` are placed in the `paths` module: ```rust -use clippy_utils::ty::match_type; +// Import the `PathLookup` for Bevy's `App` type. +use crate::paths::APP; -// The absolute path to `App`'s definition. -const APP: [&str; 3] = ["bevy_app", "app", "App"]; - -if match_type(cx, ty, &APP) { +// Returns true if `ty` is an `App`. +if APP.matches_ty(cx, ty) { // ... } ``` -All path constants are defined in `paths.rs`. If you add a new constant, place it there. - -> **Important** -> -> `bevy_app::app` is a [private module], but we still have to refer to it by name because [`struct App`] is within `bevy_app/src/app.rs`. Do not be tricked by re-exported types, such as `bevy::prelude::App`! -> -> [private module]: https://docs.rs/bevy_app/0.16.0-rc.2/src/bevy_app/lib.rs.html#26 -> [`struct App`]: https://docs.rs/bevy_app/0.16.0-rc.2/src/bevy_app/app.rs.html#78-88 - [`App`]: https://docs.rs/bevy/latest/bevy/app/struct.App.html [`match_type()`]: https://doc.rust-lang.org/nightly/nightly-rustc/clippy_utils/ty/fn.match_type.html diff --git a/docs/src/linter/compatibility.md b/docs/src/linter/compatibility.md index 71444bd5..cc8aea0b 100644 --- a/docs/src/linter/compatibility.md +++ b/docs/src/linter/compatibility.md @@ -2,7 +2,7 @@ |`bevy_lint` Version|Rust Version|Rustup Toolchain|Bevy Version| |-|-|-|-| -|0.4.0-dev|1.88.0|`nightly-2025-04-03`|0.16| +|0.4.0-dev|1.89.0|`nightly-2025-05-14`|0.16| |0.3.0|1.88.0|`nightly-2025-04-03`|0.16| |0.2.0|1.87.0|`nightly-2025-02-20`|0.15| |0.1.0|1.84.0|`nightly-2024-11-14`|0.14| diff --git a/docs/src/linter/install.md b/docs/src/linter/install.md index 64ccb0f5..594b12cf 100644 --- a/docs/src/linter/install.md +++ b/docs/src/linter/install.md @@ -26,7 +26,7 @@ bevy lint --yes `bevy_lint` requires a specific nightly Rust toolchain with the `rustc-dev` and `llvm-tools-preview` components. 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 ``` @@ -38,7 +38,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 \ @@ -84,7 +84,7 @@ cargo uninstall bevy_lint You may also wish to uninstall the Rustup toolchain. The following command will uninstall the toolchain required by the latest version of the linter, but you may need to specify a different toolchain from the [compatibility table](compatibility.md): ```sh -rustup toolchain uninstall nightly-2025-04-03 +rustup toolchain uninstall 2025-05-14 ``` ## Upgrade diff --git a/docs/src/linter/troubleshooting.md b/docs/src/linter/troubleshooting.md index 08093bfe..6f9cf13f 100644 --- a/docs/src/linter/troubleshooting.md +++ b/docs/src/linter/troubleshooting.md @@ -6,8 +6,8 @@ If you have `cranelift` setup as a custom codegen backend, you may run into the ``` error: failed to find a `codegen-backends` folder in the sysroot candidates: - * ~/.rustup/toolchains/nightly-2025-04-03-x86_64-unknown-linux-gnu - * ~/.rustup/toolchains/nightly-2025-04-03-x86_64-unknown-linux-gnu + * ~/.rustup/toolchains/nightly-2025-05-14-x86_64-unknown-linux-gnu + * ~/.rustup/toolchains/nightly-2025-05-14-x86_64-unknown-linux-gnu ``` This error occurs because you do not have `cranelift` installed for the specific nightly toolchain that the linter uses. You can fix this by installing `rustc-codegen-cranelift-preview` for the linter's toolchain: diff --git a/rust-toolchain.toml b/rust-toolchain.toml index 508ae58f..8cdc4c90 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -4,6 +4,6 @@ [toolchain] # Writing custom lints requires using nightly Rust. We pin to a specific version of nightly version # so that builds are reproducible. -channel = "nightly-2025-04-03" +channel = "nightly-2025-05-14" # These components are required to use `rustc` crates. components = ["rustc-dev", "llvm-tools-preview"]